undertow 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/lib/undertow/drain_job.rb +39 -15
- data/lib/undertow/dsl.rb +17 -4
- data/lib/undertow/railtie.rb +1 -1
- data/lib/undertow/registry.rb +4 -4
- data/lib/undertow/trackable.rb +1 -1
- data/lib/undertow/version.rb +1 -1
- 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: 8eaf3f2d446514a0b894990d2b5f0d5151a9664782af31533dfb38a1954b60b1
|
|
4
|
+
data.tar.gz: 9bdd7da4f3e68d80638e79911ce67fe20a56b689ce048835836eb2e6c90f3d69
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3f45df71d291f76c94a4f6550734ef86dbfb8bbccb8d85011e7bb6c144ea78098471e683cf59fc9860858388517ac570e5d65b0eeea215a4180443ab029fd9cb
|
|
7
|
+
data.tar.gz: 6ecdd5f94d0500e8c16ca9f127c3aed178f517be9006d589b3a28695d144931a4b08ac9e9e31567fe0b2d94fb5b2a5d1fe8ca6a58c71a864b04c6d0727c30e29
|
data/lib/undertow/drain_job.rb
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Undertow
|
|
4
4
|
# Drains the per-model Redis buffers and delivers batches of dirty IDs to
|
|
5
|
-
# each model's configured
|
|
5
|
+
# each model's configured sinks.
|
|
6
6
|
#
|
|
7
|
-
# Publishes two ActiveSupport::Notifications events
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# Publishes two ActiveSupport::Notifications events, once per sink (per chunk,
|
|
8
|
+
# if a sink's max_batch_size is smaller than the popped batch):
|
|
9
|
+
# drain.undertow , after a successful sink call ({ model:, sink:, upserted_ids:, deleted_ids:, duration_ms: })
|
|
10
|
+
# error.undertow , when a sink raises ({ model:, sink:, exception: })
|
|
10
11
|
class DrainJob < ActiveJob::Base
|
|
11
12
|
queue_as { Undertow.configuration.queue_name }
|
|
12
13
|
|
|
@@ -26,6 +27,10 @@ module Undertow
|
|
|
26
27
|
def drain_model(model_name)
|
|
27
28
|
max = Undertow.configuration.max_batch
|
|
28
29
|
|
|
30
|
+
config = Registry[model_name]
|
|
31
|
+
raise "No Undertow config registered for #{model_name}" unless config
|
|
32
|
+
raise "#{model_name} has no undertow_sink configured" if config.sinks.empty?
|
|
33
|
+
|
|
29
34
|
# Deregister before popping, any concurrent push will re-add the model,
|
|
30
35
|
# preventing the race where srem fires after a concurrent sadd.
|
|
31
36
|
Buffer.deregister_model(model_name)
|
|
@@ -37,26 +42,45 @@ module Undertow
|
|
|
37
42
|
# If the batch was capped, re-register so the next scheduler tick picks up.
|
|
38
43
|
Buffer.reregister_model(model_name) if Buffer.remaining(model_name).positive?
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
current_sink = nil
|
|
46
|
+
config.sinks.each do |sink_name, sink|
|
|
47
|
+
current_sink = sink_name
|
|
48
|
+
chunk_size = sink[:max_batch_size] || max
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
each_sink_chunk(upserted_ids, deleted_ids, chunk_size) do |chunk_upserted, chunk_deleted|
|
|
51
|
+
duration_ms = measure_ms { sink[:handler].call(model_name, chunk_upserted, chunk_deleted) }
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
ActiveSupport::Notifications.instrument('drain.undertow', {
|
|
54
|
+
model: model_name,
|
|
55
|
+
sink: sink_name,
|
|
56
|
+
upserted_ids: chunk_upserted,
|
|
57
|
+
deleted_ids: chunk_deleted,
|
|
58
|
+
duration_ms: duration_ms
|
|
59
|
+
})
|
|
60
|
+
end
|
|
61
|
+
end
|
|
52
62
|
rescue StandardError => e
|
|
53
63
|
Buffer.restore_pending(model_name, upserted_ids) if upserted_ids&.any?
|
|
54
64
|
Buffer.restore_deleted(model_name, deleted_ids) if deleted_ids&.any?
|
|
55
65
|
Buffer.reregister_model(model_name)
|
|
56
|
-
ActiveSupport::Notifications.instrument('error.undertow', { model: model_name, exception: e })
|
|
66
|
+
ActiveSupport::Notifications.instrument('error.undertow', { model: model_name, sink: current_sink, exception: e })
|
|
57
67
|
Rails.logger.error("[Undertow::DrainJob] #{model_name}: #{e.message}") if defined?(Rails)
|
|
58
68
|
end
|
|
59
69
|
|
|
70
|
+
# Splits upserted_ids/deleted_ids into chunks of at most chunk_size combined
|
|
71
|
+
# items, yielding each chunk as (chunk_upserted, chunk_deleted). Tags each ID
|
|
72
|
+
# with which array it came from, concatenates into one list, slices that,
|
|
73
|
+
# then splits each slice back apart, so a single chunk_size limit governs
|
|
74
|
+
# the combined total regardless of the upserted/deleted split.
|
|
75
|
+
def each_sink_chunk(upserted_ids, deleted_ids, chunk_size)
|
|
76
|
+
tagged = upserted_ids.map { |id| [true, id] } + deleted_ids.map { |id| [false, id] }
|
|
77
|
+
|
|
78
|
+
tagged.each_slice(chunk_size) do |slice|
|
|
79
|
+
chunk_upserted, chunk_deleted = slice.partition { |upserted, _| upserted }
|
|
80
|
+
yield chunk_upserted.map(&:last), chunk_deleted.map(&:last)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
60
84
|
def measure_ms
|
|
61
85
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
62
86
|
yield
|
data/lib/undertow/dsl.rb
CHANGED
|
@@ -6,8 +6,8 @@ module Undertow
|
|
|
6
6
|
# gets Trackable behavior wired in at boot, no include needed.
|
|
7
7
|
#
|
|
8
8
|
# class Post < ApplicationRecord
|
|
9
|
-
#
|
|
10
|
-
# undertow_skip
|
|
9
|
+
# undertow_sink(:search_index) { |model_name, upserted_ids, deleted_ids| PostSyncJob.perform_later(upserted_ids, deleted_ids) }
|
|
10
|
+
# undertow_skip %w[view_count updated_at]
|
|
11
11
|
#
|
|
12
12
|
# undertow_depends_on :author, foreign_key: :author_id, watched_columns: %w[name bio]
|
|
13
13
|
# undertow_depends_on :tag,
|
|
@@ -16,8 +16,21 @@ module Undertow
|
|
|
16
16
|
# end
|
|
17
17
|
#
|
|
18
18
|
module DSL
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
# Registers a named sink. Call once per sink; every sink on a model receives
|
|
20
|
+
# the same (model_name, upserted_ids, deleted_ids) on each drain, so the
|
|
21
|
+
# block decides what to do with them, e.g. reindex a search index or publish
|
|
22
|
+
# to Kafka.
|
|
23
|
+
#
|
|
24
|
+
# `max_batch_size:` bounds how many IDs this sink's block receives per call.
|
|
25
|
+
# Defaults to `Undertow.configuration.max_batch`, the same size as the
|
|
26
|
+
# popped batch, meaning the block gets called once with everything. Set it
|
|
27
|
+
# lower when this sink's downstream call has a tighter limit than other
|
|
28
|
+
# sinks on the same model; DrainJob will call the block multiple times,
|
|
29
|
+
# chunked to this size, without affecting any other sink's batch size.
|
|
30
|
+
def undertow_sink(name, max_batch_size: nil, &handler)
|
|
31
|
+
raise ArgumentError, 'undertow_sink requires a block' unless handler
|
|
32
|
+
|
|
33
|
+
_undertow_config.sinks[name.to_sym] = { max_batch_size: max_batch_size, handler: handler }
|
|
21
34
|
_undertow_ensure_trackable!
|
|
22
35
|
end
|
|
23
36
|
|
data/lib/undertow/railtie.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Undertow
|
|
4
4
|
class Railtie < Rails::Railtie
|
|
5
5
|
# Extend ActiveRecord::Base with the Undertow DSL so any model can call
|
|
6
|
-
#
|
|
6
|
+
# undertow_sink, undertow_skip, and undertow_depends_on in its class body.
|
|
7
7
|
initializer 'undertow.extend_active_record' do
|
|
8
8
|
ActiveSupport.on_load(:active_record) { extend Undertow::DSL }
|
|
9
9
|
end
|
data/lib/undertow/registry.rb
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
module Undertow
|
|
4
4
|
# Holds the declared dependency configuration for every tracked model.
|
|
5
|
-
# Populated at class load time via the DSL (
|
|
5
|
+
# Populated at class load time via the DSL (undertow_sink, undertow_skip,
|
|
6
6
|
# undertow_depends_on); consumed by the Railtie and DrainJob.
|
|
7
7
|
module Registry
|
|
8
8
|
MODELS_KEY = 'undertow:pending:models'
|
|
9
9
|
|
|
10
10
|
class ModelConfig
|
|
11
|
-
attr_reader :model_name, :dependencies
|
|
12
|
-
attr_accessor :
|
|
11
|
+
attr_reader :model_name, :dependencies, :sinks
|
|
12
|
+
attr_accessor :skip_columns
|
|
13
13
|
|
|
14
14
|
def initialize(model_name)
|
|
15
15
|
@model_name = model_name
|
|
16
16
|
@dependencies = []
|
|
17
17
|
@skip_columns = []
|
|
18
|
-
@
|
|
18
|
+
@sinks = {}
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
data/lib/undertow/trackable.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Undertow
|
|
4
4
|
# ActiveRecord concern mixed in automatically when a model uses the Undertow DSL
|
|
5
|
-
# (
|
|
5
|
+
# (undertow_sink, undertow_skip, undertow_depends_on). Never included manually.
|
|
6
6
|
#
|
|
7
7
|
# Provides class-level callback registration and dependency push handlers, plus
|
|
8
8
|
# instance-level self-tracking handlers. Callbacks are wired at boot by the Railtie
|
data/lib/undertow/version.rb
CHANGED