inst-jobs 2.0.0 → 3.1.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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/db/migrate/20101216224513_create_delayed_jobs.rb +9 -7
  3. data/db/migrate/20110531144916_cleanup_delayed_jobs_indexes.rb +8 -13
  4. data/db/migrate/20110610213249_optimize_delayed_jobs.rb +8 -8
  5. data/db/migrate/20110831210257_add_delayed_jobs_next_in_strand.rb +25 -25
  6. data/db/migrate/20120510004759_delayed_jobs_delete_trigger_lock_for_update.rb +4 -8
  7. data/db/migrate/20120531150712_drop_psql_jobs_pop_fn.rb +1 -3
  8. data/db/migrate/20120607164022_delayed_jobs_use_advisory_locks.rb +11 -15
  9. data/db/migrate/20120607181141_index_jobs_on_locked_by.rb +1 -1
  10. data/db/migrate/20120608191051_add_jobs_run_at_index.rb +2 -2
  11. data/db/migrate/20120927184213_change_delayed_jobs_handler_to_text.rb +1 -1
  12. data/db/migrate/20140505215510_copy_failed_jobs_original_id.rb +2 -3
  13. data/db/migrate/20150807133223_add_max_concurrent_to_jobs.rb +9 -13
  14. data/db/migrate/20151210162949_improve_max_concurrent.rb +4 -8
  15. data/db/migrate/20161206323555_add_back_default_string_limits_jobs.rb +3 -2
  16. data/db/migrate/20181217155351_speed_up_max_concurrent_triggers.rb +13 -17
  17. data/db/migrate/20200330230722_add_id_to_get_delayed_jobs_index.rb +8 -8
  18. data/db/migrate/20200824222232_speed_up_max_concurrent_delete_trigger.rb +72 -77
  19. data/db/migrate/20200825011002_add_strand_order_override.rb +93 -97
  20. data/db/migrate/20210809145804_add_n_strand_index.rb +12 -0
  21. data/db/migrate/20210812210128_add_singleton_column.rb +200 -0
  22. data/db/migrate/20210917232626_add_delete_conflicting_singletons_before_unlock_trigger.rb +27 -0
  23. data/db/migrate/20210928174754_fix_singleton_condition_in_before_insert.rb +56 -0
  24. data/db/migrate/20210929204903_update_conflicting_singleton_function_to_use_index.rb +27 -0
  25. data/db/migrate/20211101190934_update_after_delete_trigger_for_singleton_index.rb +137 -0
  26. data/db/migrate/20211207094200_update_after_delete_trigger_for_singleton_transition_cases.rb +171 -0
  27. data/db/migrate/20211220112800_fix_singleton_race_condition_insert.rb +59 -0
  28. data/db/migrate/20211220113000_fix_singleton_race_condition_delete.rb +207 -0
  29. data/db/migrate/20220127091200_fix_singleton_unique_constraint.rb +31 -0
  30. data/db/migrate/20220128084800_update_insert_trigger_for_singleton_unique_constraint_change.rb +60 -0
  31. data/db/migrate/20220128084900_update_delete_trigger_for_singleton_unique_constraint_change.rb +209 -0
  32. data/db/migrate/20220203063200_remove_old_singleton_index.rb +31 -0
  33. data/db/migrate/20220328152900_add_failed_jobs_indicies.rb +12 -0
  34. data/exe/inst_jobs +3 -2
  35. data/lib/delayed/backend/active_record.rb +226 -168
  36. data/lib/delayed/backend/base.rb +119 -72
  37. data/lib/delayed/batch.rb +11 -9
  38. data/lib/delayed/cli.rb +98 -84
  39. data/lib/delayed/core_ext/kernel.rb +4 -2
  40. data/lib/delayed/daemon.rb +70 -74
  41. data/lib/delayed/job_tracking.rb +26 -25
  42. data/lib/delayed/lifecycle.rb +28 -23
  43. data/lib/delayed/log_tailer.rb +17 -17
  44. data/lib/delayed/logging.rb +13 -16
  45. data/lib/delayed/message_sending.rb +43 -52
  46. data/lib/delayed/performable_method.rb +6 -8
  47. data/lib/delayed/periodic.rb +72 -68
  48. data/lib/delayed/plugin.rb +2 -4
  49. data/lib/delayed/pool.rb +205 -168
  50. data/lib/delayed/rails_reloader_plugin.rb +30 -0
  51. data/lib/delayed/server/helpers.rb +6 -6
  52. data/lib/delayed/server.rb +51 -54
  53. data/lib/delayed/settings.rb +96 -81
  54. data/lib/delayed/testing.rb +21 -22
  55. data/lib/delayed/version.rb +1 -1
  56. data/lib/delayed/work_queue/in_process.rb +21 -17
  57. data/lib/delayed/work_queue/parent_process/client.rb +55 -53
  58. data/lib/delayed/work_queue/parent_process/server.rb +245 -207
  59. data/lib/delayed/work_queue/parent_process.rb +52 -53
  60. data/lib/delayed/worker/consul_health_check.rb +32 -33
  61. data/lib/delayed/worker/health_check.rb +35 -27
  62. data/lib/delayed/worker/null_health_check.rb +3 -1
  63. data/lib/delayed/worker/process_helper.rb +11 -12
  64. data/lib/delayed/worker.rb +257 -244
  65. data/lib/delayed/yaml_extensions.rb +12 -10
  66. data/lib/delayed_job.rb +37 -37
  67. data/lib/inst-jobs.rb +1 -1
  68. data/spec/active_record_job_spec.rb +152 -139
  69. data/spec/delayed/cli_spec.rb +7 -7
  70. data/spec/delayed/daemon_spec.rb +10 -9
  71. data/spec/delayed/message_sending_spec.rb +16 -9
  72. data/spec/delayed/periodic_spec.rb +14 -21
  73. data/spec/delayed/server_spec.rb +38 -38
  74. data/spec/delayed/settings_spec.rb +26 -25
  75. data/spec/delayed/work_queue/in_process_spec.rb +8 -9
  76. data/spec/delayed/work_queue/parent_process/client_spec.rb +17 -12
  77. data/spec/delayed/work_queue/parent_process/server_spec.rb +118 -42
  78. data/spec/delayed/work_queue/parent_process_spec.rb +21 -23
  79. data/spec/delayed/worker/consul_health_check_spec.rb +37 -50
  80. data/spec/delayed/worker/health_check_spec.rb +60 -52
  81. data/spec/delayed/worker_spec.rb +53 -24
  82. data/spec/sample_jobs.rb +45 -15
  83. data/spec/shared/delayed_batch.rb +74 -67
  84. data/spec/shared/delayed_method.rb +143 -102
  85. data/spec/shared/performable_method.rb +39 -38
  86. data/spec/shared/shared_backend.rb +801 -440
  87. data/spec/shared/testing.rb +14 -14
  88. data/spec/shared/worker.rb +157 -149
  89. data/spec/shared_jobs_specs.rb +13 -13
  90. data/spec/spec_helper.rb +57 -56
  91. metadata +183 -103
  92. data/lib/delayed/backend/redis/bulk_update.lua +0 -50
  93. data/lib/delayed/backend/redis/destroy_job.lua +0 -2
  94. data/lib/delayed/backend/redis/enqueue.lua +0 -29
  95. data/lib/delayed/backend/redis/fail_job.lua +0 -5
  96. data/lib/delayed/backend/redis/find_available.lua +0 -3
  97. data/lib/delayed/backend/redis/functions.rb +0 -59
  98. data/lib/delayed/backend/redis/get_and_lock_next_available.lua +0 -17
  99. data/lib/delayed/backend/redis/includes/jobs_common.lua +0 -203
  100. data/lib/delayed/backend/redis/job.rb +0 -535
  101. data/lib/delayed/backend/redis/set_running.lua +0 -5
  102. data/lib/delayed/backend/redis/tickle_strand.lua +0 -2
  103. data/spec/gemfiles/42.gemfile +0 -7
  104. data/spec/gemfiles/50.gemfile +0 -7
  105. data/spec/gemfiles/51.gemfile +0 -7
  106. data/spec/gemfiles/52.gemfile +0 -7
  107. data/spec/gemfiles/60.gemfile +0 -7
  108. data/spec/redis_job_spec.rb +0 -148
