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 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