cdc-solid-queue 0.1.2 → 0.2.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 +6 -0
- data/README.md +62 -0
- data/lib/cdc/solid_queue/configuration.rb +18 -1
- data/lib/cdc/solid_queue/downstream_processor.rb +65 -0
- data/lib/cdc/solid_queue/processor_job.rb +6 -1
- data/lib/cdc/solid_queue/version.rb +1 -1
- data/lib/cdc/solid_queue.rb +1 -0
- data/sig/cdc/solid_queue.rbs +35 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a31a7915a2b6ba29afe3497045f7be40477b0388b89a0357a18bb77428f5701c
|
|
4
|
+
data.tar.gz: 4485d2cdf5c8137dc7a11503364c3f05a1d00691d83d0be18f3c5a194ba6c968
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f3a59fef64e5227fcf6ddab9ce45363221058baa8dbed1932e7eedd75620fe2f1b4f0382c97232ad6cfc4f15ac30044f3427e05c21aebec2b4283aafb255f27
|
|
7
|
+
data.tar.gz: 6aacc4886cd318f2ce12dfcb6164d3c059c8a37064ef0daced35dc016d40a4047e2054e11b4f0a6d84a3af01447b060452f9e1c6e3dae5f1f187c53df1532035
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.2.0
|
|
6
|
+
|
|
7
|
+
- Optional downstream processor delegation to `cdc-concurrent` and `cdc-parallel`.
|
|
8
|
+
- Rails example now demonstrates `cdc-concurrent` downstream processing.
|
|
9
|
+
- Benchmark can measure direct downstream delegation overhead.
|
|
10
|
+
|
|
5
11
|
## 0.1.2
|
|
6
12
|
|
|
7
13
|
- Minimal Rails app example.
|
data/README.md
CHANGED
|
@@ -54,6 +54,44 @@ class supports it. When `preserve_order` is enabled, the enqueued payload also
|
|
|
54
54
|
includes cdc-solid-queue metadata with the configured ordering key and computed
|
|
55
55
|
ordering value.
|
|
56
56
|
|
|
57
|
+
## Downstream Processing
|
|
58
|
+
|
|
59
|
+
Processor jobs can delegate work to CDC downstream runtime primitives. The
|
|
60
|
+
default downstream runtime is `:concurrent`, backed by `cdc-concurrent`, which
|
|
61
|
+
fits Solid Queue jobs that spend most of their time on I/O. CPU-heavy work can
|
|
62
|
+
opt into `:parallel`, backed by `cdc-parallel`, in Ruby 4 applications.
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
class WebhookProcessor < CDC::Core::Processor
|
|
66
|
+
concurrent_safe!
|
|
67
|
+
|
|
68
|
+
def process(event)
|
|
69
|
+
# perform I/O-bound work
|
|
70
|
+
CDC::Core::ProcessorResult.success(event)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
CDC::SolidQueue.configure do |config|
|
|
75
|
+
config.processor_job = UserChangedJob
|
|
76
|
+
config.downstream_processor = WebhookProcessor.new
|
|
77
|
+
config.downstream_runtime = :concurrent
|
|
78
|
+
config.downstream_options = { concurrency: 100, timeout: 5.0 }
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Use `:parallel` only when the processor is Ractor-safe and the application runs
|
|
83
|
+
on Ruby 4:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
config.downstream_runtime = :parallel
|
|
87
|
+
config.downstream_options = { size: 4, timeout: 5 }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Both runtime gems are optional. Add `cdc-concurrent` or `cdc-parallel` to the
|
|
91
|
+
application Gemfile when selecting that runtime. Without a configured
|
|
92
|
+
`downstream_processor`, `CDC::SolidQueue::ProcessorJob` falls back to the job's
|
|
93
|
+
own `#process(event)` method.
|
|
94
|
+
|
|
57
95
|
## Rails Task
|
|
58
96
|
|
|
59
97
|
Rails applications can load the Railtie integration:
|
|
@@ -96,6 +134,8 @@ bundle exec rake benchmark:enqueue
|
|
|
96
134
|
```
|
|
97
135
|
|
|
98
136
|
Set `CDC_SOLID_QUEUE_BENCH_EVENTS` to control the event count.
|
|
137
|
+
Set `CDC_SOLID_QUEUE_BENCH_MODE=downstream_direct` to measure direct downstream
|
|
138
|
+
processor delegation overhead without Solid Queue enqueue translation.
|
|
99
139
|
|
|
100
140
|
Example local result on Ruby 3.4.9:
|
|
101
141
|
|
|
@@ -113,6 +153,28 @@ In that run, `cdc-solid-queue` translated and dispatched about 63.6k synthetic
|
|
|
113
153
|
events per second, so real throughput will usually be dominated by Solid Queue
|
|
114
154
|
persistence, database latency, job execution cost, and CDC source throughput.
|
|
115
155
|
|
|
156
|
+
Example `downstream_direct` results on the same machine:
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
mode=downstream_direct events=100000000 elapsed=16.2669s rate=6147457.32 events/s
|
|
160
|
+
mode=downstream_direct events=1000000000 elapsed=157.8708s rate=6334292.58 events/s
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
These runs measure the lowest-overhead downstream delegation path:
|
|
164
|
+
|
|
165
|
+
```text
|
|
166
|
+
CDC::SolidQueue::DownstreamProcessor
|
|
167
|
+
-> :direct runtime branch
|
|
168
|
+
-> BenchmarkProcessor#process(event)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
They do not measure Solid Queue enqueueing, Active Job serialization,
|
|
172
|
+
PostgreSQL CDC, pgoutput parsing or decoding, `cdc-concurrent`,
|
|
173
|
+
`cdc-parallel`, real application processor work, network I/O, or database I/O.
|
|
174
|
+
The result means the direct downstream adapter can dispatch about 6.1M to 6.3M
|
|
175
|
+
prebuilt synthetic events per second on that machine, making the adapter layer
|
|
176
|
+
negligible compared with real persistence, CDC source, and processor costs.
|
|
177
|
+
|
|
116
178
|
## MVP Checkpoint Rule
|
|
117
179
|
|
|
118
180
|
A checkpoint advances after the Solid Queue job is durably inserted. Job execution success is handled by Solid Queue retry semantics.
|
|
@@ -12,8 +12,11 @@ module CDC
|
|
|
12
12
|
SUPPORTED_SOURCE = :postgresql
|
|
13
13
|
# Supported ordering scopes for serialized CDC events.
|
|
14
14
|
ORDERING_KEYS = %i[identity primary_key relation transaction global none].freeze
|
|
15
|
+
# Supported downstream execution runtimes for processor jobs.
|
|
16
|
+
DOWNSTREAM_RUNTIMES = %i[concurrent parallel direct].freeze
|
|
15
17
|
|
|
16
|
-
attr_accessor :processor_job, :queue, :preserve_order, :ordering_key, :postgresql, :checkpoint
|
|
18
|
+
attr_accessor :processor_job, :queue, :preserve_order, :ordering_key, :postgresql, :checkpoint,
|
|
19
|
+
:downstream_processor, :downstream_runtime, :downstream_options
|
|
17
20
|
|
|
18
21
|
# Build a configuration with safe defaults.
|
|
19
22
|
def initialize
|
|
@@ -23,6 +26,9 @@ module CDC
|
|
|
23
26
|
@ordering_key = :identity
|
|
24
27
|
@postgresql = {}
|
|
25
28
|
@checkpoint = Checkpoint.new
|
|
29
|
+
@downstream_processor = nil
|
|
30
|
+
@downstream_runtime = :concurrent
|
|
31
|
+
@downstream_options = {}
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
# Validate this configuration.
|
|
@@ -37,6 +43,7 @@ module CDC
|
|
|
37
43
|
validate_ordering_key!
|
|
38
44
|
validate_postgresql!
|
|
39
45
|
validate_checkpoint!
|
|
46
|
+
validate_downstream!
|
|
40
47
|
true
|
|
41
48
|
end
|
|
42
49
|
# rubocop:enable Naming/PredicateMethod
|
|
@@ -81,6 +88,16 @@ module CDC
|
|
|
81
88
|
|
|
82
89
|
raise ConfigurationError, 'checkpoint must respond to advance'
|
|
83
90
|
end
|
|
91
|
+
|
|
92
|
+
def validate_downstream!
|
|
93
|
+
unless DOWNSTREAM_RUNTIMES.include?(@downstream_runtime)
|
|
94
|
+
raise ConfigurationError, "downstream_runtime must be one of: #{DOWNSTREAM_RUNTIMES.join(', ')}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
return if @downstream_processor.nil? || @downstream_processor.respond_to?(:process)
|
|
98
|
+
|
|
99
|
+
raise ConfigurationError, 'downstream_processor must respond to process'
|
|
100
|
+
end
|
|
84
101
|
end
|
|
85
102
|
end
|
|
86
103
|
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CDC
|
|
4
|
+
module SolidQueue
|
|
5
|
+
# Delegates processor-job work to CDC downstream runtime primitives.
|
|
6
|
+
class DownstreamProcessor
|
|
7
|
+
# @return [Configuration]
|
|
8
|
+
attr_reader :configuration
|
|
9
|
+
|
|
10
|
+
# @param configuration [Configuration]
|
|
11
|
+
def initialize(configuration)
|
|
12
|
+
@configuration = configuration
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Process one normalized CDC work item.
|
|
16
|
+
#
|
|
17
|
+
# @param item [Object]
|
|
18
|
+
# @return [Object]
|
|
19
|
+
def process(item)
|
|
20
|
+
case configuration.downstream_runtime
|
|
21
|
+
when :direct
|
|
22
|
+
processor.process(item)
|
|
23
|
+
when :concurrent
|
|
24
|
+
process_with_runtime(concurrent_runtime, item)
|
|
25
|
+
when :parallel
|
|
26
|
+
process_with_runtime(parallel_runtime, item)
|
|
27
|
+
else
|
|
28
|
+
raise ConfigurationError, "unsupported downstream_runtime: #{configuration.downstream_runtime.inspect}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def processor
|
|
35
|
+
configuration.downstream_processor || raise(ConfigurationError, 'downstream_processor is required')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def process_with_runtime(runtime, item)
|
|
39
|
+
runtime.process(item)
|
|
40
|
+
ensure
|
|
41
|
+
runtime.shutdown
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def concurrent_runtime
|
|
45
|
+
require_runtime('cdc/concurrent', 'cdc-concurrent') unless defined?(CDC::Concurrent::Runtime)
|
|
46
|
+
CDC::Concurrent::Runtime.new(processor:, **configuration.downstream_options)
|
|
47
|
+
rescue LoadError => e
|
|
48
|
+
raise ConfigurationError, "cdc-concurrent is required for downstream_runtime :concurrent: #{e.message}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parallel_runtime
|
|
52
|
+
require_runtime('cdc/parallel', 'cdc-parallel') unless defined?(CDC::Parallel::Runtime)
|
|
53
|
+
CDC::Parallel::Runtime.new(processor:, **configuration.downstream_options)
|
|
54
|
+
rescue LoadError => e
|
|
55
|
+
raise ConfigurationError, "cdc-parallel is required for downstream_runtime :parallel: #{e.message}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def require_runtime(feature, gem_name)
|
|
59
|
+
require feature
|
|
60
|
+
rescue LoadError
|
|
61
|
+
raise LoadError, "install #{gem_name} and require #{feature}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -20,7 +20,12 @@ module CDC
|
|
|
20
20
|
# @param payload [Hash]
|
|
21
21
|
# @return [Object] process return value
|
|
22
22
|
def perform(payload)
|
|
23
|
-
|
|
23
|
+
event = EventSerializer.load_event(payload)
|
|
24
|
+
if SolidQueue.configuration.downstream_processor
|
|
25
|
+
return DownstreamProcessor.new(SolidQueue.configuration).process(event)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
process(event)
|
|
24
29
|
end
|
|
25
30
|
|
|
26
31
|
# Process a normalized CDC event payload.
|
data/lib/cdc/solid_queue.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative 'solid_queue/event_serializer'
|
|
|
6
6
|
require_relative 'solid_queue/checkpoint'
|
|
7
7
|
require_relative 'solid_queue/configuration'
|
|
8
8
|
require_relative 'solid_queue/enqueuer'
|
|
9
|
+
require_relative 'solid_queue/downstream_processor'
|
|
9
10
|
require_relative 'solid_queue/processor_job'
|
|
10
11
|
require_relative 'solid_queue/postgresql_stream'
|
|
11
12
|
require_relative 'solid_queue/runner'
|
data/sig/cdc/solid_queue.rbs
CHANGED
|
@@ -32,6 +32,22 @@ module Pgoutput
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
module CDC
|
|
35
|
+
module Concurrent
|
|
36
|
+
class Runtime
|
|
37
|
+
def initialize: (processor: untyped, **untyped options) -> void
|
|
38
|
+
def process: (untyped item) -> untyped
|
|
39
|
+
def shutdown: () -> untyped
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
module Parallel
|
|
44
|
+
class Runtime
|
|
45
|
+
def initialize: (processor: untyped, **untyped options) -> void
|
|
46
|
+
def process: (untyped item) -> untyped
|
|
47
|
+
def shutdown: () -> untyped
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
35
51
|
module SolidQueue
|
|
36
52
|
VERSION: String
|
|
37
53
|
|
|
@@ -54,6 +70,7 @@ module CDC
|
|
|
54
70
|
class Configuration
|
|
55
71
|
SUPPORTED_SOURCE: Symbol
|
|
56
72
|
ORDERING_KEYS: Array[Symbol]
|
|
73
|
+
DOWNSTREAM_RUNTIMES: Array[Symbol]
|
|
57
74
|
|
|
58
75
|
attr_accessor processor_job: untyped
|
|
59
76
|
attr_accessor queue: String
|
|
@@ -61,6 +78,9 @@ module CDC
|
|
|
61
78
|
attr_accessor ordering_key: Symbol
|
|
62
79
|
attr_accessor postgresql: Hash[Symbol, untyped]
|
|
63
80
|
attr_accessor checkpoint: untyped
|
|
81
|
+
attr_accessor downstream_processor: untyped
|
|
82
|
+
attr_accessor downstream_runtime: Symbol
|
|
83
|
+
attr_accessor downstream_options: Hash[Symbol, untyped]
|
|
64
84
|
|
|
65
85
|
def initialize: () -> void
|
|
66
86
|
def validate!: () -> true
|
|
@@ -73,6 +93,7 @@ module CDC
|
|
|
73
93
|
def validate_ordering_key!: () -> nil
|
|
74
94
|
def validate_postgresql!: () -> nil
|
|
75
95
|
def validate_checkpoint!: () -> nil
|
|
96
|
+
def validate_downstream!: () -> nil
|
|
76
97
|
end
|
|
77
98
|
|
|
78
99
|
class EventSerializer
|
|
@@ -116,6 +137,20 @@ module CDC
|
|
|
116
137
|
def ordering_value: (Hash[untyped, untyped] payload) -> untyped
|
|
117
138
|
end
|
|
118
139
|
|
|
140
|
+
class DownstreamProcessor
|
|
141
|
+
attr_reader configuration: Configuration
|
|
142
|
+
def initialize: (Configuration configuration) -> void
|
|
143
|
+
def process: (untyped item) -> untyped
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
def processor: () -> untyped
|
|
148
|
+
def process_with_runtime: (untyped runtime, untyped item) -> untyped
|
|
149
|
+
def concurrent_runtime: () -> untyped
|
|
150
|
+
def parallel_runtime: () -> untyped
|
|
151
|
+
def require_runtime: (String feature, String gem_name) -> untyped
|
|
152
|
+
end
|
|
153
|
+
|
|
119
154
|
module ProcessorJob
|
|
120
155
|
def self.included: (untyped base) -> void
|
|
121
156
|
def perform: (Hash[untyped, untyped] payload) -> untyped
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cdc-solid-queue
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ken C. Demanawa
|
|
@@ -93,6 +93,7 @@ files:
|
|
|
93
93
|
- lib/cdc/solid_queue/checkpoint.rb
|
|
94
94
|
- lib/cdc/solid_queue/cli.rb
|
|
95
95
|
- lib/cdc/solid_queue/configuration.rb
|
|
96
|
+
- lib/cdc/solid_queue/downstream_processor.rb
|
|
96
97
|
- lib/cdc/solid_queue/enqueuer.rb
|
|
97
98
|
- lib/cdc/solid_queue/error.rb
|
|
98
99
|
- lib/cdc/solid_queue/event_serializer.rb
|