@@ -6,92 +6,87 @@ class SpeedUpMaxConcurrentDeleteTrigger < ActiveRecord::Migration[4.2]
6
6
  end
7
7
 
8
8
  def up
9
- if connection.adapter_name == 'PostgreSQL'
10
- # tl;dr sacrifice some responsiveness to max_concurrent changes for faster performance
11
- # don't get the count every single time - it's usually safe to just set the next one in line
12
- # since the max_concurrent doesn't change all that often for a strand
13
- execute(<<-SQL)
14
- CREATE OR REPLACE FUNCTION delayed_jobs_after_delete_row_tr_fn () RETURNS trigger AS $$
15
- DECLARE
16
- running_count integer;
17
- should_lock boolean;
18
- should_be_precise boolean;
19
- BEGIN
20
- IF OLD.strand IS NOT NULL THEN
21
- should_lock := true;
22
- should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
23
-
24
- IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
25
- running_count := (SELECT COUNT(*) FROM (
26
- SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
27
- ) subquery_for_count);
28
- should_lock := running_count < OLD.max_concurrent;
29
- END IF;
30
-
31
- IF should_lock THEN
32
- PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
9
+ # tl;dr sacrifice some responsiveness to max_concurrent changes for faster performance
10
+ # don't get the count every single time - it's usually safe to just set the next one in line
11
+ # since the max_concurrent doesn't change all that often for a strand
12
+ execute(<<-SQL)
13
+ CREATE OR REPLACE FUNCTION delayed_jobs_after_delete_row_tr_fn () RETURNS trigger AS $$
14
+ DECLARE
15
+ running_count integer;
16
+ should_lock boolean;
17
+ should_be_precise boolean;
18
+ BEGIN
19
+ IF OLD.strand IS NOT NULL THEN
20
+ should_lock := true;
21
+ should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
22
+
23
+ IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
24
+ running_count := (SELECT COUNT(*) FROM (
25
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
26
+ ) subquery_for_count);
27
+ should_lock := running_count < OLD.max_concurrent;
28
+ END IF;
29
+
30
+ IF should_lock THEN
31
+ PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
32
+ END IF;
33
+
34
+ IF should_be_precise THEN
35
+ running_count := (SELECT COUNT(*) FROM (
36
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
37
+ ) subquery_for_count);
38
+ IF running_count < OLD.max_concurrent THEN
39
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id IN (
40
+ SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
41
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT (OLD.max_concurrent - running_count) FOR UPDATE
42
+ );
33
43
  END IF;
