online_migrations 0.23.0 → 0.24.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1052d2cc58898bc561b5e755daccc137d7d6e1d35afa3af5968c2b655ebe5805
4
- data.tar.gz: ad72d601c36ea9192f192fb8941d30a2cfbcf0ed87e0717fb5d4ebf47b20f169
3
+ metadata.gz: 99170e244f0f008e8d097222c88a96555692aa5afc136087d03341866ed8412c
4
+ data.tar.gz: 6068a722274c73d8a0ab3c1bed4d1f2673cd99d15ca77c24f7fff5af75e77129
5
5
  SHA512:
6
- metadata.gz: 697ac61bcb41e00744cfa87965910a3e1d68eaa571ad5c6dc3b3d8a4d14deb16ae0cbc0d9771d8d57668be8529f200736a14c1769d7d8cf6c64fc083060ff74c
7
- data.tar.gz: 7b4e383f7628ba5cc74c2bb86f4460890b70f3052738f0f20db3571ee770e22c49bc548f5c0ef3fd47caa456a84eb4174f031742869612ee3b27505faf49ecec
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.run_background_migrations"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OnlineMigrations
4
- VERSION = "0.23.0"
4
+ VERSION = "0.24.0"
5
5
  end
@@ -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
- def run_background_migrations
88
- BackgroundMigrations::Scheduler.run
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
- def run_background_schema_migrations
94
- BackgroundSchemaMigrations::Scheduler.run
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.23.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-13 00:00:00.000000000 Z
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