ductwork 0.25.0 → 1.0.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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/audit-clock-drift/SKILL.md +11 -0
  3. data/.claude/skills/audit-database-indexes/SKILL.md +11 -0
  4. data/.claude/skills/audit-database-support/SKILL.md +18 -0
  5. data/.claude/skills/audit-durability/SKILL.md +17 -0
  6. data/.saturnci/database.yml +1 -0
  7. data/CHANGELOG-PRO.md +72 -0
  8. data/CHANGELOG.md +148 -1
  9. data/CLAUDE.md +34 -0
  10. data/README.md +24 -14
  11. data/app/controllers/ductwork/application_controller.rb +3 -3
  12. data/app/controllers/ductwork/dashboards_controller.rb +1 -1
  13. data/app/controllers/ductwork/pipelines_controller.rb +4 -3
  14. data/app/helpers/ductwork/application_helper.rb +2 -2
  15. data/app/views/ductwork/dashboards/show.html.erb +12 -12
  16. data/app/views/ductwork/pipelines/index.html.erb +12 -12
  17. data/app/views/ductwork/pipelines/show.html.erb +13 -13
  18. data/app/views/ductwork/step_errors/index.html.erb +2 -2
  19. data/lib/ductwork/branch_claim.rb +185 -0
  20. data/lib/ductwork/cli.rb +73 -24
  21. data/lib/ductwork/configuration.rb +73 -3
  22. data/lib/ductwork/context.rb +17 -19
  23. data/lib/ductwork/database_clock.rb +84 -0
  24. data/lib/ductwork/dsl/branch_builder.rb +46 -1
  25. data/lib/ductwork/dsl/definition_builder.rb +101 -2
  26. data/lib/ductwork/{job_claim.rb → execution_claim.rb} +7 -6
  27. data/lib/ductwork/fault_injection.rb +35 -0
  28. data/lib/ductwork/migration_helper.rb +1 -1
  29. data/lib/ductwork/models/advancement.rb +48 -0
  30. data/lib/ductwork/models/attempt.rb +9 -0
  31. data/lib/ductwork/models/availability.rb +2 -0
  32. data/lib/ductwork/models/branch.rb +800 -0
  33. data/lib/ductwork/models/branch_link.rb +10 -0
  34. data/lib/ductwork/models/execution.rb +199 -2
  35. data/lib/ductwork/models/job.rb +8 -121
  36. data/lib/ductwork/models/pipeline.rb +152 -255
  37. data/lib/ductwork/models/process.rb +177 -5
  38. data/lib/ductwork/models/result.rb +2 -1
  39. data/lib/ductwork/models/run.rb +119 -1
  40. data/lib/ductwork/models/step.rb +26 -6
  41. data/lib/ductwork/models/transition.rb +15 -0
  42. data/lib/ductwork/models/tuple.rb +1 -4
  43. data/lib/ductwork/models/workflow.rb +3 -0
  44. data/lib/ductwork/optimistic_locking_execution_claim.rb +97 -0
  45. data/lib/ductwork/processes/health_check.rb +87 -0
  46. data/lib/ductwork/processes/job_worker.rb +86 -20
  47. data/lib/ductwork/processes/job_worker_runner.rb +25 -37
  48. data/lib/ductwork/processes/pipeline_advancer.rb +48 -76
  49. data/lib/ductwork/processes/pipeline_advancer_runner.rb +34 -44
  50. data/lib/ductwork/processes/process_supervisor.rb +87 -10
  51. data/lib/ductwork/processes/thread_supervisor.rb +55 -21
  52. data/lib/ductwork/processes/thread_supervisor_runner.rb +8 -5
  53. data/lib/ductwork/processes/worker_health_check.rb +55 -0
  54. data/lib/ductwork/row_locking_execution_claim.rb +77 -0
  55. data/lib/ductwork/testing/rspec.rb +28 -11
  56. data/lib/ductwork/version.rb +1 -1
  57. data/lib/ductwork.rb +5 -0
  58. data/lib/generators/ductwork/install/install_generator.rb +14 -4
  59. data/lib/generators/ductwork/install/templates/config/ductwork.yml +4 -0
  60. data/lib/generators/ductwork/install/templates/db/create_ductwork_advancements.rb +29 -0
  61. data/lib/generators/ductwork/install/templates/db/create_ductwork_attempts.rb +20 -0
  62. data/lib/generators/ductwork/install/templates/db/create_ductwork_availabilities.rb +18 -5
  63. data/lib/generators/ductwork/install/templates/db/create_ductwork_branch_links.rb +27 -0
  64. data/lib/generators/ductwork/install/templates/db/create_ductwork_branches.rb +37 -0
  65. data/lib/generators/ductwork/install/templates/db/create_ductwork_executions.rb +8 -1
  66. data/lib/generators/ductwork/install/templates/db/create_ductwork_pipelines.rb +0 -8
  67. data/lib/generators/ductwork/install/templates/db/create_ductwork_processes.rb +2 -0
  68. data/lib/generators/ductwork/install/templates/db/create_ductwork_results.rb +1 -0
  69. data/lib/generators/ductwork/install/templates/db/create_ductwork_runs.rb +45 -6
  70. data/lib/generators/ductwork/install/templates/db/create_ductwork_steps.rb +20 -5
  71. data/lib/generators/ductwork/install/templates/db/create_ductwork_transitions.rb +43 -0
  72. data/lib/generators/ductwork/install/templates/db/create_ductwork_tuples.rb +3 -3
  73. data/lib/generators/ductwork/update/templates/db/add_crash_count_to_ductwork_advancements.rb +7 -0
  74. data/lib/generators/ductwork/update/templates/db/add_crash_count_to_ductwork_executions.rb +14 -0
  75. data/lib/generators/ductwork/update/templates/db/add_indexes_to_ductwork_results.rb +7 -0
  76. data/lib/generators/ductwork/update/templates/db/add_indexes_to_ductwork_runs.rb +9 -0
  77. data/lib/generators/ductwork/update/templates/db/add_indexes_to_ductwork_transitions.rb +16 -0
  78. data/lib/generators/ductwork/update/templates/db/add_pipeline_started_index_to_ductwork_runs.rb +7 -0
  79. data/lib/generators/ductwork/update/templates/db/add_process_id_to_ductwork_executions.rb +35 -0
  80. data/lib/generators/ductwork/update/templates/db/add_role_to_ductwork_processes.rb +20 -0
  81. data/lib/generators/ductwork/update/templates/db/associate_branches_to_runs.rb +74 -0
  82. data/lib/generators/ductwork/update/templates/db/associate_steps_to_branches.rb +24 -0
  83. data/lib/generators/ductwork/update/templates/db/associate_steps_to_runs.rb +72 -0
  84. data/lib/generators/ductwork/update/templates/db/associate_tuples_to_runs.rb +73 -0
  85. data/lib/generators/ductwork/update/templates/db/backfill_branch_ids_on_steps.rb +21 -0
  86. data/lib/generators/ductwork/update/templates/db/create_ductwork_advancements.rb +28 -0
  87. data/lib/generators/ductwork/update/templates/db/create_ductwork_branch_links.rb +27 -0
  88. data/lib/generators/ductwork/update/templates/db/create_ductwork_branches.rb +37 -0
  89. data/lib/generators/ductwork/update/templates/db/create_ductwork_runs.rb +55 -0
  90. data/lib/generators/ductwork/update/templates/db/create_ductwork_transitions.rb +32 -0
  91. data/lib/generators/ductwork/update/templates/db/denormalize_pipeline_klass_on_availabilities.rb +11 -4
  92. data/lib/generators/ductwork/update/templates/db/migrate_tables_to_uuid_primary_key.rb +5 -0
  93. data/lib/generators/ductwork/update/templates/db/rename_runs_to_attempts.rb +7 -0
  94. data/lib/generators/ductwork/update/templates/db/update_process_associations.rb +29 -0
  95. data/lib/generators/ductwork/update/update_generator.rb +99 -0
  96. metadata +47 -6
  97. data/lib/ductwork/optimistic_locking_job_claim.rb +0 -88
  98. data/lib/ductwork/row_locking_job_claim.rb +0 -75
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ductwork
4
+ class RowLockingExecutionClaim
5
+ def initialize(klass, owner_process_id)
6
+ @availability = nil
7
+ @execution = nil
8
+ @klass = klass
9
+ @process_id = owner_process_id
10
+ end
11
+
12
+ def latest
13
+ Ductwork::Record.transaction do
14
+ claim_availability
15
+
16
+ if availability.present?
17
+ Ductwork.logger.debug(
18
+ msg: "Job claimed",
19
+ role: :job_worker,
20
+ process_id: process_id,
21
+ availability_id: availability.id
22
+ )
23
+ update_state
24
+ else
25
+ Ductwork.logger.debug(
26
+ msg: "No available job to claim",
27
+ role: :job_worker,
28
+ process_id: process_id,
29
+ pipeline: klass
30
+ )
31
+ end
32
+ end
33
+
34
+ execution
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :availability, :execution, :klass, :process_id
40
+
41
+ def claim_availability
42
+ sql = Ductwork::DatabaseClock.now_sql("ductwork_availabilities.started_at")
43
+ @availability = Ductwork::Availability
44
+ .where(sql)
45
+ .where(completed_at: nil, pipeline_klass: klass)
46
+ .order(:started_at)
47
+ .lock("FOR UPDATE SKIP LOCKED")
48
+ .limit(1)
49
+ .first
50
+
51
+ return unless availability
52
+
53
+ completed_at = Time.current
54
+ @execution = availability.execution
55
+
56
+ availability.update_columns(completed_at:, process_id:)
57
+ execution.update_columns(process_id:)
58
+ end
59
+
60
+ def update_state
61
+ step = execution.job.step
62
+
63
+ Ductwork::Step
64
+ .where(id: step.id)
65
+ .where.not(status: "in_progress")
66
+ .update_all(status: "in_progress", updated_at: Time.current)
67
+ Ductwork::Run
68
+ .where(id: step.run_id)
69
+ .where.not(status: "in_progress")
70
+ .update_all(status: "in_progress", updated_at: Time.current)
71
+ Ductwork::Pipeline
72
+ .where(id: Ductwork::Run.where(id: step.run_id).select(:pipeline_id))
73
+ .where.not(status: "in_progress")
74
+ .update_all(status: "in_progress", updated_at: Time.current)
75
+ end
76
+ end
77
+ end
@@ -66,7 +66,7 @@ RSpec::Matchers.define(:have_set_context) do |expected|
66
66
  supports_block_expectations
