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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2a93bea7c30ca2f1c54bb53b00057967b662b16c8ca3a40bb5afb6c73ccb736
4
- data.tar.gz: 1d413c1aeae90544b9430864d7cb753e1b510115a571d4640bf5c6a7c09caf20
3
+ metadata.gz: a31a7915a2b6ba29afe3497045f7be40477b0388b89a0357a18bb77428f5701c
4
+ data.tar.gz: 4485d2cdf5c8137dc7a11503364c3f05a1d00691d83d0be18f3c5a194ba6c968
5
5
  SHA512:
6
- metadata.gz: 50b5a0edeec10b23ef6eeb124bb39e77d31a0148aa25128264936678c4dbd7b995f1d3e1cae3341bd1458c5ec687d1a579f291c0d714ebdb3c9de2ff5b790c08
7
- data.tar.gz: c73e0438419bc58f93f0fe641df7fbefc266f39c60d322ceea0bad96e1524afa0197ce89d12e857bab2898f55513d29f98f3f0228fc526037046e0ac739e1a61
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
- process(EventSerializer.load_event(payload))
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.
@@ -3,6 +3,6 @@
3
3
  module CDC
4
4
  module SolidQueue
5
5
  # Current cdc-solid-queue gem version.
6
- VERSION = '0.1.2'
6
+ VERSION = '0.2.0'
7
7
  end
8
8
  end
@@ -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'
@@ -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.1.2
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