online_migrations 0.26.0 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/docs/0.27-upgrade.md +24 -0
- data/docs/background_data_migrations.md +200 -101
- data/docs/background_schema_migrations.md +2 -2
- data/lib/generators/online_migrations/{background_migration_generator.rb → data_migration_generator.rb} +4 -4
- data/lib/generators/online_migrations/templates/change_background_data_migrations.rb.tt +34 -0
- data/lib/generators/online_migrations/templates/{background_data_migration.rb.tt → data_migration.rb.tt} +8 -9
- data/lib/generators/online_migrations/templates/initializer.rb.tt +19 -25
- data/lib/generators/online_migrations/templates/install_migration.rb.tt +9 -40
- data/lib/generators/online_migrations/upgrade_generator.rb +16 -8
- data/lib/online_migrations/active_record_batch_enumerator.rb +8 -0
- data/lib/online_migrations/background_data_migrations/backfill_column.rb +50 -0
- data/lib/online_migrations/background_data_migrations/config.rb +62 -0
- data/lib/online_migrations/{background_migrations → background_data_migrations}/copy_column.rb +15 -28
- data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_associated_records.rb +9 -5
- data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_orphaned_records.rb +5 -9
- data/lib/online_migrations/background_data_migrations/migration.rb +312 -0
- data/lib/online_migrations/{background_migrations → background_data_migrations}/migration_helpers.rb +72 -61
- data/lib/online_migrations/background_data_migrations/migration_job.rb +160 -0
- data/lib/online_migrations/background_data_migrations/migration_status_validator.rb +65 -0
- data/lib/online_migrations/{background_migrations → background_data_migrations}/perform_action_on_relation.rb +5 -5
- data/lib/online_migrations/{background_migrations → background_data_migrations}/reset_counters.rb +5 -5
- data/lib/online_migrations/background_data_migrations/scheduler.rb +78 -0
- data/lib/online_migrations/background_data_migrations/ticker.rb +62 -0
- data/lib/online_migrations/background_schema_migrations/config.rb +2 -2
- data/lib/online_migrations/background_schema_migrations/migration.rb +51 -123
- data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +25 -46
- data/lib/online_migrations/background_schema_migrations/migration_runner.rb +43 -97
- data/lib/online_migrations/background_schema_migrations/scheduler.rb +2 -2
- data/lib/online_migrations/change_column_type_helpers.rb +17 -4
- data/lib/online_migrations/config.rb +4 -4
- data/lib/online_migrations/data_migration.rb +127 -0
- data/lib/online_migrations/error_messages.rb +2 -0
- data/lib/online_migrations/lock_retrier.rb +5 -2
- data/lib/online_migrations/schema_statements.rb +1 -1
- data/lib/online_migrations/shard_aware.rb +44 -0
- data/lib/online_migrations/version.rb +1 -1
- data/lib/online_migrations.rb +18 -11
- metadata +22 -21
- data/lib/online_migrations/background_migration.rb +0 -64
- data/lib/online_migrations/background_migrations/backfill_column.rb +0 -54
- data/lib/online_migrations/background_migrations/background_migration_class_validator.rb +0 -29
- data/lib/online_migrations/background_migrations/config.rb +0 -74
- data/lib/online_migrations/background_migrations/migration.rb +0 -329
- data/lib/online_migrations/background_migrations/migration_job.rb +0 -109
- data/lib/online_migrations/background_migrations/migration_job_runner.rb +0 -66
- data/lib/online_migrations/background_migrations/migration_job_status_validator.rb +0 -29
- data/lib/online_migrations/background_migrations/migration_runner.rb +0 -161
- data/lib/online_migrations/background_migrations/migration_status_validator.rb +0 -48
- data/lib/online_migrations/background_migrations/scheduler.rb +0 -42
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
gem "sidekiq", ">= 7.3.3"
|
4
|
+
require "sidekiq"
|
5
|
+
|
6
|
+
module OnlineMigrations
|
7
|
+
# Base class that is inherited by the host application's data migration classes.
|
8
|
+
class DataMigration
|
9
|
+
class NotFoundError < NameError; end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
# Finds a Data Migration with the given name.
|
13
|
+
#
|
14
|
+
# @param name [String] the name of the Data Migration to be found.
|
15
|
+
#
|
16
|
+
# @return [DataMigration] the Data Migration with the given name.
|
17
|
+
#
|
18
|
+
# @raise [NotFoundError] if a Data Migration with the given name does not exist.
|
19
|
+
#
|
20
|
+
def named(name)
|
21
|
+
namespace = OnlineMigrations.config.background_data_migrations.migrations_module.constantize
|
22
|
+
internal_namespace = ::OnlineMigrations::BackgroundDataMigrations
|
23
|
+
|
24
|
+
migration = "#{namespace}::#{name}".safe_constantize ||
|
25
|
+
"#{internal_namespace}::#{name}".safe_constantize
|
26
|
+
|
27
|
+
raise NotFoundError.new("Data Migration #{name} not found", name) if migration.nil?
|
28
|
+
if !(migration.is_a?(Class) && migration < self)
|
29
|
+
raise NotFoundError.new("#{name} is not a Data Migration", name)
|
30
|
+
end
|
31
|
+
|
32
|
+
migration
|
33
|
+
end
|
34
|
+
|
35
|
+
# @private
|
36
|
+
attr_accessor :active_record_enumerator_batch_size
|
37
|
+
|
38
|
+
# Limit the number of records that will be fetched in a single query when
|
39
|
+
# iterating over an Active Record collection migration.
|
40
|
+
#
|
41
|
+
# @param size [Integer] the number of records to fetch in a single query.
|
42
|
+
#
|
43
|
+
def collection_batch_size(size)
|
44
|
+
self.active_record_enumerator_batch_size = size
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# A hook to override that will be called when the migration starts running.
|
49
|
+
#
|
50
|
+
def after_start
|
51
|
+
end
|
52
|
+
|
53
|
+
# A hook to override that will be called around 'process' each time.
|
54
|
+
#
|
55
|
+
# Can be useful for some metrics collection, performance tracking etc.
|
56
|
+
#
|
57
|
+
def around_process
|
58
|
+
yield
|
59
|
+
end
|
60
|
+
|
61
|
+
# A hook to override that will be called when the migration resumes its work.
|
62
|
+
#
|
63
|
+
def after_resume
|
64
|
+
end
|
65
|
+
|
66
|
+
# A hook to override that will be called each time the migration is interrupted.
|
67
|
+
#
|
68
|
+
# This can be due to interruption or sidekiq stopping.
|
69
|
+
#
|
70
|
+
def after_stop
|
71
|
+
end
|
72
|
+
|
73
|
+
# A hook to override that will be called when the migration finished its work.
|
74
|
+
#
|
75
|
+
def after_complete
|
76
|
+
end
|
77
|
+
|
78
|
+
# A hook to override that will be called when the migration is paused.
|
79
|
+
#
|
80
|
+
def after_pause
|
81
|
+
end
|
82
|
+
|
83
|
+
# A hook to override that will be called when the migration is cancelled.
|
84
|
+
#
|
85
|
+
def after_cancel
|
86
|
+
end
|
87
|
+
|
88
|
+
# The collection to be processed.
|
89
|
+
#
|
90
|
+
# @return [ActiveRecord::Relation, ActiveRecord::Batches::BatchEnumerator, Array, Enumerator]
|
91
|
+
#
|
92
|
+
# @raise [NotImplementedError] with a message advising subclasses to override this method.
|
93
|
+
#
|
94
|
+
def collection
|
95
|
+
raise NotImplementedError, "#{self.class.name} must implement a 'collection' method"
|
96
|
+
end
|
97
|
+
|
98
|
+
# The action to be performed on each item from the collection.
|
99
|
+
#
|
100
|
+
# @param _item the current item from the collection being iterated
|
101
|
+
# @raise [NotImplementedError] with a message advising subclasses to override this method.
|
102
|
+
#
|
103
|
+
def process(_item)
|
104
|
+
raise NotImplementedError, "#{self.class.name} must implement a 'process' method"
|
105
|
+
end
|
106
|
+
|
107
|
+
# Total count of iterations to be performed (optional, to be able to show progress).
|
108
|
+
#
|
109
|
+
# @return [Integer, nil]
|
110
|
+
#
|
111
|
+
def count
|
112
|
+
end
|
113
|
+
|
114
|
+
# Enumerator builder. You may override this method to return any Enumerator yielding
|
115
|
+
# pairs of `[item, item_cursor]`, instead of using `collection`.
|
116
|
+
#
|
117
|
+
# It is useful when it is not practical or impossible to define an explicit collection
|
118
|
+
# in the `collection` method.
|
119
|
+
#
|
120
|
+
# @param cursor [Object, nil] cursor position to resume from, or nil on initial call.
|
121
|
+
#
|
122
|
+
# @return [Enumerator]
|
123
|
+
#
|
124
|
+
def build_enumerator(cursor:)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -230,6 +230,8 @@ during writes works automatically). For most column type changes, this does not
|
|
230
230
|
7. Finally, if everything works as expected, remove copy trigger and old column:
|
231
231
|
|
232
232
|
class Cleanup<%= migration_name %> < <%= migration_parent %>
|
233
|
+
disable_ddl_transaction!
|
234
|
+
|
233
235
|
def up
|
234
236
|
<%= cleanup_code %>
|
235
237
|
end
|
@@ -91,10 +91,13 @@ module OnlineMigrations
|
|
91
91
|
else
|
92
92
|
yield
|
93
93
|
end
|
94
|
-
rescue ActiveRecord::LockWaitTimeout
|
94
|
+
rescue ActiveRecord::LockWaitTimeout, ActiveRecord::Deadlocked => e
|
95
95
|
if current_attempt <= attempts
|
96
96
|
current_delay = delay(current_attempt)
|
97
|
-
|
97
|
+
|
98
|
+
problem = e.is_a?(ActiveRecord::Deadlocked) ? "Deadlock detected." : "Lock timeout."
|
99
|
+
Utils.say("#{problem} Retrying in #{current_delay} seconds...")
|
100
|
+
|
98
101
|
sleep(current_delay)
|
99
102
|
retry
|
100
103
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module OnlineMigrations
|
4
4
|
module SchemaStatements
|
5
5
|
include ChangeColumnTypeHelpers
|
6
|
-
include
|
6
|
+
include BackgroundDataMigrations::MigrationHelpers
|
7
7
|
include BackgroundSchemaMigrations::MigrationHelpers
|
8
8
|
|
9
9
|
# Updates the value of a column in batches.
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OnlineMigrations
|
4
|
+
module ShardAware
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
before_validation :set_connection_class_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def on_shard_if_present(&block)
|
12
|
+
if shard
|
13
|
+
connection_class.connected_to(shard: shard.to_sym, role: :writing, &block)
|
14
|
+
else
|
15
|
+
yield
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def connection_class_name=(value)
|
20
|
+
if value && (klass = value.safe_constantize)
|
21
|
+
if !(klass <= ActiveRecord::Base)
|
22
|
+
raise ArgumentError, "connection_class_name is not an ActiveRecord::Base child class"
|
23
|
+
end
|
24
|
+
|
25
|
+
connection_class = Utils.find_connection_class(klass)
|
26
|
+
super(connection_class.name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @private
|
31
|
+
def connection_class
|
32
|
+
if connection_class_name && (klass = connection_class_name.safe_constantize)
|
33
|
+
Utils.find_connection_class(klass)
|
34
|
+
else
|
35
|
+
ActiveRecord::Base
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def set_connection_class_name
|
41
|
+
self.connection_class_name ||= "ActiveRecord::Base"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/online_migrations.rb
CHANGED
@@ -6,7 +6,8 @@ require "online_migrations/version"
|
|
6
6
|
require "online_migrations/utils"
|
7
7
|
require "online_migrations/background_schema_migrations/migration_helpers"
|
8
8
|
require "online_migrations/change_column_type_helpers"
|
9
|
-
require "online_migrations/
|
9
|
+
require "online_migrations/background_data_migrations/migration_helpers"
|
10
|
+
require "online_migrations/active_record_batch_enumerator"
|
10
11
|
require "online_migrations/schema_statements"
|
11
12
|
require "online_migrations/schema_cache"
|
12
13
|
require "online_migrations/migration"
|
@@ -29,7 +30,8 @@ module OnlineMigrations
|
|
29
30
|
autoload :ForeignKeysCollector
|
30
31
|
autoload :IndexDefinition
|
31
32
|
autoload :CommandChecker
|
32
|
-
autoload :
|
33
|
+
autoload :DataMigration
|
34
|
+
autoload :ShardAware
|
33
35
|
|
34
36
|
autoload_at "online_migrations/lock_retrier" do
|
35
37
|
autoload :LockRetrier
|
@@ -40,24 +42,21 @@ module OnlineMigrations
|
|
40
42
|
|
41
43
|
autoload :CopyTrigger
|
42
44
|
|
43
|
-
module
|
45
|
+
module BackgroundDataMigrations
|
44
46
|
extend ActiveSupport::Autoload
|
45
47
|
|
46
48
|
autoload :Config
|
47
49
|
autoload :MigrationStatusValidator
|
48
|
-
autoload :MigrationJobStatusValidator
|
49
|
-
autoload :BackgroundMigrationClassValidator
|
50
50
|
autoload :BackfillColumn
|
51
51
|
autoload :CopyColumn
|
52
52
|
autoload :DeleteAssociatedRecords
|
53
53
|
autoload :DeleteOrphanedRecords
|
54
54
|
autoload :PerformActionOnRelation
|
55
55
|
autoload :ResetCounters
|
56
|
-
autoload :MigrationJob
|
57
56
|
autoload :Migration
|
58
|
-
autoload :
|
59
|
-
autoload :MigrationRunner
|
57
|
+
autoload :MigrationJob
|
60
58
|
autoload :Scheduler
|
59
|
+
autoload :Ticker
|
61
60
|
end
|
62
61
|
|
63
62
|
module BackgroundSchemaMigrations
|
@@ -70,6 +69,10 @@ module OnlineMigrations
|
|
70
69
|
autoload :Scheduler
|
71
70
|
end
|
72
71
|
|
72
|
+
# Make aliases for less typing.
|
73
|
+
DataMigrations = BackgroundDataMigrations
|
74
|
+
SchemaMigrations = BackgroundSchemaMigrations
|
75
|
+
|
73
76
|
class << self
|
74
77
|
# @private
|
75
78
|
attr_accessor :current_migration
|
@@ -87,10 +90,10 @@ module OnlineMigrations
|
|
87
90
|
# @option options [String, Symbol, nil] :shard The name of the shard to run
|
88
91
|
# background data migrations on. By default runs on all shards.
|
89
92
|
#
|
90
|
-
def
|
91
|
-
|
93
|
+
def run_background_data_migrations(**options)
|
94
|
+
BackgroundDataMigrations::Scheduler.run(**options)
|
92
95
|
end
|
93
|
-
alias run_background_data_migrations
|
96
|
+
alias run_background_migrations run_background_data_migrations
|
94
97
|
|
95
98
|
# Run background schema migrations
|
96
99
|
#
|
@@ -122,6 +125,10 @@ module OnlineMigrations
|
|
122
125
|
else
|
123
126
|
ActiveRecord::ConnectionAdapters::SchemaCache.prepend(OnlineMigrations::SchemaCache)
|
124
127
|
end
|
128
|
+
|
129
|
+
if !ActiveRecord::Batches::BatchEnumerator.method_defined?(:use_ranges)
|
130
|
+
ActiveRecord::Batches::BatchEnumerator.include(OnlineMigrations::ActiveRecordBatchEnumerator)
|
131
|
+
end
|
125
132
|
end
|
126
133
|
end
|
127
134
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: online_migrations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.27.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- fatkodima
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-05-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -34,39 +34,38 @@ files:
|
|
34
34
|
- CHANGELOG.md
|
35
35
|
- LICENSE.txt
|
36
36
|
- README.md
|
37
|
+
- docs/0.27-upgrade.md
|
37
38
|
- docs/background_data_migrations.md
|
38
39
|
- docs/background_schema_migrations.md
|
39
40
|
- docs/configuring.md
|
40
|
-
- lib/generators/online_migrations/
|
41
|
+
- lib/generators/online_migrations/data_migration_generator.rb
|
41
42
|
- lib/generators/online_migrations/install_generator.rb
|
42
43
|
- lib/generators/online_migrations/templates/add_sharding_to_online_migrations.rb.tt
|
43
44
|
- lib/generators/online_migrations/templates/add_timestamps_to_background_migrations.rb.tt
|
44
|
-
- lib/generators/online_migrations/templates/background_data_migration.rb.tt
|
45
45
|
- lib/generators/online_migrations/templates/background_schema_migrations_change_unique_index.rb.tt
|
46
|
+
- lib/generators/online_migrations/templates/change_background_data_migrations.rb.tt
|
46
47
|
- lib/generators/online_migrations/templates/create_background_schema_migrations.rb.tt
|
48
|
+
- lib/generators/online_migrations/templates/data_migration.rb.tt
|
47
49
|
- lib/generators/online_migrations/templates/initializer.rb.tt
|
48
50
|
- lib/generators/online_migrations/templates/install_migration.rb.tt
|
49
51
|
- lib/generators/online_migrations/templates/migration.rb.tt
|
50
52
|
- lib/generators/online_migrations/upgrade_generator.rb
|
51
53
|
- lib/online_migrations.rb
|
54
|
+
- lib/online_migrations/active_record_batch_enumerator.rb
|
52
55
|
- lib/online_migrations/application_record.rb
|
53
|
-
- lib/online_migrations/
|
54
|
-
- lib/online_migrations/
|
55
|
-
- lib/online_migrations/
|
56
|
-
- lib/online_migrations/
|
57
|
-
- lib/online_migrations/
|
58
|
-
- lib/online_migrations/
|
59
|
-
- lib/online_migrations/
|
60
|
-
- lib/online_migrations/
|
61
|
-
- lib/online_migrations/
|
62
|
-
- lib/online_migrations/
|
63
|
-
- lib/online_migrations/
|
64
|
-
- lib/online_migrations/
|
65
|
-
- lib/online_migrations/
|
66
|
-
- lib/online_migrations/background_migrations/migration_status_validator.rb
|
67
|
-
- lib/online_migrations/background_migrations/perform_action_on_relation.rb
|
68
|
-
- lib/online_migrations/background_migrations/reset_counters.rb
|
69
|
-
- lib/online_migrations/background_migrations/scheduler.rb
|
56
|
+
- lib/online_migrations/background_data_migrations/backfill_column.rb
|
57
|
+
- lib/online_migrations/background_data_migrations/config.rb
|
58
|
+
- lib/online_migrations/background_data_migrations/copy_column.rb
|
59
|
+
- lib/online_migrations/background_data_migrations/delete_associated_records.rb
|
60
|
+
- lib/online_migrations/background_data_migrations/delete_orphaned_records.rb
|
61
|
+
- lib/online_migrations/background_data_migrations/migration.rb
|
62
|
+
- lib/online_migrations/background_data_migrations/migration_helpers.rb
|
63
|
+
- lib/online_migrations/background_data_migrations/migration_job.rb
|
64
|
+
- lib/online_migrations/background_data_migrations/migration_status_validator.rb
|
65
|
+
- lib/online_migrations/background_data_migrations/perform_action_on_relation.rb
|
66
|
+
- lib/online_migrations/background_data_migrations/reset_counters.rb
|
67
|
+
- lib/online_migrations/background_data_migrations/scheduler.rb
|
68
|
+
- lib/online_migrations/background_data_migrations/ticker.rb
|
70
69
|
- lib/online_migrations/background_schema_migrations/config.rb
|
71
70
|
- lib/online_migrations/background_schema_migrations/migration.rb
|
72
71
|
- lib/online_migrations/background_schema_migrations/migration_helpers.rb
|
@@ -79,6 +78,7 @@ files:
|
|
79
78
|
- lib/online_migrations/command_recorder.rb
|
80
79
|
- lib/online_migrations/config.rb
|
81
80
|
- lib/online_migrations/copy_trigger.rb
|
81
|
+
- lib/online_migrations/data_migration.rb
|
82
82
|
- lib/online_migrations/database_tasks.rb
|
83
83
|
- lib/online_migrations/error_messages.rb
|
84
84
|
- lib/online_migrations/foreign_keys_collector.rb
|
@@ -89,6 +89,7 @@ files:
|
|
89
89
|
- lib/online_migrations/schema_cache.rb
|
90
90
|
- lib/online_migrations/schema_dumper.rb
|
91
91
|
- lib/online_migrations/schema_statements.rb
|
92
|
+
- lib/online_migrations/shard_aware.rb
|
92
93
|
- lib/online_migrations/utils.rb
|
93
94
|
- lib/online_migrations/verbose_sql_logs.rb
|
94
95
|
- lib/online_migrations/version.rb
|
@@ -1,64 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OnlineMigrations
|
4
|
-
# Base class that is inherited by the host application's background migration classes.
|
5
|
-
class BackgroundMigration
|
6
|
-
class NotFoundError < NameError; end
|
7
|
-
|
8
|
-
class << self
|
9
|
-
# Finds a Background Migration with the given name.
|
10
|
-
#
|
11
|
-
# @param name [String] the name of the Background Migration to be found.
|
12
|
-
#
|
13
|
-
# @return [BackgroundMigration] the Background Migration with the given name.
|
14
|
-
#
|
15
|
-
# @raise [NotFoundError] if a Background Migration with the given name does not exist.
|
16
|
-
#
|
17
|
-
def named(name)
|
18
|
-
namespace = OnlineMigrations.config.background_migrations.migrations_module.constantize
|
19
|
-
internal_namespace = ::OnlineMigrations::BackgroundMigrations
|
20
|
-
|
21
|
-
migration = "#{namespace}::#{name}".safe_constantize ||
|
22
|
-
"#{internal_namespace}::#{name}".safe_constantize
|
23
|
-
|
24
|
-
raise NotFoundError.new("Background Migration #{name} not found", name) if migration.nil?
|
25
|
-
if !(migration.is_a?(Class) && migration < self)
|
26
|
-
raise NotFoundError.new("#{name} is not a Background Migration", name)
|
27
|
-
end
|
28
|
-
|
29
|
-
migration
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
# The relation to be iterated over.
|
34
|
-
#
|
35
|
-
# @return [ActiveRecord::Relation]
|
36
|
-
#
|
37
|
-
# @raise [NotImplementedError] with a message advising subclasses to
|
38
|
-
# implement an override for this method.
|
39
|
-
#
|
40
|
-
def relation
|
41
|
-
raise NotImplementedError, "#{self.class.name} must implement a 'relation' method"
|
42
|
-
end
|
43
|
-
|
44
|
-
# Processes one batch.
|
45
|
-
#
|
46
|
-
# @param _relation [ActiveRecord::Relation] the current batch from the enumerator being iterated
|
47
|
-
# @return [void]
|
48
|
-
#
|
49
|
-
# @raise [NotImplementedError] with a message advising subclasses to
|
50
|
-
# implement an override for this method.
|
51
|
-
#
|
52
|
-
def process_batch(_relation)
|
53
|
-
raise NotImplementedError, "#{self.class.name} must implement a 'process_batch' method"
|
54
|
-
end
|
55
|
-
|
56
|
-
# Returns the count of rows that will be iterated over (optional, to be able to show progress).
|
57
|
-
#
|
58
|
-
# @return [Integer, nil, :no_count]
|
59
|
-
#
|
60
|
-
def count
|
61
|
-
:no_count
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
@@ -1,54 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OnlineMigrations
|
4
|
-
module BackgroundMigrations
|
5
|
-
# @private
|
6
|
-
class BackfillColumn < BackgroundMigration
|
7
|
-
attr_reader :table_name, :updates, :model_name
|
8
|
-
|
9
|
-
def initialize(table_name, updates, model_name = nil)
|
10
|
-
@table_name = table_name
|
11
|
-
@updates = updates
|
12
|
-
@model_name = model_name
|
13
|
-
end
|
14
|
-
|
15
|
-
def relation
|
16
|
-
column, value = updates.first
|
17
|
-
|
18
|
-
if updates.size == 1 && !value.nil?
|
19
|
-
# If value is nil, the generated SQL is correct (`WHERE column IS NOT NULL`).
|
20
|
-
# Otherwise, the SQL is `WHERE column != value`. This condition ignores column
|
21
|
-
# with NULLs in it, so we need to also manually check for NULLs.
|
22
|
-
quoted_column = connection.quote_column_name(column)
|
23
|
-
model.unscoped.where("#{quoted_column} != ? OR #{quoted_column} IS NULL", value)
|
24
|
-
else
|
25
|
-
model.unscoped.where.not(updates)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def process_batch(relation)
|
30
|
-
relation.update_all(updates)
|
31
|
-
end
|
32
|
-
|
33
|
-
def count
|
34
|
-
# Exact counts are expensive on large tables, since PostgreSQL
|
35
|
-
# needs to do a full scan. An estimated count should give a pretty decent
|
36
|
-
# approximation of rows count in this case.
|
37
|
-
Utils.estimated_count(connection, table_name)
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
def model
|
42
|
-
@model ||= if model_name.present?
|
43
|
-
Object.const_get(model_name, false)
|
44
|
-
else
|
45
|
-
Utils.define_model(table_name)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def connection
|
50
|
-
model.connection
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OnlineMigrations
|
4
|
-
module BackgroundMigrations
|
5
|
-
# @private
|
6
|
-
class BackgroundMigrationClassValidator < ActiveModel::Validator
|
7
|
-
def validate(record)
|
8
|
-
relation = record.migration_relation
|
9
|
-
migration_name = record.migration_name
|
10
|
-
|
11
|
-
if !relation.is_a?(ActiveRecord::Relation)
|
12
|
-
record.errors.add(
|
13
|
-
:migration_name,
|
14
|
-
"#{migration_name}#relation must return an ActiveRecord::Relation object"
|
15
|
-
)
|
16
|
-
return
|
17
|
-
end
|
18
|
-
|
19
|
-
if relation.arel.orders.present? || relation.arel.taken.present?
|
20
|
-
record.errors.add(
|
21
|
-
:migration_name,
|
22
|
-
"#{migration_name}#relation cannot use ORDER BY or LIMIT due to the way how iteration with a cursor is designed. " \
|
23
|
-
"You can use other ways to limit the number of rows, e.g. a WHERE condition on the primary key column."
|
24
|
-
)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
@@ -1,74 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OnlineMigrations
|
4
|
-
module BackgroundMigrations
|
5
|
-
# Class representing configuration options for background migrations.
|
6
|
-
class Config
|
7
|
-
# The path where generated background migrations will be placed
|
8
|
-
# @return [String] defaults to "lib"
|
9
|
-
attr_accessor :migrations_path
|
10
|
-
|
11
|
-
# The module in which background migrations will be placed
|
12
|
-
# @return [String] defaults to "OnlineMigrations::BackgroundMigrations"
|
13
|
-
attr_accessor :migrations_module
|
14
|
-
|
15
|
-
# The number of rows to process in a single background migration run
|
16
|
-
# @return [Integer] defaults to 1_000
|
17
|
-
#
|
18
|
-
attr_accessor :batch_size
|
19
|
-
|
20
|
-
# The smaller batches size that the batches will be divided into
|
21
|
-
# @return [Integer] defaults to 100
|
22
|
-
#
|
23
|
-
attr_accessor :sub_batch_size
|
24
|
-
|
25
|
-
# The pause interval between each background migration job's execution (in seconds)
|
26
|
-
# @return [Integer] defaults to 0
|
27
|
-
#
|
28
|
-
attr_accessor :batch_pause
|
29
|
-
|
30
|
-
# The number of milliseconds to sleep between each sub_batch execution
|
31
|
-
# @return [Integer] defaults to 100 milliseconds
|
32
|
-
#
|
33
|
-
attr_accessor :sub_batch_pause_ms
|
34
|
-
|
35
|
-
# Maximum number of batch run attempts
|
36
|
-
#
|
37
|
-
# When attempts are exhausted, the individual batch is marked as failed.
|
38
|
-
# @return [Integer] defaults to 5
|
39
|
-
#
|
40
|
-
attr_accessor :batch_max_attempts
|
41
|
-
|
42
|
-
# The number of seconds that must pass before the running job is considered stuck
|
43
|
-
#
|
44
|
-
# @return [Integer] defaults to 1 hour
|
45
|
-
#
|
46
|
-
attr_accessor :stuck_jobs_timeout
|
47
|
-
|
48
|
-
# The callback to perform when an error occurs in the migration job.
|
49
|
-
#
|
50
|
-
# @example
|
51
|
-
# OnlineMigrations.config.background_migrations.error_handler = ->(error, errored_job) do
|
52
|
-
# Bugsnag.notify(error) do |notification|
|
53
|
-
# notification.add_metadata(:background_migration, { name: errored_job.migration_name })
|
54
|
-
# end
|
55
|
-
# end
|
56
|
-
#
|
57
|
-
# @return [Proc] the callback to perform when an error occurs in the migration job
|
58
|
-
#
|
59
|
-
attr_accessor :error_handler
|
60
|
-
|
61
|
-
def initialize
|
62
|
-
@migrations_path = "lib"
|
63
|
-
@migrations_module = "OnlineMigrations::BackgroundMigrations"
|
64
|
-
@batch_size = 1_000
|
65
|
-
@sub_batch_size = 100
|
66
|
-
@batch_pause = 0.seconds
|
67
|
-
@sub_batch_pause_ms = 100
|
68
|
-
@batch_max_attempts = 5
|
69
|
-
@stuck_jobs_timeout = 1.hour
|
70
|
-
@error_handler = ->(error, errored_job) {}
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|