67
67
 
68
68
  match do |block|
69
- ctx = Ductwork::Context.new(pipeline_id)
69
+ ctx = Ductwork::Context.new(run_id)
70
70
  before_values = expected.map { |k, _| ctx.get(k.to_s) }
71
71
 
72
72
  block.call
@@ -76,7 +76,21 @@ RSpec::Matchers.define(:have_set_context) do |expected|
76
76
  before_values.all?(&:nil?) && after_context == expected
77
77
  end
78
78
 
79
- chain :for_pipeline, :pipeline_id
79
+ chain :for_pipeline do |pipeline|
80
+ @run_id = pipeline.current_run.id
81
+ end
82
+
83
+ chain :for_run do |given_run_id|
84
+ @run_id = given_run_id
85
+ end
86
+
87
+ def run_id
88
+ if @run_id.blank?
89
+ raise ArgumentError, "Must chain with .for_pipeline or .for_run"
90
+ end
91
+
92
+ @run_id
93
+ end
80
94
 
81
95
  failure_message do
82
96
  "Context does not match expected result"
@@ -88,25 +102,28 @@ module Ductwork
88
102
  module RSpec
89
103
  def pipeline_for(klass, **attrs)
90
104
  definition = klass.pipeline_definition.to_json
91
- definition_sha1 = OpenSSL::Digest::SHA256.hexdigest(definition)
105
+ definition_sha1 = Digest::SHA1.hexdigest(definition)
106
+ pipeline_klass = klass.name.to_s
107
+ now = Time.current
92
108
  status = attrs[:status] || "in_progress"
93
- triggered_at = attrs[:triggered_at] || Time.current
94
- started_at = attrs[:started_at] || Time.current
95
- last_advanced_at = attrs[:last_advanced_at] || Time.current
109
+ triggered_at = attrs[:triggered_at] || now
110
+ started_at = attrs[:started_at] || now
96
111
 
97
- Ductwork::Pipeline.create!(
98
- klass:,
112
+ pipeline = Ductwork::Pipeline.create!(klass:, status:)
113
+ pipeline.runs.create!(
114
+ pipeline_klass:,
99
115
  definition:,
100
116
  definition_sha1:,
101
117
  status:,
102
118
  triggered_at:,
103
- started_at:,
104
- last_advanced_at:
119
+ started_at:
105
120
  )
121
+
122
+ pipeline
106
123
  end
107
124
 
108
125
  def set_pipeline_context(pipeline, **key_values)
109
- ctx = Ductwork::Context.new(pipeline.id)
126
+ ctx = Ductwork::Context.new(pipeline.current_run.id)
110
127
  key_values.each do |key, value|
111
128
  ctx.set(key.to_s, value)
112
129
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ductwork
4
- VERSION = "0.25.0"
4
+ VERSION = "1.0.0"
5
5
  end
data/lib/ductwork.rb CHANGED
@@ -65,6 +65,7 @@ module Ductwork
65
65
  def validate!
66
66
  step_directory = Rails.root.join("app/steps")
67
67
  pipeline_directory = Rails.root.join("app/pipelines")
68
+ workflow_directory = Rails.root.join("app/workflows")
68
69
  loader = if defined?(Rails.autoloaders)
69
70
  Rails.autoloaders.main
70
71
  else
@@ -79,6 +80,10 @@ module Ductwork
79
80
  loader.eager_load_dir(pipeline_directory)
80
81
  end
81
82
 
83
+ if workflow_directory.exist?
84
+ loader.eager_load_dir(workflow_directory)
85
+ end
86
+
82
87
  true
83
88
  end
84
89
 
@@ -17,20 +17,30 @@ module Ductwork
17
17
 
18
18
  migration_template "db/create_ductwork_pipelines.rb",
19
19
  "db/migrate/create_ductwork_pipelines.rb"
20
+ migration_template "db/create_ductwork_runs.rb",
21
+ "db/migrate/create_ductwork_runs.rb"
22
+ migration_template "db/create_ductwork_branches.rb",
23
+ "db/migrate/create_ductwork_branches.rb"
24
+ migration_template "db/create_ductwork_branch_links.rb",
25
+ "db/migrate/create_ductwork_branch_links.rb"
20
26
  migration_template "db/create_ductwork_steps.rb",
21
27
  "db/migrate/create_ductwork_steps.rb"
22
28
  migration_template "db/create_ductwork_jobs.rb",
23
29
  "db/migrate/create_ductwork_jobs.rb"
30
+ migration_template "db/create_ductwork_processes.rb",
31
+ "db/migrate/create_ductwork_processes.rb"
24
32
  migration_template "db/create_ductwork_executions.rb",
25
33
  "db/migrate/create_ductwork_executions.rb"
26
34
  migration_template "db/create_ductwork_availabilities.rb",
27
35
  "db/migrate/create_ductwork_availabilities.rb"
28
- migration_template "db/create_ductwork_runs.rb",
29
- "db/migrate/create_ductwork_runs.rb"
36
+ migration_template "db/create_ductwork_attempts.rb",
37
+ "db/migrate/create_ductwork_attempts.rb"
30
38
  migration_template "db/create_ductwork_results.rb",
31
39
  "db/migrate/create_ductwork_results.rb"
32
- migration_template "db/create_ductwork_processes.rb",
33
- "db/migrate/create_ductwork_processes.rb"
40
+ migration_template "db/create_ductwork_transitions.rb",
41
+ "db/migrate/create_ductwork_transitions.rb"
42
+ migration_template "db/create_ductwork_advancements.rb",
43
+ "db/migrate/create_ductwork_advancements.rb"
34
44
  migration_template "db/create_ductwork_tuples.rb",
35
45
  "db/migrate/create_ductwork_tuples.rb"
36
46
 
@@ -9,12 +9,16 @@ default: &default
9
9
  polling_timeout: 1
10
10
  shutdown_timeout: 20
11
11
  pipeline_advancer:
12
+ count: 3
13
+ max_crash: 10
14
+ max_retry: 3
12
15
  polling_timeout: 1
13
16
  shutdown_timeout: 20
14
17
  steps:
15
18
  max_depth: -1
16
19
  supervisor:
17
20
  polling_timeout: 1
21
+ reaper_timeout: 300
18
22
  shutdown_timeout: 30
19
23
 
20
24
  development:
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateDuctworkAdvancements < Ductwork::Migration
4
+ def change
5
+ create_ductwork_table :ductwork_advancements do |table|
6
+ belongs_to(
7
+ table,
8
+ :process,
9
+ index: true,
10
+ null: true,
11
+ foreign_key: { to_table: :ductwork_processes }
12
+ )
13
+ belongs_to(
14
+ table,
15
+ :transition,
16
+ index: true,
17
+ null: false,
18
+ foreign_key: { to_table: :ductwork_transitions }
19
+ )
20
+ table.datetime :started_at, null: false
21
+ table.datetime :completed_at
22
+ table.integer :crash_count
23
+ table.string :error_klass
24
+ table.string :error_message
25
+ table.text :error_backtrace
26
+ table.timestamps null: false
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateDuctworkAttempts < Ductwork::Migration
4
+ def change
5
+ create_ductwork_table :ductwork_attempts do |table|
6
+ belongs_to(
7
+ table,
8
+ :execution,
9
+ index: false,
10
+ null: false,
11
+ foreign_key: { to_table: :ductwork_executions }
12
+ )
13
+ table.timestamp :started_at, null: false
14
+ table.timestamp :completed_at
15
+ table.timestamps null: false
16
+ end
17
+
18
+ add_index :ductwork_attempts, :execution_id, unique: true
19
+ end
20
+ end
@@ -10,18 +10,31 @@ class CreateDuctworkAvailabilities < Ductwork::Migration
10
10
  null: false,
11
11
  foreign_key: { to_table: :ductwork_executions }
12
12
  )
13
+ belongs_to(
14
+ table,
15
+ :process,
16
+ index: true,
17
+ null: true,
18
+ foreign_key: { to_table: :ductwork_processes }
19
+ )
13
20
  table.timestamp :started_at, null: false
14
21
  table.timestamp :completed_at
15
- table.integer :process_id
16
22
  table.string :pipeline_klass, null: false
17
23
  table.timestamps null: false
18
24
  end
19
25
 
20
26
  add_index :ductwork_availabilities, :execution_id, unique: true
21
27
  add_index :ductwork_availabilities, %i[id process_id]
22
- add_index :ductwork_availabilities,
23
- %i[pipeline_klass started_at],
24
- name: "index_ductwork_availabilities_on_claim_latest",
25
- where: "completed_at IS NULL"
28
+
29
+ if mysql?
30
+ add_index :ductwork_availabilities,
31
+ %i[pipeline_klass completed_at started_at],
32
+ name: "index_ductwork_availabilities_on_claim_latest"
33
+ else
34
+ add_index :ductwork_availabilities,
35
+ %i[pipeline_klass started_at],
36
+ name: "index_ductwork_availabilities_on_claim_latest",
37
+ where: "completed_at IS NULL"
38
+ end
26
39
  end
27
40
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateDuctworkBranchLinks < Ductwork::Migration
4
+ def change
5
+ create_ductwork_table :ductwork_branch_links do |table|
6
+ belongs_to(
7
+ table,
8
+ :parent_branch,
9
+ index: true,
10
+ null: false,
11
+ foreign_key: { to_table: :ductwork_branches }
12
+ )
13
+ belongs_to(
14
+ table,
15
+ :child_branch,
16
+ index: true,
17
+ null: false,
18
+ foreign_key: { to_table: :ductwork_branches }
19
+ )
20
+ table.timestamps null: false
21
+ end
22
+
23
+ add_index :ductwork_branch_links,
24
+ %w[parent_branch_id child_branch_id],
25
+ unique: true
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateDuctworkBranches < Ductwork::Migration
4
+ def change
5
+ create_ductwork_table :ductwork_branches do |table|
6
+ belongs_to(
7
+ table,
8
+ :run,
9
+ index: true,
10
+ null: false,
11
+ foreign_key: { to_table: :ductwork_runs }
12
+ )
13
+ table.string :pipeline_klass, null: false
14
+ table.string :status, null: false
15
+ table.string :halt_reason
16
+ table.string :claim_token
17
+ table.datetime :claimed_for_advancing_at
18
+ table.datetime :last_advanced_at, null: false
19
+ table.datetime :started_at, null: false
20
+ table.datetime :completed_at
21
+ table.timestamps null: false
22
+ end
23
+
24
+ if mysql?
25
+ add_index :ductwork_branches,
26
+ %w[pipeline_klass status claimed_for_advancing_at last_advanced_at],
27
+ name: "index_ductwork_branches_on_claim_latest"
28
+ else
29
+ add_index :ductwork_branches,
30
+ %w[pipeline_klass status last_advanced_at],
31
+ where: "claimed_for_advancing_at IS NULL",
32
+ name: "index_ductwork_branches_on_claim_latest"
33
+ end
34
+ add_index :ductwork_branches, %w[run_id started_at]
35
+ add_index :ductwork_branches, %w[run_id status]
36
+ end
37
+ end
@@ -10,10 +10,17 @@ class CreateDuctworkExecutions < Ductwork::Migration
10
10
  null: false,
11
11
  foreign_key: { to_table: :ductwork_jobs }
12
12
  )
13
+ belongs_to(
14
+ table,
15
+ :process,
16
+ index: true,
17
+ null: true,
18
+ foreign_key: { to_table: :ductwork_processes }
19
+ )
13
20
  table.timestamp :started_at, null: false
14
21
  table.timestamp :completed_at
15
22
  table.integer :retry_count, null: false
16
- table.integer :process_id
23
+ table.integer :crash_count, null: false
17
24
  table.timestamps null: false
18
25
  end
19
26
 
@@ -4,14 +4,6 @@ class CreateDuctworkPipelines < Ductwork::Migration
4
4
  def change
5
5
  create_ductwork_table :ductwork_pipelines do |table|
6
6
  table.string :klass, null: false
7
- table.text :definition, null: false
8
- table.string :definition_sha1, null: false
9
- table.timestamp :triggered_at, null: false
10
- table.timestamp :started_at, null: false
11
- table.timestamp :completed_at
12
- table.timestamp :halted_at
13
- table.timestamp :claimed_for_advancing_at
14
- table.timestamp :last_advanced_at, null: false
15
7
  table.string :status, null: false
16
8
  table.timestamps null: false
17
9
  end
@@ -5,10 +5,12 @@ class CreateDuctworkProcesses < Ductwork::Migration
5
5
  create_ductwork_table :ductwork_processes do |table|
6
6
  table.integer :pid, null: false
7
7
  table.string :machine_identifier, null: false
8
+ table.string :role, null: false
8
9
  table.timestamp :last_heartbeat_at, null: false
9
10
  table.timestamps null: false
10
11
  end
11
12
 
13
+ add_index :ductwork_processes, :last_heartbeat_at
12
14
  add_index :ductwork_processes, %i[pid machine_identifier], unique: true
13
15
  end
14
16
  end
@@ -18,5 +18,6 @@ class CreateDuctworkResults < Ductwork::Migration
18
18
  end
19
19
 
20
20
  add_index :ductwork_results, :execution_id, unique: true
21
+ add_index :ductwork_results, %i[result_type created_at]
21
22
  end
22
23
  end
@@ -5,16 +5,55 @@ class CreateDuctworkRuns < Ductwork::Migration
5
5
  create_ductwork_table :ductwork_runs do |table|
6
6
  belongs_to(
7
7
  table,
8
- :execution,
9
- index: false,
8
+ :pipeline,
9
+ index: true,
10
10
  null: false,
11
- foreign_key: { to_table: :ductwork_executions }
11
+ foreign_key: { to_table: :ductwork_pipelines }
12
12
  )
13
- table.timestamp :started_at, null: false
14
- table.timestamp :completed_at
13
+ table.string :pipeline_klass, null: false
14
+ table.text :definition, null: false
15
+ table.string :definition_sha1, null: false
16
+ table.string :status, null: false
17
+ table.datetime :triggered_at, null: false
18
+ table.datetime :started_at, null: false
19
+ table.datetime :completed_at
20
+ table.datetime :halted_at
21
+ table.datetime :on_halt_dispatched_at
15
22
  table.timestamps null: false
