ductwork 0.4.0 → 0.5.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 +4 -4
- data/CHANGELOG.md +15 -0
- data/app/models/ductwork/pipeline.rb +58 -44
- data/app/models/ductwork/step.rb +1 -0
- data/lib/ductwork/processes/pipeline_advancer.rb +11 -8
- data/lib/ductwork/version.rb +1 -1
- data/lib/generators/ductwork/install/templates/db/create_ductwork_availabilities.rb +3 -0
- data/lib/generators/ductwork/install/templates/db/create_ductwork_executions.rb +2 -0
- data/lib/generators/ductwork/install/templates/db/create_ductwork_jobs.rb +2 -2
- data/lib/generators/ductwork/install/templates/db/create_ductwork_pipelines.rb +1 -1
- data/lib/generators/ductwork/install/templates/db/create_ductwork_results.rb +2 -0
- data/lib/generators/ductwork/install/templates/db/create_ductwork_runs.rb +2 -0
- data/lib/generators/ductwork/install/templates/db/create_ductwork_steps.rb +3 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e10d600812e131829fe76b4a694bf755a707d4a4c66e29afd21701ee17e2fa8b
|
|
4
|
+
data.tar.gz: 101e1a25cac0c898f678b0da11f2c3e0d679bfb9c11741ed99c8e4f5bb8e3782
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: be40286349648fa967100abdcecc2fd3519e616881e635362903fb90656c6568e59a005e1015ac477bcae7eff61cda7b05378b35603302f2ca2ea08f3558de6d
|
|
7
|
+
data.tar.gz: ed9f573f3d6f2913c2d22dc0081064cb6d67eb743f92a1e573260d31c56592837d093292029a7de82b6ca40ebe022dc2092522167b84f52eb805ff9768fbe6f8
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Ductwork Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.0]
|
|
4
|
+
|
|
5
|
+
- core: add "waiting" status to `Step` model
|
|
6
|
+
- core: add "waiting" status to `Pipeline` model
|
|
7
|
+
- fix: change `jobs.input_args` and `jobs.output_payload` column type to `text`
|
|
8
|
+
- fix: change `pipelines.definition` column type to `text` - this prevents larger definitions from being clipped if there is a size limit on the string column
|
|
9
|
+
- feat: add missing unique index on `ductwork_results` and `ductwork_runs` tables
|
|
10
|
+
- feat: add missing composite index on `ductwork_executions` table for `Ductwork::Job.claim_latest` method
|
|
11
|
+
- feat: add missing composite index on `ductwork_availabilities` table for `Ductwork::Job.claim_latest` method
|
|
12
|
+
- feat: use array instead of ActiveRecord relation when advancing pipelines - this has major performance benefits but comes with memory-usage implications (see comments)
|
|
13
|
+
- fix: add condition to query to return correct pipelines that need advancing
|
|
14
|
+
- fix: release pipeline claim only if successfully claimed
|
|
15
|
+
- chore: add pipeline ID to misc log lines
|
|
16
|
+
- feat: add missing composite indexes on `ductwork_steps` table
|
|
17
|
+
|
|
3
18
|
## [0.4.0]
|
|
4
19
|
|
|
5
20
|
- chore: change job worker thread name format
|
|
@@ -14,6 +14,7 @@ module Ductwork
|
|
|
14
14
|
enum :status,
|
|
15
15
|
pending: "pending",
|
|
16
16
|
in_progress: "in_progress",
|
|
17
|
+
waiting: "waiting",
|
|
17
18
|
halted: "halted",
|
|
18
19
|
completed: "completed"
|
|
19
20
|
|
|
@@ -87,22 +88,19 @@ module Ductwork
|
|
|
87
88
|
end
|
|
88
89
|
|
|
89
90
|
def advance!
|
|
90
|
-
# NOTE:
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
.fetch(:edges, {})
|
|
98
|
-
.select { |k| k.in?(advancing.pluck(:klass)) }
|
|
99
|
-
end
|
|
91
|
+
# NOTE: if we've expanded the pipeline there could be a lot of
|
|
92
|
+
# advancing records which may cause memory issues. something to
|
|
93
|
+
# watch out for here and maybe add in config to use AR relation
|
|
94
|
+
# at certain counts or even memory limits.
|
|
95
|
+
advancing_steps = steps.advancing.pluck(:id, :klass)
|
|
96
|
+
advancing_ids = advancing_steps.map(&:first)
|
|
97
|
+
edges = find_edges(advancing_steps)
|
|
100
98
|
|
|
101
99
|
Ductwork::Record.transaction do
|
|
102
100
|
if edges.nil? || edges.values.all?(&:empty?)
|
|
103
|
-
conditionally_complete_pipeline(
|
|
101
|
+
conditionally_complete_pipeline(advancing_ids)
|
|
104
102
|
else
|
|
105
|
-
advance_to_next_steps_by_type(edges,
|
|
103
|
+
advance_to_next_steps_by_type(edges, advancing_ids)
|
|
106
104
|
end
|
|
107
105
|
end
|
|
108
106
|
end
|
|
@@ -120,10 +118,25 @@ module Ductwork
|
|
|
120
118
|
Ductwork::Job.enqueue(next_step, input_arg)
|
|
121
119
|
end
|
|
122
120
|
|
|
123
|
-
def
|
|
124
|
-
|
|
121
|
+
def find_edges(advancing_steps)
|
|
122
|
+
if advancing_steps.any?
|
|
123
|
+
klasses = advancing_steps.map(&:last)
|
|
125
124
|
|
|
126
|
-
|
|
125
|
+
parsed_definition.fetch(:edges, {}).select { |k| k.in?(klasses) }
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def conditionally_complete_pipeline(advancing_ids)
|
|
130
|
+
steps
|
|
131
|
+
.where(id: advancing_ids)
|
|
132
|
+
.update_all(status: :completed, completed_at: Time.current)
|
|
133
|
+
|
|
134
|
+
remaining = steps
|
|
135
|
+
.where(status: %w[in_progress pending advancing])
|
|
136
|
+
.where.not(id: advancing_ids)
|
|
137
|
+
.exists?
|
|
138
|
+
|
|
139
|
+
if !remaining
|
|
127
140
|
update!(status: :completed, completed_at: Time.current)
|
|
128
141
|
|
|
129
142
|
Ductwork.logger.info(
|
|
@@ -134,9 +147,11 @@ module Ductwork
|
|
|
134
147
|
end
|
|
135
148
|
end
|
|
136
149
|
|
|
137
|
-
def advance_to_next_steps_by_type(edges,
|
|
150
|
+
def advance_to_next_steps_by_type(edges, advancing_ids)
|
|
151
|
+
steps.where(id: advancing_ids).update_all(status: :completed, completed_at: Time.current)
|
|
152
|
+
|
|
138
153
|
if edges.all? { |_, v| v.dig(-1, :type) == "combine" }
|
|
139
|
-
conditionally_combine_next_steps(edges,
|
|
154
|
+
conditionally_combine_next_steps(edges, advancing_ids)
|
|
140
155
|
else
|
|
141
156
|
edges.each do |step_klass, step_edges|
|
|
142
157
|
edge = step_edges[-1]
|
|
@@ -145,27 +160,25 @@ module Ductwork
|
|
|
145
160
|
step_type = edge[:type] == "chain" ? "default" : edge[:type]
|
|
146
161
|
|
|
147
162
|
if step_type == "collapse"
|
|
148
|
-
conditionally_collapse_next_steps(step_klass, edge,
|
|
163
|
+
conditionally_collapse_next_steps(step_klass, edge, advancing_ids)
|
|
149
164
|
else
|
|
150
|
-
advance_non_merging_steps(step_klass,
|
|
165
|
+
advance_non_merging_steps(step_klass, edge, advancing_ids)
|
|
151
166
|
end
|
|
152
167
|
end
|
|
153
168
|
end
|
|
154
|
-
advancing.update!(status: :completed, completed_at: Time.current)
|
|
155
169
|
log_pipeline_advanced(edges)
|
|
156
170
|
end
|
|
157
171
|
|
|
158
|
-
def advance_non_merging_steps(step_klass,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
# this enum value "default" :sad:
|
|
163
|
-
step_type = edge[:type] == "chain" ? "default" : edge[:type]
|
|
172
|
+
def advance_non_merging_steps(step_klass, edge, advancing_ids)
|
|
173
|
+
# NOTE: "chain" is used by ActiveRecord so we have to call
|
|
174
|
+
# this enum value "default" :sad:
|
|
175
|
+
step_type = edge[:type] == "chain" ? "default" : edge[:type]
|
|
164
176
|
|
|
177
|
+
steps.where(id: advancing_ids, klass: step_klass).find_each do |step|
|
|
165
178
|
if step_type.in?(%w[default divide])
|
|
166
|
-
advance_to_next_steps(step_type,
|
|
179
|
+
advance_to_next_steps(step_type, step.id, edge)
|
|
167
180
|
elsif step_type == "expand"
|
|
168
|
-
expand_to_next_steps(step_type,
|
|
181
|
+
expand_to_next_steps(step_type, step.id, edge)
|
|
169
182
|
else
|
|
170
183
|
Ductwork.logger.error(
|
|
171
184
|
msg: "Invalid step type",
|
|
@@ -177,7 +190,7 @@ module Ductwork
|
|
|
177
190
|
end
|
|
178
191
|
end
|
|
179
192
|
|
|
180
|
-
def advance_to_next_steps(step_type,
|
|
193
|
+
def advance_to_next_steps(step_type, step_id, edge)
|
|
181
194
|
too_many = edge[:to].tally.any? do |to_klass, count|
|
|
182
195
|
depth = Ductwork
|
|
183
196
|
.configuration
|
|
@@ -196,14 +209,15 @@ module Ductwork
|
|
|
196
209
|
step_type: step_type,
|
|
197
210
|
started_at: Time.current
|
|
198
211
|
)
|
|
199
|
-
Ductwork::Job.
|
|
212
|
+
return_value = Ductwork::Job.find_by(step_id:).return_value
|
|
213
|
+
Ductwork::Job.enqueue(next_step, return_value)
|
|
200
214
|
end
|
|
201
215
|
end
|
|
202
216
|
end
|
|
203
217
|
|
|
204
|
-
def conditionally_combine_next_steps(edges,
|
|
218
|
+
def conditionally_combine_next_steps(edges, advancing_ids)
|
|
205
219
|
if steps.where(status: %w[pending in_progress], klass: edges.keys).none?
|
|
206
|
-
combine_next_steps(edges,
|
|
220
|
+
combine_next_steps(edges, advancing_ids)
|
|
207
221
|
else
|
|
208
222
|
Ductwork.logger.debug(
|
|
209
223
|
msg: "Not all divided steps have completed; not combining",
|
|
@@ -213,14 +227,15 @@ module Ductwork
|
|
|
213
227
|
end
|
|
214
228
|
end
|
|
215
229
|
|
|
216
|
-
def combine_next_steps(edges,
|
|
230
|
+
def combine_next_steps(edges, advancing_ids)
|
|
217
231
|
klass = edges.values.sample.dig(-1, :to).sole
|
|
218
232
|
step_type = "combine"
|
|
219
|
-
groups =
|
|
233
|
+
groups = steps
|
|
234
|
+
.where(id: advancing_ids)
|
|
220
235
|
.group(:klass)
|
|
221
236
|
.count
|
|
222
237
|
.keys
|
|
223
|
-
.map { |k|
|
|
238
|
+
.map { |k| steps.where(id: advancing_ids).where(klass: k) }
|
|
224
239
|
|
|
225
240
|
groups.first.zip(*groups[1..]).each do |group|
|
|
226
241
|
input_arg = Ductwork::Job
|
|
@@ -230,9 +245,11 @@ module Ductwork
|
|
|
230
245
|
end
|
|
231
246
|
end
|
|
232
247
|
|
|
233
|
-
def expand_to_next_steps(step_type,
|
|
248
|
+
def expand_to_next_steps(step_type, step_id, edge)
|
|
234
249
|
next_klass = edge[:to].sole
|
|
235
|
-
return_value =
|
|
250
|
+
return_value = Ductwork::Job
|
|
251
|
+
.find_by(step_id:)
|
|
252
|
+
.return_value
|
|
236
253
|
max_depth = Ductwork.configuration.steps_max_depth(pipeline: klass, step: next_klass)
|
|
237
254
|
|
|
238
255
|
if max_depth != -1 && return_value.count > max_depth
|
|
@@ -248,9 +265,9 @@ module Ductwork
|
|
|
248
265
|
end
|
|
249
266
|
end
|
|
250
267
|
|
|
251
|
-
def conditionally_collapse_next_steps(step_klass, edge,
|
|
268
|
+
def conditionally_collapse_next_steps(step_klass, edge, advancing_ids)
|
|
252
269
|
if steps.where(status: %w[pending in_progress], klass: step_klass).none?
|
|
253
|
-
collapse_next_steps(edge[:to].sole,
|
|
270
|
+
collapse_next_steps(edge[:to].sole, advancing_ids)
|
|
254
271
|
else
|
|
255
272
|
Ductwork.logger.debug(
|
|
256
273
|
msg: "Not all expanded steps have completed; not collapsing",
|
|
@@ -260,14 +277,11 @@ module Ductwork
|
|
|
260
277
|
end
|
|
261
278
|
end
|
|
262
279
|
|
|
263
|
-
def collapse_next_steps(klass,
|
|
280
|
+
def collapse_next_steps(klass, advancing_ids)
|
|
264
281
|
step_type = "collapse"
|
|
265
282
|
input_arg = []
|
|
266
283
|
|
|
267
|
-
|
|
268
|
-
# could be A LOT of jobs so we want to use batch methods
|
|
269
|
-
# to avoid creating too many in-memory objects
|
|
270
|
-
Ductwork::Job.where(step_id: advancing.ids).find_each do |job|
|
|
284
|
+
Ductwork::Job.where(step_id: advancing_ids).find_each do |job|
|
|
271
285
|
input_arg << job.return_value
|
|
272
286
|
end
|
|
273
287
|
|
data/app/models/ductwork/step.rb
CHANGED
|
@@ -15,6 +15,7 @@ module Ductwork
|
|
|
15
15
|
Ductwork::Pipeline
|
|
16
16
|
.in_progress
|
|
17
17
|
.where(klass: klass, claimed_for_advancing_at: nil)
|
|
18
|
+
.where(steps: Ductwork::Step.where(status: :advancing))
|
|
18
19
|
.where.not(steps: Ductwork::Step.where.not(status: %w[advancing completed]))
|
|
19
20
|
.order(:last_advanced_at)
|
|
20
21
|
.limit(1)
|
|
@@ -30,6 +31,7 @@ module Ductwork
|
|
|
30
31
|
if rows_updated == 1
|
|
31
32
|
Ductwork.logger.debug(
|
|
32
33
|
msg: "Pipeline claimed",
|
|
34
|
+
pipeline_id: id,
|
|
33
35
|
pipeline: klass,
|
|
34
36
|
role: :pipeline_advancer
|
|
35
37
|
)
|
|
@@ -39,28 +41,29 @@ module Ductwork
|
|
|
39
41
|
|
|
40
42
|
Ductwork.logger.debug(
|
|
41
43
|
msg: "Pipeline advanced",
|
|
44
|
+
pipeline_id: id,
|
|
42
45
|
pipeline: klass,
|
|
43
46
|
role: :pipeline_advancer
|
|
44
47
|
)
|
|
48
|
+
|
|
49
|
+
# release the pipeline and set last advanced at so it doesnt block.
|
|
50
|
+
# we're not using a queue so we have to use a db timestamp
|
|
51
|
+
pipeline.update!(
|
|
52
|
+
claimed_for_advancing_at: nil,
|
|
53
|
+
last_advanced_at: Time.current
|
|
54
|
+
)
|
|
45
55
|
else
|
|
46
56
|
Ductwork.logger.debug(
|
|
47
57
|
msg: "Did not claim pipeline, avoided race condition",
|
|
58
|
+
pipeline_id: id,
|
|
48
59
|
pipeline: klass,
|
|
49
60
|
role: :pipeline_advancer
|
|
50
61
|
)
|
|
51
62
|
end
|
|
52
|
-
|
|
53
|
-
# release the pipeline and set last advanced at so it doesnt block.
|
|
54
|
-
# we're not using a queue so we have to use a db timestamp
|
|
55
|
-
Ductwork::Pipeline.find(id).update!(
|
|
56
|
-
claimed_for_advancing_at: nil,
|
|
57
|
-
last_advanced_at: Time.current
|
|
58
|
-
)
|
|
59
63
|
else
|
|
60
64
|
Ductwork.logger.debug(
|
|
61
65
|
msg: "No pipeline needs advancing",
|
|
62
66
|
pipeline: klass,
|
|
63
|
-
id: id,
|
|
64
67
|
role: :pipeline_advancer
|
|
65
68
|
)
|
|
66
69
|
end
|
data/lib/ductwork/version.rb
CHANGED
|
@@ -12,5 +12,8 @@ class CreateDuctworkAvailabilities < ActiveRecord::Migration[<%= Rails::VERSION:
|
|
|
12
12
|
|
|
13
13
|
add_index :ductwork_availabilities, :execution_id, unique: true
|
|
14
14
|
add_index :ductwork_availabilities, %i[id process_id]
|
|
15
|
+
add_index :ductwork_availabilities,
|
|
16
|
+
%i[completed_at started_at created_at],
|
|
17
|
+
name: "index_ductwork_availabilities_on_claim_latest"
|
|
15
18
|
end
|
|
16
19
|
end
|
|
@@ -7,8 +7,8 @@ class CreateDuctworkJobs < ActiveRecord::Migration[<%= Rails::VERSION::MAJOR %>.
|
|
|
7
7
|
table.string :klass, null: false
|
|
8
8
|
table.timestamp :started_at, null: false
|
|
9
9
|
table.timestamp :completed_at
|
|
10
|
-
table.
|
|
11
|
-
table.
|
|
10
|
+
table.text :input_args, null: false
|
|
11
|
+
table.text :output_payload
|
|
12
12
|
table.timestamps null: false
|
|
13
13
|
end
|
|
14
14
|
|
|
@@ -4,7 +4,7 @@ class CreateDuctworkPipelines < ActiveRecord::Migration[<%= Rails::VERSION::MAJO
|
|
|
4
4
|
def change
|
|
5
5
|
create_table :ductwork_pipelines do |table|
|
|
6
6
|
table.string :klass, null: false
|
|
7
|
-
table.
|
|
7
|
+
table.text :definition, null: false
|
|
8
8
|
table.string :definition_sha1, null: false
|
|
9
9
|
table.timestamp :triggered_at, null: false
|
|
10
10
|
table.timestamp :completed_at
|
|
@@ -12,6 +12,9 @@ class CreateDuctworkSteps < ActiveRecord::Migration[<%= Rails::VERSION::MAJOR %>
|
|
|
12
12
|
table.timestamps null: false
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
add_index :ductwork_steps, %i[pipeline_id status klass]
|
|
16
|
+
add_index :ductwork_steps, %i[pipeline_id klass status]
|
|
17
|
+
add_index :ductwork_steps, %i[status klass]
|
|
15
18
|
add_index :ductwork_steps, %i[pipeline_id status]
|
|
16
19
|
end
|
|
17
20
|
end
|