online_migrations 0.23.0 → 0.24.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/docs/background_data_migrations.md +10 -0
- data/docs/background_schema_migrations.md +10 -0
- data/lib/online_migrations/background_migrations/scheduler.rb +12 -14
- data/lib/online_migrations/background_schema_migrations/scheduler.rb +16 -14
- data/lib/online_migrations/version.rb +1 -1
- data/lib/online_migrations.rb +12 -5
- metadata +2 -3
- data/lib/online_migrations/advisory_lock.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 99170e244f0f008e8d097222c88a96555692aa5afc136087d03341866ed8412c
|
4
|
+
data.tar.gz: 6068a722274c73d8a0ab3c1bed4d1f2673cd99d15ca77c24f7fff5af75e77129
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d2a127aee978d36676d8bdd85352487f66a14289cca28173eb1126aee65ceeea5dda067171ffb320b7c0cb2be903712ab158af12312452180b8e56da1e7b641
|
7
|
+
data.tar.gz: 305f89162f87a3746d91bfdfacae00b7d0f2a374f27755c846cd4386d65694442b70fc6963a7dec8845c192eca2cc625bb020c7c4aec664320a395b124bb3e71
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## master (unreleased)
|
2
2
|
|
3
|
+
## 0.24.0 (2025-01-20)
|
4
|
+
|
5
|
+
- Add ability to run a separate background migrations scheduler per shard
|
6
|
+
|
7
|
+
- Revert "Prevent multiple instances of schedulers from being running simultaneously"
|
8
|
+
|
9
|
+
The feature was implemented using advisory locks, but they do not always play nicely with the
|
10
|
+
database poolers, so the feature was reverted. It is expected now for user's code to prevent
|
11
|
+
multiple instances of the schedulers from being run (by wrapping it into Redis lock etc).
|
12
|
+
|
3
13
|
## 0.23.0 (2025-01-13)
|
4
14
|
|
5
15
|
- Prevent multiple instances of schedulers from being running simultaneously
|
@@ -349,3 +349,13 @@ OnlineMigrations::ApplicationRecord.connects_to database: { writing: :shard_one
|
|
349
349
|
|
350
350
|
By default, ActiveRecord uses the database config named `:primary` (if exists) under the environment section from the `database.yml`.
|
351
351
|
Otherwise, the first config under the environment section is used.
|
352
|
+
|
353
|
+
By default, the scheduler works on a single shard on each run. To run a separate scheduler per shard:
|
354
|
+
|
355
|
+
```ruby
|
356
|
+
[:shard_one, :shard_two, :shard_three].each do |shard|
|
357
|
+
every 1.minute do
|
358
|
+
runner "OnlineMigrations.run_background_data_migrations(shard: :#{shard})"
|
359
|
+
end
|
360
|
+
end
|
361
|
+
```
|
@@ -192,3 +192,13 @@ OnlineMigrations::ApplicationRecord.connects_to database: { writing: :shard_one
|
|
192
192
|
|
193
193
|
By default, ActiveRecord uses the database config named `:primary` (if exists) under the environment section from the `database.yml`.
|
194
194
|
Otherwise, the first config under the environment section is used.
|
195
|
+
|
196
|
+
By default, the scheduler works on a single shard on each run. To run a separate scheduler per shard:
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
[:shard_one, :shard_two, :shard_three].each do |shard|
|
200
|
+
every 1.minute do
|
201
|
+
runner "OnlineMigrations.run_background_schema_migrations(shard: :#{shard})"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
```
|
@@ -9,36 +9,34 @@ module OnlineMigrations
|
|
9
9
|
# successive runs has passed.
|
10
10
|
#
|
11
11
|
# Scheduler should be configured to run periodically, for example, via cron.
|
12
|
+
#
|
12
13
|
# @example Run via whenever
|
13
14
|
# # add this to schedule.rb
|
14
15
|
# every 1.minute do
|
15
|
-
# runner "OnlineMigrations.
|
16
|
+
# runner "OnlineMigrations.run_background_data_migrations"
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# @example Run via whenever (specific shard)
|
20
|
+
# every 1.minute do
|
21
|
+
# runner "OnlineMigrations.run_background_data_migrations(shard: :shard_two)"
|
16
22
|
# end
|
17
23
|
#
|
18
24
|
class Scheduler
|
19
|
-
def self.run
|
20
|
-
new.run
|
25
|
+
def self.run(**options)
|
26
|
+
new.run(**options)
|
21
27
|
end
|
22
28
|
|
23
29
|
# Runs Scheduler
|
24
|
-
def run
|
30
|
+
def run(**options)
|
25
31
|
active_migrations = Migration.runnable.active.queue_order
|
32
|
+
active_migrations = active_migrations.where(shard: options[:shard]) if options.key?(:shard)
|
26
33
|
runnable_migration = active_migrations.select(&:interval_elapsed?).first
|
27
34
|
|
28
35
|
if runnable_migration
|
29
36
|
runner = MigrationRunner.new(runnable_migration)
|
30
|
-
|
31
|
-
try_with_lock do
|
32
|
-
runner.run_migration_job
|
33
|
-
end
|
37
|
+
runner.run_migration_job
|
34
38
|
end
|
35
39
|
end
|
36
|
-
|
37
|
-
private
|
38
|
-
def try_with_lock(&block)
|
39
|
-
lock = AdvisoryLock.new(name: "online_migrations_data_scheduler")
|
40
|
-
lock.try_with_lock(&block)
|
41
|
-
end
|
42
40
|
end
|
43
41
|
end
|
44
42
|
end
|
@@ -7,34 +7,41 @@ module OnlineMigrations
|
|
7
7
|
# running migration on the same table.
|
8
8
|
#
|
9
9
|
# Scheduler should be configured to run periodically, for example, via cron.
|
10
|
+
#
|
10
11
|
# @example Run via whenever
|
11
12
|
# # add this to schedule.rb
|
12
13
|
# every 1.minute do
|
13
14
|
# runner "OnlineMigrations.run_background_schema_migrations"
|
14
15
|
# end
|
15
16
|
#
|
17
|
+
# @example Run via whenever (specific shard)
|
18
|
+
# every 1.minute do
|
19
|
+
# runner "OnlineMigrations.run_background_schema_migrations(shard: :shard_two)"
|
20
|
+
# end
|
21
|
+
#
|
16
22
|
class Scheduler
|
17
|
-
def self.run
|
18
|
-
new.run
|
23
|
+
def self.run(**options)
|
24
|
+
new.run(**options)
|
19
25
|
end
|
20
26
|
|
21
27
|
# Runs Scheduler
|
22
|
-
def run
|
23
|
-
migration = find_migration
|
28
|
+
def run(**options)
|
29
|
+
migration = find_migration(**options)
|
24
30
|
if migration
|
25
31
|
runner = MigrationRunner.new(migration)
|
26
|
-
|
27
|
-
try_with_lock do
|
28
|
-
runner.run
|
29
|
-
end
|
32
|
+
runner.run
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|
33
36
|
private
|
34
|
-
def find_migration
|
37
|
+
def find_migration(**options)
|
35
38
|
active_migrations = Migration.running.reject(&:stuck?)
|
36
39
|
runnable_migrations = Migration.runnable.enqueued.queue_order.to_a + Migration.retriable.queue_order.to_a
|
37
40
|
|
41
|
+
if options.key?(:shard)
|
42
|
+
runnable_migrations = runnable_migrations.select { |migration| migration.shard.to_s == options[:shard].to_s }
|
43
|
+
end
|
44
|
+
|
38
45
|
runnable_migrations.find do |runnable_migration|
|
39
46
|
active_migrations.none? do |active_migration|
|
40
47
|
active_migration.connection_class_name == runnable_migration.connection_class_name &&
|
@@ -43,11 +50,6 @@ module OnlineMigrations
|
|
43
50
|
end
|
44
51
|
end
|
45
52
|
end
|
46
|
-
|
47
|
-
def try_with_lock(&block)
|
48
|
-
lock = AdvisoryLock.new(name: "online_migrations_schema_scheduler")
|
49
|
-
lock.try_with_lock(&block)
|
50
|
-
end
|
51
53
|
end
|
52
54
|
end
|
53
55
|
end
|
data/lib/online_migrations.rb
CHANGED
@@ -23,7 +23,6 @@ module OnlineMigrations
|
|
23
23
|
|
24
24
|
extend ActiveSupport::Autoload
|
25
25
|
|
26
|
-
autoload :AdvisoryLock
|
27
26
|
autoload :ApplicationRecord
|
28
27
|
autoload :BatchIterator
|
29
28
|
autoload :VerboseSqlLogs
|
@@ -84,14 +83,22 @@ module OnlineMigrations
|
|
84
83
|
end
|
85
84
|
|
86
85
|
# Run background data migrations
|
87
|
-
|
88
|
-
|
86
|
+
#
|
87
|
+
# @option options [String, Symbol, nil] :shard The name of the shard to run
|
88
|
+
# background data migrations on. By default runs on all shards.
|
89
|
+
#
|
90
|
+
def run_background_migrations(**options)
|
91
|
+
BackgroundMigrations::Scheduler.run(**options)
|
89
92
|
end
|
90
93
|
alias run_background_data_migrations run_background_migrations
|
91
94
|
|
92
95
|
# Run background schema migrations
|
93
|
-
|
94
|
-
|
96
|
+
#
|
97
|
+
# @option options [String, Symbol, nil] :shard The name of the shard to run
|
98
|
+
# background schema migrations on. By default runs on all shards.
|
99
|
+
#
|
100
|
+
def run_background_schema_migrations(**options)
|
101
|
+
BackgroundSchemaMigrations::Scheduler.run(**options)
|
95
102
|
end
|
96
103
|
|
97
104
|
def deprecator
|
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.24.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- fatkodima
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-01-
|
11
|
+
date: 2025-01-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -48,7 +48,6 @@ files:
|
|
48
48
|
- lib/generators/online_migrations/templates/migration.rb.tt
|
49
49
|
- lib/generators/online_migrations/upgrade_generator.rb
|
50
50
|
- lib/online_migrations.rb
|
51
|
-
- lib/online_migrations/advisory_lock.rb
|
52
51
|
- lib/online_migrations/application_record.rb
|
53
52
|
- lib/online_migrations/background_migration.rb
|
54
53
|
- lib/online_migrations/background_migrations/backfill_column.rb
|
@@ -1,60 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "zlib"
|
4
|
-
|
5
|
-
module OnlineMigrations
|
6
|
-
# @private
|
7
|
-
class AdvisoryLock
|
8
|
-
attr_reader :name, :connection
|
9
|
-
|
10
|
-
def initialize(name:, connection: ApplicationRecord.connection)
|
11
|
-
@name = name
|
12
|
-
@connection = connection
|
13
|
-
end
|
14
|
-
|
15
|
-
def try_lock
|
16
|
-
locked = connection.select_value("SELECT pg_try_advisory_lock(#{lock_key})")
|
17
|
-
Utils.to_bool(locked)
|
18
|
-
end
|
19
|
-
|
20
|
-
def unlock
|
21
|
-
connection.select_value("SELECT pg_advisory_unlock(#{lock_key})")
|
22
|
-
end
|
23
|
-
|
24
|
-
# Runs the given block if an advisory lock is able to be acquired.
|
25
|
-
def try_with_lock
|
26
|
-
if try_lock
|
27
|
-
begin
|
28
|
-
yield
|
29
|
-
ensure
|
30
|
-
unlock
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def active?
|
36
|
-
objid = lock_key & 0xffffffff
|
37
|
-
classid = lock_key >> 32
|
38
|
-
|
39
|
-
active = connection.select_value(<<~SQL)
|
40
|
-
SELECT granted
|
41
|
-
FROM pg_locks
|
42
|
-
WHERE locktype = 'advisory'
|
43
|
-
AND pid = pg_backend_pid()
|
44
|
-
AND mode = 'ExclusiveLock'
|
45
|
-
AND classid = #{classid}
|
46
|
-
AND objid = #{objid}
|
47
|
-
SQL
|
48
|
-
|
49
|
-
Utils.to_bool(active)
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
SALT = 936723412
|
54
|
-
|
55
|
-
def lock_key
|
56
|
-
name_hash = Zlib.crc32(name)
|
57
|
-
SALT * name_hash
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|