34
-
35
- IF should_be_precise THEN
36
- running_count := (SELECT COUNT(*) FROM (
37
- SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
38
- ) subquery_for_count);
39
- IF running_count < OLD.max_concurrent THEN
40
- UPDATE delayed_jobs SET next_in_strand = 't' WHERE id IN (
41
- SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
42
- j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT (OLD.max_concurrent - running_count) FOR UPDATE
43
- );
44
- END IF;
44
+ ELSE
45
+ -- n-strands don't require precise ordering; we can make this query more performant
46
+ IF OLD.max_concurrent > 1 THEN
47
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
48
+ (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
49
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT 1 FOR UPDATE SKIP LOCKED);
45
50
  ELSE
46
- -- n-strands don't require precise ordering; we can make this query more performant
47
- IF OLD.max_concurrent > 1 THEN
48
- UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
51
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
49
52
  (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
50
- j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT 1 FOR UPDATE SKIP LOCKED);
51
- ELSE
52
- UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
53
- (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
54
- j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT 1 FOR UPDATE);
55
- END IF;
53
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT 1 FOR UPDATE);
56
54
  END IF;
57
55
  END IF;
58
- RETURN OLD;
59
- END;
60
- $$ LANGUAGE plpgsql;
61
- SQL
62
- end
56
+ END IF;
57
+ RETURN OLD;
58
+ END;
59
+ $$ LANGUAGE plpgsql;
60
+ SQL
63
61
  end
64
62
 
65
63
  def down
66
- if connection.adapter_name == 'PostgreSQL'
67
- execute(<<-SQL)
68
- CREATE OR REPLACE FUNCTION delayed_jobs_after_delete_row_tr_fn () RETURNS trigger AS $$
69
- DECLARE
70
- running_count integer;
71
- BEGIN
72
- IF OLD.strand IS NOT NULL THEN
73
- PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
74
- IF OLD.id % 20 = 0 THEN
75
- running_count := (SELECT COUNT(*) FROM (
76
- SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
77
- ) subquery_for_count);
78
- IF running_count < OLD.max_concurrent THEN
79
- UPDATE delayed_jobs SET next_in_strand = 't' WHERE id IN (
80
- SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
81
- j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT (OLD.max_concurrent - running_count) FOR UPDATE
82
- );
83
- END IF;
84
- ELSE
85
- UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
86
- (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
87
- j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT 1 FOR UPDATE);
64
+ execute(<<-SQL)
65
+ CREATE OR REPLACE FUNCTION delayed_jobs_after_delete_row_tr_fn () RETURNS trigger AS $$
66
+ DECLARE
67
+ running_count integer;
68
+ BEGIN
69
+ IF OLD.strand IS NOT NULL THEN
70
+ PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
71
+ IF OLD.id % 20 = 0 THEN
72
+ running_count := (SELECT COUNT(*) FROM (
73
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
74
+ ) subquery_for_count);
75
+ IF running_count < OLD.max_concurrent THEN
76
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id IN (
77
+ SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
78
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT (OLD.max_concurrent - running_count) FOR UPDATE
79
+ );
88
80
  END IF;
81
+ ELSE
82
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
83
+ (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
84
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT 1 FOR UPDATE);
89
85
  END IF;
90
- RETURN OLD;
91
- END;
92
- $$ LANGUAGE plpgsql;
93
- SQL
94
- end
86
+ END IF;
87
+ RETURN OLD;
88
+ END;
89
+ $$ LANGUAGE plpgsql;
90
+ SQL
95
91
  end
96
92
  end
97
-
@@ -10,119 +10,115 @@ class AddStrandOrderOverride < ActiveRecord::Migration[4.2]
10
10
  def up
11
11
  add_column :delayed_jobs, :strand_order_override, :integer, default: 0, null: false
12
12
  add_column :failed_jobs, :strand_order_override, :integer, default: 0, null: false
13
- add_index :delayed_jobs, [:strand, :strand_order_override, :id],
14
- algorithm: :concurrently,
15
- where: "strand IS NOT NULL",
16
- name: "next_in_strand_index"
13
+ add_index :delayed_jobs, %i[strand strand_order_override id],
14
+ algorithm: :concurrently,
15
+ where: "strand IS NOT NULL",
16
+ name: "next_in_strand_index"
17
17
 
18
- if connection.adapter_name == 'PostgreSQL'
19
- # Use the strand_order_override as the primary sorting mechanism (useful when moving between jobs queues without preserving ID ordering)
20
- execute(<<-SQL)
21
- CREATE OR REPLACE FUNCTION delayed_jobs_after_delete_row_tr_fn () RETURNS trigger AS $$
22
- DECLARE
23
- running_count integer;
24
- should_lock boolean;
25
- should_be_precise boolean;
26
- BEGIN
27
- IF OLD.strand IS NOT NULL THEN
28
- should_lock := true;
29
- should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
30
-
31
- IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
32
- running_count := (SELECT COUNT(*) FROM (
33
- SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
34
- ) subquery_for_count);
35
- should_lock := running_count < OLD.max_concurrent;
36
- END IF;
37
-
38
- IF should_lock THEN
39
- PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
18
+ # Use the strand_order_override as the primary sorting mechanism (useful when moving between jobs queues without preserving ID ordering)
19
+ execute(<<-SQL)
20
+ CREATE OR REPLACE FUNCTION delayed_jobs_after_delete_row_tr_fn () RETURNS trigger AS $$
21
+ DECLARE
22
+ running_count integer;
23
+ should_lock boolean;
24
+ should_be_precise boolean;
25
+ BEGIN
26
+ IF OLD.strand IS NOT NULL THEN
27
+ should_lock := true;
28
+ should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
29
+
30
+ IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
31
+ running_count := (SELECT COUNT(*) FROM (
32
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
33
+ ) subquery_for_count);
34
+ should_lock := running_count < OLD.max_concurrent;
35
+ END IF;
36
+
37
+ IF should_lock THEN
38
+ PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
39
+ END IF;
40
+
41
+ IF should_be_precise THEN
42
+ running_count := (SELECT COUNT(*) FROM (
43
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
44
+ ) subquery_for_count);
45
+ IF running_count < OLD.max_concurrent THEN
46
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id IN (
47
+ SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
48
+ j2.strand = OLD.strand ORDER BY j2.strand_order_override ASC, j2.id ASC LIMIT (OLD.max_concurrent - running_count) FOR UPDATE
49
+ );
40
50
  END IF;