16
23
  end
17
24
 
18
- add_index :ductwork_runs, :execution_id, unique: true
25
+ add_index :ductwork_runs, %i[pipeline_id status]
26
+ add_index :ductwork_runs, %i[pipeline_id started_at]
27
+ add_index :ductwork_runs, :started_at
28
+ add_index :ductwork_runs, %i[pipeline_klass started_at]
29
+ add_index :ductwork_runs, %i[status started_at]
30
+
31
+ if mysql?
32
+ reversible do |direction|
33
+ direction.up do
34
+ execute <<~SQL
35
+ ALTER TABLE ductwork_runs
36
+ ADD COLUMN active_pipeline_id VARCHAR(36)
37
+ GENERATED ALWAYS AS (
38
+ IF(status IN ('in_progress', 'paused'), pipeline_id, NULL)
39
+ )
40
+ SQL
41
+ add_index :ductwork_runs, :active_pipeline_id,
42
+ unique: true,
43
+ name: :idx_unique_active_run
44
+ end
45
+
46
+ direction.down do
47
+ remove_index :ductwork_runs, name: :idx_unique_active_run
48
+ remove_column :ductwork_runs, :active_pipeline_id
49
+ end
50
+ end
51
+ else
52
+ add_index :ductwork_runs,
53
+ :pipeline_id,
54
+ unique: true,
55
+ where: "status IN ('in_progress', 'paused')",
56
+ name: :idx_unique_active_run
57
+ end
19
58
  end
20
59
  end
@@ -5,10 +5,24 @@ class CreateDuctworkSteps < Ductwork::Migration
5
5
  create_ductwork_table :ductwork_steps do |table|
6
6
  belongs_to(
7
7
  table,
8
- :pipeline,
8
+ :run,
9
9
  index: true,
10
10
  null: false,
11
- foreign_key: { to_table: :ductwork_pipelines }
11
+ foreign_key: { to_table: :ductwork_runs }
12
+ )
13
+ belongs_to(
14
+ table,
15
+ :branch,
16
+ index: true,
17
+ null: false,
18
+ foreign_key: { to_table: :ductwork_branches }
19
+ )
20
+ belongs_to(
21
+ table,
22
+ :source_step,
23
+ index: true,
24
+ null: true,
25
+ foreign_key: { to_table: :ductwork_steps }
12
26
  )
13
27
  table.string :node, null: false
14
28
  table.string :klass, null: false
@@ -21,9 +35,10 @@ class CreateDuctworkSteps < Ductwork::Migration
21
35
  table.timestamps null: false
22
36
  end
23
37
 
24
- add_index :ductwork_steps, %i[pipeline_id status node]
25
- add_index :ductwork_steps, %i[pipeline_id node status]
38
+ add_index :ductwork_steps, %i[branch_id started_at]
39
+ add_index :ductwork_steps, %i[run_id node status]
40
+ add_index :ductwork_steps, %i[run_id status node]
41
+ add_index :ductwork_steps, %i[status branch_id]
26
42
  add_index :ductwork_steps, %i[status node]
