Commit c80949fd authored by Shinya Maeda's avatar Shinya Maeda

Simplify FastDestroyAll module

parent b97d0849
...@@ -6,7 +6,6 @@ module Ci ...@@ -6,7 +6,6 @@ module Ci
belongs_to :build, class_name: "Ci::Build", foreign_key: :build_id belongs_to :build, class_name: "Ci::Build", foreign_key: :build_id
default_value_for :data_store, :redis default_value_for :data_store, :redis
fast_destroy_all_with :redis_delete_data, :redis_data_keys
WriteError = Class.new(StandardError) WriteError = Class.new(StandardError)
...@@ -39,6 +38,18 @@ module Ci ...@@ -39,6 +38,18 @@ module Ci
redis.del(keys) redis.del(keys)
end end
end end
##
# FastDestroyAll concerns
def begin_fast_destroy
redis_data_keys
end
##
# FastDestroyAll concerns
def finalize_fast_destroy(keys)
redis_delete_data(keys)
end
end end
## ##
......
...@@ -4,23 +4,33 @@ ...@@ -4,23 +4,33 @@
# In general, `destroy_all` is inefficient because it calls each callback with `DELETE` queries i.e. O(n), whereas, # In general, `destroy_all` is inefficient because it calls each callback with `DELETE` queries i.e. O(n), whereas,
# `delete_all` is efficient as it deletes all rows with a single `DELETE` query. # `delete_all` is efficient as it deletes all rows with a single `DELETE` query.
# #
# It's better to use `delete_all` as much as possible, however, in the real cases, it's hard to adopt because # It's better to use `delete_all` as our best practice, however,
# in some cases, external data (in ObjectStorage/FileStorage/Redis) is assosiated with rows. # if external data (e.g. ObjectStorage, FileStorage or Redis) are assosiated with database records,
# it is difficult to accomplish it.
# #
# This module introduces a protocol to support the adoption with easy way. # This module defines a format to use `delete_all` and delete associated external data.
# You can use in the following scnenes # Here is an exmaple
# - When calling `destroy_all` explicitly #
# e.g. `job.trace_chunks.fast_destroy_all`` # Situation
# - When a parent record is deleted and all children are deleted subsequently (cascade delete) # - `Project` has many `Ci::BuildTraceChunk` through `Ci::Build`
# e.g. `before_destroy -> { run_after_commit(&build_trace_chunks.after_commit_cleanup_proc) }`` # - `Ci::BuildTraceChunk` stores associated data in Redis, so it relies on `dependent: :destroy` and `before_destroy` for the deletion
#
# How to use
# - Define `use_fast_destroy :build_trace_chunks` in `Project` model.
# - Define `begin_fast_destroy` and `finalize_fast_destroy(params)` in `Ci::BuildTraceChunk` model.
# - Use `fast_destroy_all` instead of `destroy` and `destroy_all`
# - Remove `dependent: :destroy` and `before_destroy` as it's no longer need
#
# Expectation
# - When a project is `destroy`ed, the associated trace_chunks will be deleted by `delete_all`,
# and the associated data will be removed, too.
# - When `fast_destroy_all` is called, it also performns as same.
module FastDestroyAll module FastDestroyAll
extend ActiveSupport::Concern extend ActiveSupport::Concern
ForbiddenActionError = Class.new(StandardError) ForbiddenActionError = Class.new(StandardError)
included do included do
class_attribute :_delete_method, :_delete_params_generator
before_destroy do before_destroy do
raise ForbiddenActionError, '`destroy` and `destroy_all` are forbbiden. Please use `fast_destroy_all`' raise ForbiddenActionError, '`destroy` and `destroy_all` are forbbiden. Please use `fast_destroy_all`'
end end
...@@ -28,40 +38,31 @@ module FastDestroyAll ...@@ -28,40 +38,31 @@ module FastDestroyAll
class_methods do class_methods do
## ##
# This method is for registering :delete_method and :delete_params_generator # This method delete rows and associated external data efficiently
# You have to define the method if you want to use `FastDestroyAll`` module.
# #
# e.g. fast_destroy_all_with :delete_all_redis_data, :redis_all_data_keys # This method can replace `destroy` and `destroy_all` withtout having `after_destroy` hook
def fast_destroy_all_with(delete_method, delete_params_generator) def fast_destroy_all
self._delete_method = delete_method params = begin_fast_destroy
self._delete_params_generator = delete_params_generator
delete_all
finalize_fast_destroy(params)
end end
## ##
# This method generates a proc to delete external data. # This method returns identifiers to delete associated external data (e.g. file paths, redis keys)
# It's useful especially when you want to hook parent record's deletion event.
# #
# e.g. before_destroy -> { run_after_commit(&build_trace_chunks.after_commit_cleanup_proc) } # This method must be defined in fast destroyable model
def after_commit_cleanup_proc def begin_fast_destroy
params = send self._delete_params_generator # rubocop:disable GitlabSecurity/PublicSend raise NotImplementedError
subject = self # Preserve the subject class, otherwise `proc` uses a different class
proc do
subject.send subject._delete_method, params # rubocop:disable GitlabSecurity/PublicSend
true
end
end end
## ##
# This method deletes all rows at first and delete all external data at second. # This method deletes associated external data with the identifiers returned by `begin_fast_destroy`
# Before deleting the rows, it generates a proc to delete external data. #
# So it won't lose the track of deleting external data, even if it happened after rows had been deleted. # This method must be defined in fast destroyable model
def fast_destroy_all def finalize_fast_destroy(params)
after_commit_cleanup_proc.tap do |delete_all_external_data| raise NotImplementedError
delete_all
delete_all_external_data.call
end
end end
end end
...@@ -70,20 +71,21 @@ module FastDestroyAll ...@@ -70,20 +71,21 @@ module FastDestroyAll
class_methods do class_methods do
## ##
# This is a helper method for performaning fast_destroy_all when parent relations are deleted # This method is to be defined on models which have fast destroyable models as children,
# Their children must include `FastDestroyAll` module. # and let us avoid to use `dependent: :destroy` hook
#
# `use_fast_destroy` must be defined **before** `has_many` and `has_one`, such as `has_many :relation, dependent: :destroy`
# Otherwise `use_fast_destroy` performs against **deleted** rows, which fails to get identifiers of external data
#
# e.g. use_fast_destroy :build_trace_chunks
def use_fast_destroy(relation) def use_fast_destroy(relation)
before_destroy do before_destroy do
subject = public_send(relation) # rubocop:disable GitlabSecurity/PublicSend perform_fast_destroy(public_send(relation)) # rubocop:disable GitlabSecurity/PublicSend
after_commit_proc = subject.after_commit_cleanup_proc
run_after_commit(&after_commit_proc)
end end
end end
end end
def perform_fast_destroy(subject)
params = subject.begin_fast_destroy
run_after_commit do
subject.finalize_fast_destroy(params)
end
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment