cdc-solid-queue 0.1.1 → 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: 7c617d6e5257bc18cc38e242fe0ad0ac8b885de15fe6d6bdfbf5df5965924495
4
- data.tar.gz: 2f8a7b90d7edd8f39010d74821730fc6ad833774bb54136c0dc51068f87ff9f0
3
+ metadata.gz: a31a7915a2b6ba29afe3497045f7be40477b0388b89a0357a18bb77428f5701c
4
+ data.tar.gz: 4485d2cdf5c8137dc7a11503364c3f05a1d00691d83d0be18f3c5a194ba6c968
5
5
  SHA512:
6
- metadata.gz: f6c4cff53700e038fdfe3316c71972d61da38ca04937625143cc2790583e8cb64c7ee38d9f84da79f7425c04fdeef941c6dd65d41a78d0230cce7ba8c6ec55bb
7
- data.tar.gz: '068730758e551e38b2f2abc65d9b79c21406dc243f00d022e3aea8885e96ddbb01cceb02c9b765c0ca5859e63f5ffe422435b1ee0bb3517840ac54873ae5a0ee'
6
+ metadata.gz: 9f3a59fef64e5227fcf6ddab9ce45363221058baa8dbed1932e7eedd75620fe2f1b4f0382c97232ad6cfc4f15ac30044f3427e05c21aebec2b4283aafb255f27
7
+ data.tar.gz: 6aacc4886cd318f2ce12dfcb6164d3c059c8a37064ef0daced35dc016d40a4047e2054e11b4f0a6d84a3af01447b060452f9e1c6e3dae5f1f187c53df1532035
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
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
+
11
+ ## 0.1.2
12
+
13
+ - Minimal Rails app example.
14
+ - Local smoke tests.
15
+ - Enqueue overhead benchmark.
16
+
3
17
  ## 0.1.1
4
18
 
5
19
  - Initial implementation skeleton.
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:
@@ -72,6 +110,71 @@ The task wires `Pgoutput::Client::Runner`, `Pgoutput::RelationTracker`,
72
110
  `Pgoutput::Decoder`, and `Pgoutput::SourceAdapter::Cdc` into the
73
111
  `CDC::SolidQueue::Runner`.
74
112
 
113
+ See `examples/rails_app` for a minimal Rails-side setup with a Solid Queue job,
114
+ initializer, Railtie require, and a local PostgreSQL container configured for
115
+ logical replication.
116
+
117
+ ## Smoke Tests
118
+
119
+ Run local smoke tests without PostgreSQL or Rails:
120
+
121
+ ```bash
122
+ bundle exec rake smoke:local
123
+ ```
124
+
125
+ The smoke tests verify enqueue metadata, event rehydration, and
126
+ checkpoint-after-enqueue behavior.
127
+
128
+ ## Benchmark
129
+
130
+ Run the enqueue overhead benchmark:
131
+
132
+ ```bash
133
+ bundle exec rake benchmark:enqueue
134
+ ```
135
+
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.
139
+
140
+ Example local result on Ruby 3.4.9:
141
+
142
+ ```text
143
+ events=1000000 elapsed=15.7210s rate=63609.14 events/s
144
+ ```
145
+
146
+ This is an upper-bound microbenchmark for the Ruby-side enqueue translation
147
+ layer. It measures event serialization, queue and ordering metadata calculation,
148
+ and dispatch to a fake benchmark job. It does not measure real Solid Queue
149
+ database inserts, Rails job execution, PostgreSQL replication, pgoutput
150
+ decoding, network I/O, or checkpoint persistence.
151
+
152
+ In that run, `cdc-solid-queue` translated and dispatched about 63.6k synthetic
153
+ events per second, so real throughput will usually be dominated by Solid Queue
154
+ persistence, database latency, job execution cost, and CDC source throughput.
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
+
75
178
  ## MVP Checkpoint Rule
76
179
 
77
180
  A checkpoint advances after the Solid Queue job is durably inserted. Job execution success is handled by Solid Queue retry semantics.
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CDC
4
+ module SolidQueue
5
+ # Command helpers used by Rails tasks and executable entrypoints.
6
+ module CLI
7
+ class << self
8
+ # Start PostgreSQL CDC ingestion using the global configuration.
9
+ #
10
+ # @return [Integer] number of enqueued events when the stream exits
11
+ def start
12
+ configuration = CDC::SolidQueue.configuration
13
+ enqueuer = CDC::SolidQueue::Enqueuer.new(configuration)
14
+ stream = CDC::SolidQueue::PostgresqlStream.new(configuration)
15
+
16
+ CDC::SolidQueue::Runner.new(stream:, enqueuer:).start
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -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.
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails/railtie'
3
+ require_relative '../solid_queue'
4
+ begin
5
+ require 'rails/railtie'
6
+ rescue LoadError
7
+ Rails::Railtie
8
+ end
4
9
 
5
10
  module CDC
6
11
  module SolidQueue
@@ -3,10 +3,6 @@
3
3
  namespace :cdc_solid_queue do
4
4
  desc 'Start PostgreSQL CDC ingestion into Solid Queue'
5
5
  task start: :environment do
6
- configuration = CDC::SolidQueue.configuration
7
- enqueuer = CDC::SolidQueue::Enqueuer.new(configuration)
8
- stream = CDC::SolidQueue::PostgresqlStream.new(configuration)
9
-
10
- CDC::SolidQueue::Runner.new(stream:, enqueuer:).start
6
+ CDC::SolidQueue::CLI.start
11
7
  end
12
8
  end
@@ -3,6 +3,6 @@
3
3
  module CDC
4
4
  module SolidQueue
5
5
  # Current cdc-solid-queue gem version.
6
- VERSION = '0.1.1'
6
+ VERSION = '0.2.0'
7
7
  end
8
8
  end
@@ -6,9 +6,11 @@ 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'
13
+ require_relative 'solid_queue/cli'
12
14
 
13
15
  # Namespace for Change Data Capture integrations.
14
16
  module CDC
@@ -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
@@ -152,6 +187,10 @@ module CDC
152
187
  def metadata_value: (untyped metadata, Symbol name) -> untyped
153
188
  end
154
189
 
190
+ module CLI
191
+ def self.start: () -> Integer
192
+ end
193
+
155
194
  class Railtie < ::Rails::Railtie
156
195
  end
157
196
  end
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.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ken C. Demanawa
@@ -91,7 +91,9 @@ files:
91
91
  - README.md
92
92
  - lib/cdc/solid_queue.rb
93
93
  - lib/cdc/solid_queue/checkpoint.rb
94
+ - lib/cdc/solid_queue/cli.rb
94
95
  - lib/cdc/solid_queue/configuration.rb
96
+ - lib/cdc/solid_queue/downstream_processor.rb
95
97
  - lib/cdc/solid_queue/enqueuer.rb
96
98
  - lib/cdc/solid_queue/error.rb
97
99
  - lib/cdc/solid_queue/event_serializer.rb