27
- add_index :ductwork_steps, %i[pipeline_id status]
28
43
  end
29
44
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateDuctworkTransitions < Ductwork::Migration
4
+ def change
5
+ create_ductwork_table :ductwork_transitions do |table|
6
+ belongs_to(
7
+ table,
8
+ :branch,
9
+ index: true,
10
+ null: false,
11
+ foreign_key: { to_table: :ductwork_branches }
12
+ )
13
+ belongs_to(
14
+ table,
15
+ :in_step,
16
+ index: true,
17
+ null: false,
18
+ foreign_key: { to_table: :ductwork_steps }
19
+ )
20
+ belongs_to(
21
+ table,
22
+ :out_step,
23
+ index: true,
24
+ null: true,
25
+ foreign_key: { to_table: :ductwork_steps }
26
+ )
27
+ table.datetime :started_at, null: false
28
+ table.datetime :completed_at
29
+ table.timestamps null: false
30
+ end
31
+
32
+ if mysql?
33
+ add_index :ductwork_transitions,
34
+ %i[branch_id completed_at started_at],
35
+ name: "index_ductwork_transitions_on_latest_open"
36
+ else
37
+ add_index :ductwork_transitions,
38
+ %i[branch_id started_at],
39
+ where: "completed_at IS NULL",
40
+ name: "index_ductwork_transitions_on_latest_open"
41
+ end
42
+ end
43
+ end
@@ -5,10 +5,10 @@ class CreateDuctworkTuples < Ductwork::Migration
5
5
  create_ductwork_table :ductwork_tuples do |table|
6
6
  belongs_to(
7
7
  table,
8
- :pipeline,
8
+ :run,
9
9
  index: true,
10
10
  null: false,
11
- foreign_key: { to_table: :ductwork_pipelines }
11
+ foreign_key: { to_table: :ductwork_runs }
12
12
  )
13
13
  table.string :key, null: false
14
14
  table.string :serialized_value
@@ -17,6 +17,6 @@ class CreateDuctworkTuples < Ductwork::Migration
17
17
  table.timestamps null: false
18
18
  end
19
19
 
20
- add_index :ductwork_tuples, %i[key pipeline_id], unique: true
20
+ add_index :ductwork_tuples, %i[run_id key], unique: true
21
21
  end
22
22
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddCrashCountToDuctworkAdvancements < Ductwork::Migration
4
+ def change
5
+ add_column :ductwork_advancements, :crash_count, :integer
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddCrashCountToDuctworkExecutions < Ductwork::Migration
4
+ def change
5
+ add_column :ductwork_executions, :crash_count, :integer
6
+
7
+ # NOTE: change this how you see fit for your scale
8
+ Ductwork::Execution
9
+ .where(crash_count: nil)
10
+ .update_all(crash_count: 0)
11
+
12
+ change_column_null :ductwork_executions, :crash_count, false
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddIndexesToDuctworkResults < Ductwork::Migration
4
+ def change
5
+ add_index :ductwork_results, %i[result_type created_at]
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddIndexesToDuctworkRuns < Ductwork::Migration
4
+ def change
5
+ add_index :ductwork_runs, :started_at
6
+ add_index :ductwork_runs, %i[pipeline_klass started_at]
7
+ add_index :ductwork_runs, %i[status started_at]
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddIndexesToDuctworkTransitions < Ductwork::Migration
4
+ def change
5
+ if mysql?
6
+ add_index :ductwork_transitions,
7
+ %i[branch_id completed_at started_at],
8
+ name: "index_ductwork_transitions_on_latest_open"
9
+ else
10
+ add_index :ductwork_transitions,
11
+ %i[branch_id started_at],
12
+ where: "completed_at IS NULL",
13
+ name: "index_ductwork_transitions_on_latest_open"
14
+ end
15
+ end
16
+ end