41
-
42
- IF should_be_precise THEN
43
- running_count := (SELECT COUNT(*) FROM (
44
- SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
45
- ) subquery_for_count);
46
- IF running_count < OLD.max_concurrent THEN
47
- UPDATE delayed_jobs SET next_in_strand = 't' WHERE id IN (
48
- SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
49
- j2.strand = OLD.strand ORDER BY j2.strand_order_override ASC, j2.id ASC LIMIT (OLD.max_concurrent - running_count) FOR UPDATE
50
- );
51
- END IF;
51
+ ELSE
52
+ -- n-strands don't require precise ordering; we can make this query more performant
53
+ IF OLD.max_concurrent > 1 THEN
54
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
55
+ (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
56
+ j2.strand = OLD.strand ORDER BY j2.strand_order_override ASC, j2.id ASC LIMIT 1 FOR UPDATE SKIP LOCKED);
52
57
  ELSE
53
- -- n-strands don't require precise ordering; we can make this query more performant
54
- IF OLD.max_concurrent > 1 THEN
55
- UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
58
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
56
59
  (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
57
- j2.strand = OLD.strand ORDER BY j2.strand_order_override ASC, j2.id ASC LIMIT 1 FOR UPDATE SKIP LOCKED);
58
- ELSE
59
- UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
60
- (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
61
- j2.strand = OLD.strand ORDER BY j2.strand_order_override ASC, j2.id ASC LIMIT 1 FOR UPDATE);
62
- END IF;
60
+ j2.strand = OLD.strand ORDER BY j2.strand_order_override ASC, j2.id ASC LIMIT 1 FOR UPDATE);
63
61
  END IF;
64
62
  END IF;
65
- RETURN OLD;
66
- END;
67
- $$ LANGUAGE plpgsql;
68
- SQL
69
- end
63
+ END IF;
64
+ RETURN OLD;
65
+ END;
66
+ $$ LANGUAGE plpgsql;
67
+ SQL
70
68
  end
71
69
 
72
70
  def down
73
71
  remove_column :delayed_jobs, :strand_order_override, :integer
74
72
  remove_column :failed_jobs, :strand_order_override, :integer
75
73
 
76
- if connection.adapter_name == 'PostgreSQL'
77
- execute(<<-SQL)
78
- CREATE OR REPLACE FUNCTION delayed_jobs_after_delete_row_tr_fn () RETURNS trigger AS $$
79
- DECLARE
80
- running_count integer;
81
- should_lock boolean;
82
- should_be_precise boolean;
83
- BEGIN
84
- IF OLD.strand IS NOT NULL THEN
85
- should_lock := true;
86
- should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
87
-
88
- IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
89
- running_count := (SELECT COUNT(*) FROM (
90
- SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
91
- ) subquery_for_count);
92
- should_lock := running_count < OLD.max_concurrent;
93
- END IF;
94
-
95
- IF should_lock THEN
96
- PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
74
+ execute(<<-SQL)
75
+ CREATE OR REPLACE FUNCTION delayed_jobs_after_delete_row_tr_fn () RETURNS trigger AS $$
76
+ DECLARE
77
+ running_count integer;
78
+ should_lock boolean;
79
+ should_be_precise boolean;
80
+ BEGIN
81
+ IF OLD.strand IS NOT NULL THEN
82
+ should_lock := true;
83
+ should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
84
+
85
+ IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
86
+ running_count := (SELECT COUNT(*) FROM (
87
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
88
+ ) subquery_for_count);
89
+ should_lock := running_count < OLD.max_concurrent;
90
+ END IF;
91
+
92
+ IF should_lock THEN
93
+ PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
94
+ END IF;
95
+
96
+ IF should_be_precise THEN
97
+ running_count := (SELECT COUNT(*) FROM (
98
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
99
+ ) subquery_for_count);
100
+ IF running_count < OLD.max_concurrent THEN
101
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id IN (
102
+ SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
103
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT (OLD.max_concurrent - running_count) FOR UPDATE
104
+ );
97
105
  END IF;
98
-
99
- IF should_be_precise THEN
100
- running_count := (SELECT COUNT(*) FROM (
101
- SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
102
- ) subquery_for_count);
103
- IF running_count < OLD.max_concurrent THEN
104
- UPDATE delayed_jobs SET next_in_strand = 't' WHERE id IN (
105
- SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
106
- j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT (OLD.max_concurrent - running_count) FOR UPDATE
107
- );
108
- END IF;
106
+ ELSE
107
+ -- n-strands don't require precise ordering; we can make this query more performant
108
+ IF OLD.max_concurrent > 1 THEN
109
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
110
+ (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
111
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT 1 FOR UPDATE SKIP LOCKED);
109
112
  ELSE
110
- -- n-strands don't require precise ordering; we can make this query more performant
111
- IF OLD.max_concurrent > 1 THEN
112
- UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
113
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
113
114
  (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
114
- j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT 1 FOR UPDATE SKIP LOCKED);
115
- ELSE
116
- UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
117
- (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
118
- j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT 1 FOR UPDATE);
119
- END IF;
115
+ j2.strand = OLD.strand ORDER BY j2.id ASC LIMIT 1 FOR UPDATE);
120
116
  END IF;
121
117
  END IF;
122
- RETURN OLD;
123
- END;
124
- $$ LANGUAGE plpgsql;
125
- SQL
126
- end
118
+ END IF;
119
+ RETURN OLD;
120
+ END;
121
+ $$ LANGUAGE plpgsql;
122
+ SQL
127
123
  end
128
124
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddNStrandIndex < ActiveRecord::Migration[5.2]
4
+ disable_ddl_transaction!
5
+
6
+ def change
7
+ add_index :delayed_jobs, %i[strand next_in_strand id],
8
+ name: "n_strand_index",
9
+ where: "strand IS NOT NULL",
10
+ algorithm: :concurrently
11
+ end
12
+ end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddSingletonColumn < ActiveRecord::Migration[5.2]
4
+ disable_ddl_transaction!
5
+
6
+ def change
7
+ add_column :delayed_jobs, :singleton, :string, if_not_exists: true
8
+ add_column :failed_jobs, :singleton, :string, if_not_exists: true
9
+ # only one job can be queued in a singleton
10
+ add_index :delayed_jobs,
11
+ :singleton,
12
+ where: "singleton IS NOT NULL AND locked_by IS NULL",
13
+ unique: true,
14
+ name: "index_delayed_jobs_on_singleton_not_running",
15
+ algorithm: :concurrently
16
+ # only one job can be running for a singleton
17
+ add_index :delayed_jobs,
18
+ :singleton,
19
+ where: "singleton IS NOT NULL AND locked_by IS NOT NULL",
20
+ unique: true,
21
+ name: "index_delayed_jobs_on_singleton_running",
22
+ algorithm: :concurrently
23
+
24
+ reversible do |direction|
25
+ direction.up do
26
+ execute(<<~SQL)
27
+ CREATE OR REPLACE FUNCTION delayed_jobs_after_delete_row_tr_fn () RETURNS trigger AS $$
28
+ DECLARE
29
+ running_count integer;
30
+ should_lock boolean;
31
+ should_be_precise boolean;
32
+ update_query varchar;
33
+ skip_locked varchar;
34
+ BEGIN
35
+ IF OLD.strand IS NOT NULL THEN
36
+ should_lock := true;
37
+ should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
38
+
39
+ IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
40
+ running_count := (SELECT COUNT(*) FROM (
41
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
42
+ ) subquery_for_count);
43
+ should_lock := running_count < OLD.max_concurrent;
44
+ END IF;
45
+
46
+ IF should_lock THEN
47
+ PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
48
+ END IF;
49
+
50
+ -- note that we don't really care if the row we're deleting has a singleton, or if it even
51
+ -- matches the row(s) we're going to update. we just need to make sure that whatever
52
+ -- singleton we grab isn't already running (which is a simple existence check, since
53
+ -- the unique indexes ensure there is at most one singleton running, and one queued)
54
+ update_query := 'UPDATE delayed_jobs SET next_in_strand=true WHERE id IN (
55
+ SELECT id FROM delayed_jobs j2
56
+ WHERE next_in_strand=false AND
57
+ j2.strand=$1.strand AND
58
+ (j2.singleton IS NULL OR NOT EXISTS (SELECT 1 FROM delayed_jobs j3 WHERE j3.singleton=j2.singleton AND j3.id<>j2.id))
59
+ ORDER BY j2.strand_order_override ASC, j2.id ASC
60
+ LIMIT ';
61
+
62
+ IF should_be_precise THEN
63
+ running_count := (SELECT COUNT(*) FROM (
64
+ SELECT 1 FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
65
+ ) s);
66
+ IF running_count < OLD.max_concurrent THEN
67
+ update_query := update_query || '($1.max_concurrent - $2)';
68
+ ELSE
69
+ -- we have too many running already; just bail
70
+ RETURN OLD;
71
+ END IF;
72
+ ELSE
73
+ update_query := update_query || '1';
74
+
75
+ -- n-strands don't require precise ordering; we can make this query more performant
76
+ IF OLD.max_concurrent > 1 THEN
77
+ skip_locked := ' SKIP LOCKED';
78
+ END IF;
79
+ END IF;
80
+
81
+ update_query := update_query || ' FOR UPDATE' || COALESCE(skip_locked, '') || ')';
82
+ EXECUTE update_query USING OLD, running_count;
83
+ ELSIF OLD.singleton IS NOT NULL THEN
84
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE singleton=OLD.singleton AND next_in_strand=false;
85
+ END IF;
86
+ RETURN OLD;
87
+ END;
88
+ $$ LANGUAGE plpgsql;
89
+ SQL
90
+ execute(<<~SQL)
91
+ CREATE OR REPLACE FUNCTION delayed_jobs_before_insert_row_tr_fn () RETURNS trigger AS $$
92
+ BEGIN
93
+ IF NEW.strand IS NOT NULL THEN
94
+ PERFORM pg_advisory_xact_lock(half_md5_as_bigint(NEW.strand));
95
+ IF (SELECT COUNT(*) FROM (
96
+ SELECT 1 FROM delayed_jobs WHERE strand = NEW.strand AND next_in_strand=true LIMIT NEW.max_concurrent
97
+ ) s) = NEW.max_concurrent THEN
98
+ NEW.next_in_strand := false;
99
+ END IF;
100
+ END IF;
101
+ IF NEW.singleton IS NOT NULL THEN
102
+ PERFORM 1 FROM delayed_jobs WHERE singleton = NEW.singleton;
103
+ IF FOUND THEN
104
+ NEW.next_in_strand := false;
105
+ END IF;
106
+ END IF;
107
+ RETURN NEW;
108
+ END;
109
+ $$ LANGUAGE plpgsql;
110
+ SQL
111
+ end
112
+ direction.down do
113
+ execute(<<~SQL)
114
+ CREATE OR REPLACE FUNCTION delayed_jobs_after_delete_row_tr_fn () RETURNS trigger AS $$
115
+ DECLARE
116
+ running_count integer;
117
+ should_lock boolean;
118
+ should_be_precise boolean;
119
+ BEGIN
120
+ IF OLD.strand IS NOT NULL THEN
121
+ should_lock := true;
122
+ should_be_precise := OLD.id % (OLD.max_concurrent * 4) = 0;
123
+
124
+ IF NOT should_be_precise AND OLD.max_concurrent > 16 THEN
125
+ running_count := (SELECT COUNT(*) FROM (
126
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
127
+ ) subquery_for_count);
128
+ should_lock := running_count < OLD.max_concurrent;
129
+ END IF;
130
+
131
+ IF should_lock THEN
132
+ PERFORM pg_advisory_xact_lock(half_md5_as_bigint(OLD.strand));
133
+ END IF;
134
+
135
+ IF should_be_precise THEN
136
+ running_count := (SELECT COUNT(*) FROM (
137
+ SELECT 1 as one FROM delayed_jobs WHERE strand = OLD.strand AND next_in_strand = 't' LIMIT OLD.max_concurrent
138
+ ) subquery_for_count);
139
+ IF running_count < OLD.max_concurrent THEN
140
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id IN (
141
+ SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
142
+ j2.strand = OLD.strand ORDER BY j2.strand_order_override ASC, j2.id ASC LIMIT (OLD.max_concurrent - running_count) FOR UPDATE
143
+ );
144
+ END IF;
145
+ ELSE
146
+ -- n-strands don't require precise ordering; we can make this query more performant
147
+ IF OLD.max_concurrent > 1 THEN
148
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
149
+ (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
150
+ j2.strand = OLD.strand ORDER BY j2.strand_order_override ASC, j2.id ASC LIMIT 1 FOR UPDATE SKIP LOCKED);
151
+ ELSE
152
+ UPDATE delayed_jobs SET next_in_strand = 't' WHERE id =
153
+ (SELECT id FROM delayed_jobs j2 WHERE next_in_strand = 'f' AND
154
+ j2.strand = OLD.strand ORDER BY j2.strand_order_override ASC, j2.id ASC LIMIT 1 FOR UPDATE);
155
+ END IF;
156
+ END IF;
157
+ END IF;
158
+ RETURN OLD;
159
+ END;
160
+ $$ LANGUAGE plpgsql;
161
+ SQL
162
+ execute(<<~SQL)
163
+ CREATE OR REPLACE FUNCTION delayed_jobs_before_insert_row_tr_fn () RETURNS trigger AS $$
164
+ BEGIN
165
+ IF NEW.strand IS NOT NULL THEN
166
+ PERFORM pg_advisory_xact_lock(half_md5_as_bigint(NEW.strand));
167
+ IF (SELECT COUNT(*) FROM (
168
+ SELECT 1 AS one FROM delayed_jobs WHERE strand = NEW.strand LIMIT NEW.max_concurrent
169
+ ) subquery_for_count) = NEW.max_concurrent THEN
170
+ NEW.next_in_strand := 'f';
171
+ END IF;
172
+ END IF;
173
+ RETURN NEW;
174
+ END;
175
+ $$ LANGUAGE plpgsql;
176
+ SQL
177
+ end
178
+ end
179
+
180
+ connection.transaction do
181
+ reversible do |direction|
182
+ direction.up do
183
+ drop_triggers
184
+ execute("CREATE TRIGGER delayed_jobs_before_insert_row_tr BEFORE INSERT ON delayed_jobs FOR EACH ROW WHEN (NEW.strand IS NOT NULL OR NEW.singleton IS NOT NULL) EXECUTE PROCEDURE delayed_jobs_before_insert_row_tr_fn()")
185
+ execute("CREATE TRIGGER delayed_jobs_after_delete_row_tr AFTER DELETE ON delayed_jobs FOR EACH ROW WHEN ((OLD.strand IS NOT NULL OR OLD.singleton IS NOT NULL) AND OLD.next_in_strand=true) EXECUTE PROCEDURE delayed_jobs_after_delete_row_tr_fn()")
186
+ end
187
+ direction.down do
188
+ drop_triggers
189
+ execute("CREATE TRIGGER delayed_jobs_before_insert_row_tr BEFORE INSERT ON delayed_jobs FOR EACH ROW WHEN (NEW.strand IS NOT NULL) EXECUTE PROCEDURE delayed_jobs_before_insert_row_tr_fn()")
190
+ execute("CREATE TRIGGER delayed_jobs_after_delete_row_tr AFTER DELETE ON delayed_jobs FOR EACH ROW WHEN (OLD.strand IS NOT NULL AND OLD.next_in_strand = 't') EXECUTE PROCEDURE delayed_jobs_after_delete_row_tr_fn()")
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ def drop_triggers
197
+ execute("DROP TRIGGER delayed_jobs_before_insert_row_tr ON delayed_jobs")
198
+ execute("DROP TRIGGER delayed_jobs_after_delete_row_tr ON delayed_jobs")
199
+ end
200
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddDeleteConflictingSingletonsBeforeUnlockTrigger < ActiveRecord::Migration[5.2]
4
+ def up
5
+ execute(<<~SQL)
6
+ CREATE FUNCTION delayed_jobs_before_unlock_delete_conflicting_singletons_row_fn () RETURNS trigger AS $$
7
+ BEGIN
8
+ IF EXISTS (SELECT 1 FROM delayed_jobs j2 WHERE j2.singleton=OLD.singleton) THEN
9
+ DELETE FROM delayed_jobs WHERE id<>OLD.id AND singleton=OLD.singleton;
10
+ END IF;
11
+ RETURN NEW;
12
+ END;
13
+ $$ LANGUAGE plpgsql;
14
+ SQL
15
+ execute(<<~SQL)
16
+ CREATE TRIGGER delayed_jobs_before_unlock_delete_conflicting_singletons_row_tr BEFORE UPDATE ON delayed_jobs FOR EACH ROW WHEN (
17
+ OLD.singleton IS NOT NULL AND
18
+ OLD.singleton=NEW.singleton AND
19
+ OLD.locked_by IS NOT NULL AND
20
+ NEW.locked_by IS NULL) EXECUTE PROCEDURE delayed_jobs_before_unlock_delete_conflicting_singletons_row_fn();
21
+ SQL
22
+ end
23
+
24
+ def down
25
+ execute("DROP FUNCTION delayed_jobs_before_unlock_delete_conflicting_singletons_row_tr_fn()")
26
+ end
27
+ end