rails-transactional-outbox 0.4.0 → 1.1.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: 20d98c7e305f01ecd8e54b92170a117c01a412d050acab60899835b99a0d7d0c
4
- data.tar.gz: 1658aadc54b3a01dd1e48274055befa210638df3b1fdc60bdd5a0bb2f858e1ae
3
+ metadata.gz: 7d730c069808243b30123a8dbd33c701bd778f42f2911544fabd9042125bb8ed
4
+ data.tar.gz: e9af76cb075537856068c8d677ebe646d81f10315cbd910806a2acc2be566344
5
5
  SHA512:
6
- metadata.gz: 75c6ef520d685476d27fc71f3c3bcac986248ab5d62addd2840238ed020136622cea9c4d20f0ade4fe6d693ffa3545531152d4a57465b72a6cb7a91112a2d5d0
7
- data.tar.gz: 9efa9ef99b4c610f89c9ae22d2b5bd0592c0535667d46636e69a8d219cb3e92dda648d315ccf56ca8cfe01f3ae9ccb8588d4f51742072a6309829a68da33614e
6
+ metadata.gz: b6ffcf4b7801ade3779ad221ab777fdaf5231eeadc3215ae65b11295799c646dc4124430281f9ce013c1d3207688a23de3f9fa35722a547a4751d965f95181b7
7
+ data.tar.gz: 4da8c107563ec5279805e6e10febcbf7f9a3f71872b28f5df77f897e9b807e634eabb6b182a4c1ad2d29a7a0cbcc75d68322c66c3adcc27d009b419492d76dc7
data/.circleci/config.yml CHANGED
@@ -2,7 +2,7 @@ version: 2.1
2
2
  jobs:
3
3
  build:
4
4
  docker:
5
- - image: ruby:2.7.2
5
+ - image: ruby:3.1.5
6
6
  steps:
7
7
  - checkout
8
8
  - run:
@@ -9,14 +9,14 @@ jobs:
9
9
  - uses: actions/checkout@v2
10
10
  - uses: ruby/setup-ruby@v1
11
11
  with:
12
- ruby-version: 2.7
12
+ ruby-version: 3.1
13
13
  bundler-cache: true
14
14
  - run: bundle exec rubocop
15
15
  rspec:
16
16
  strategy:
17
17
  fail-fast: false
18
18
  matrix:
19
- ruby: [ '2.7', '3.0', '3.1' ]
19
+ ruby: ['3.1', '3.2', '3.3']
20
20
  runs-on: ubuntu-latest
21
21
  env:
22
22
  DATABASE_URL: "postgresql://postgres:postgres@127.0.0.1:5432/rails-transactional-outbox-test"
data/.rubocop.yml CHANGED
@@ -9,7 +9,7 @@ inherit_mode:
9
9
 
10
10
  AllCops:
11
11
  NewCops: enable
12
- TargetRubyVersion: 2.7
12
+ TargetRubyVersion: 3.1
13
13
  Exclude:
14
14
  - "db/**/*"
15
15
  - "bin/**/*"
@@ -147,6 +147,7 @@ Lint/ConstantDefinitionInBlock:
147
147
  RSpec/AnyInstance:
148
148
  Exclude:
149
149
  - 'spec/rails_transactional_outbox/outbox_model_spec.rb'
150
+ - 'spec/rails_transactional_outbox/datadog_latency_reporter_job_spec.rb'
150
151
 
151
152
  RSpec/VerifiedDoubles:
152
153
  Exclude:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.1.0] - 2025-04-08
4
+
5
+ - Make gem compatible with Datadog gem 2.0
6
+
7
+ ## [1.0.0] - 2024-08-28
8
+
9
+ - [Feature] add latency tracking ability via Datadog
10
+ - [Breaking Change] Require Ruby >= 3.1
11
+
3
12
  ## [0.4.0] - 2024-01-25
4
13
 
5
14
  - add config option to specify causality keys limit
data/Gemfile CHANGED
@@ -8,7 +8,7 @@ gemspec
8
8
  gem "rake", "~> 13.0"
9
9
 
10
10
  gem "crypt_keeper", github: "jmazzi/crypt_keeper"
11
- gem "ddtrace"
11
+ gem "datadog"
12
12
  gem "pg"
13
13
  gem "redlock"
14
14
  gem "rspec", "~> 3.0"
data/Gemfile.lock CHANGED
@@ -9,12 +9,15 @@ GIT
9
9
  PATH
10
10
  remote: .
11
11
  specs:
12
- rails-transactional-outbox (0.4.0)
12
+ rails-transactional-outbox (1.1.0)
13
13
  activerecord (>= 5)
14
14
  activesupport (>= 3.2)
15
15
  concurrent-ruby
16
+ dogstatsd-ruby
16
17
  dry-monitor
17
18
  file-based-healthcheck
19
+ sidekiq (>= 5.0)
20
+ sidekiq-cron
18
21
  sigurd
19
22
  zeitwerk
20
23
 
@@ -33,48 +36,71 @@ GEM
33
36
  tzinfo (~> 2.0)
34
37
  ast (2.4.2)
35
38
  concurrent-ruby (1.1.10)
36
- ddtrace (1.2.0)
37
- debase-ruby_core_source (= 0.10.16)
38
- libddprof (~> 0.6.0.1.0)
39
- libddwaf (~> 1.3.0.2.0)
39
+ connection_pool (2.4.1)
40
+ cronex (0.15.0)
41
+ tzinfo
42
+ unicode (>= 0.4.4.5)
43
+ datadog (2.14.0)
44
+ datadog-ruby_core_source (~> 3.4)
45
+ libdatadog (~> 16.0.1.1.0)
46
+ libddwaf (~> 1.21.0.0.1)
47
+ logger
40
48
  msgpack
41
- debase-ruby_core_source (0.10.16)
49
+ datadog-ruby_core_source (3.4.0)
42
50
  diff-lcs (1.5.0)
43
- dry-configurable (1.0.1)
44
- dry-core (~> 1.0, < 2)
51
+ dogstatsd-ruby (5.6.6)
52
+ dry-configurable (1.3.0)
53
+ dry-core (~> 1.1)
45
54
  zeitwerk (~> 2.6)
46
- dry-core (1.0.0)
55
+ dry-core (1.1.0)
47
56
  concurrent-ruby (~> 1.0)
57
+ logger
48
58
  zeitwerk (~> 2.6)
49
- dry-events (1.0.1)
59
+ dry-events (1.1.0)
50
60
  concurrent-ruby (~> 1.0)
51
- dry-core (~> 1.0, < 2)
61
+ dry-core (~> 1.1)
52
62
  dry-monitor (1.0.1)
53
63
  dry-configurable (~> 1.0, < 2)
54
64
  dry-core (~> 1.0, < 2)
55
65
  dry-events (~> 1.0, < 2)
66
+ et-orbi (1.2.11)
67
+ tzinfo
56
68
  exponential-backoff (0.0.4)
57
- ffi (1.15.5)
58
- file-based-healthcheck (0.2.0)
69
+ ffi (1.17.1-arm64-darwin)
70
+ ffi (1.17.1-x86_64-darwin)
71
+ ffi (1.17.1-x86_64-linux-gnu)
72
+ file-based-healthcheck (0.3.0)
59
73
  activesupport (>= 3.2)
74
+ fugit (1.11.1)
75
+ et-orbi (~> 1, >= 1.2.11)
76
+ raabro (~> 1.4)
77
+ globalid (1.2.1)
78
+ activesupport (>= 6.1)
60
79
  i18n (1.12.0)
61
80
  concurrent-ruby (~> 1.0)
62
81
  json (2.6.2)
63
- libddprof (0.6.0.1.0)
64
- libddprof (0.6.0.1.0-x86_64-linux)
65
- libddwaf (1.3.0.2.0-x86_64-darwin)
82
+ libdatadog (16.0.1.1.0)
83
+ libdatadog (16.0.1.1.0-x86_64-linux)
84
+ libddwaf (1.21.0.0.1-arm64-darwin)
85
+ ffi (~> 1.0)
86
+ libddwaf (1.21.0.0.1-x86_64-darwin)
66
87
  ffi (~> 1.0)
67
- libddwaf (1.3.0.2.0-x86_64-linux)
88
+ libddwaf (1.21.0.0.1-x86_64-linux)
68
89
  ffi (~> 1.0)
90
+ logger (1.7.0)
69
91
  minitest (5.16.3)
70
- msgpack (1.5.3)
92
+ msgpack (1.8.0)
71
93
  parallel (1.22.1)
72
94
  parser (3.1.2.1)
73
95
  ast (~> 2.4.1)
74
96
  pg (1.4.3)
97
+ raabro (1.4.0)
98
+ rack (2.2.9)
75
99
  rainbow (3.1.1)
76
100
  rake (13.0.6)
77
101
  redis (4.8.0)
102
+ redis-namespace (1.11.0)
103
+ redis (>= 4)
78
104
  redlock (1.3.0)
79
105
  redis (>= 3.0.0, < 6.0)
80
106
  regexp_parser (2.5.0)
@@ -91,6 +117,11 @@ GEM
91
117
  rspec-mocks (3.11.1)
92
118
  diff-lcs (>= 1.2.0, < 2.0)
93
119
  rspec-support (~> 3.11.0)
120
+ rspec-sidekiq (5.0.0)
121
+ rspec-core (~> 3.0)
122
+ rspec-expectations (~> 3.0)
123
+ rspec-mocks (~> 3.0)
124
+ sidekiq (>= 5, < 8)
94
125
  rspec-support (3.11.0)
95
126
  rubocop (1.35.1)
96
127
  json (~> 2.3)
@@ -114,28 +145,41 @@ GEM
114
145
  ruby-progressbar (1.11.0)
115
146
  sentry-ruby (5.4.1)
116
147
  concurrent-ruby (~> 1.0, >= 1.0.2)
148
+ sidekiq (6.5.12)
149
+ connection_pool (>= 2.2.5, < 3)
150
+ rack (~> 2.0)
151
+ redis (>= 4.5.0, < 5)
152
+ sidekiq-cron (2.2.0)
153
+ cronex (>= 0.13.0)
154
+ fugit (~> 1.8, >= 1.11.1)
155
+ globalid (>= 1.0.1)
156
+ sidekiq (>= 6.5.0)
117
157
  sigurd (0.1.0)
118
158
  concurrent-ruby (~> 1)
119
159
  exponential-backoff
120
160
  timecop (0.9.5)
121
161
  tzinfo (2.0.5)
122
162
  concurrent-ruby (~> 1.0)
163
+ unicode (0.4.4.5)
123
164
  unicode-display_width (2.2.0)
124
- zeitwerk (2.6.12)
165
+ zeitwerk (2.7.2)
125
166
 
126
167
  PLATFORMS
168
+ arm64-darwin-22
127
169
  x86_64-darwin-18
128
170
  x86_64-darwin-21
129
171
  x86_64-linux
130
172
 
131
173
  DEPENDENCIES
132
174
  crypt_keeper!
133
- ddtrace
175
+ datadog
134
176
  pg
135
177
  rails-transactional-outbox!
136
178
  rake (~> 13.0)
179
+ redis-namespace
137
180
  redlock
138
181
  rspec (~> 3.0)
182
+ rspec-sidekiq
139
183
  rubocop
140
184
  rubocop-performance
141
185
  rubocop-rake
data/README.md CHANGED
@@ -46,6 +46,9 @@ Rails.application.config.to_prepare do
46
46
  config.outbox_entries_processor = `RailsTransactionalOutbox::OutboxEntriesProcessors::OrderedByCausalityKeyProcessor`.new # not required, defaults to RailsTransactionalOutbox::OutboxEntriesProcessors::NonOrderedProcessor.new
47
47
  config.outbox_entry_causality_key_resolver = ->(model) { model.tenant_id } # not required, defaults to a lambda returning nil. Needed when using `outbox_entry_causality_key_resolver`
48
48
  config.unprocessed_causality_keys_limit = 100_000 # not required, defaults to 10_000. Might be a good idea to decrease the value when you start experiencing OOMs - they are likely to be caused by fetching too many causality keys. It is likely to happen when you have huge amount of records to process.
49
+
50
+ config.datadog_statsd_client = Datadog::Statsd.new("localhost", 8125, namespace: "application_name.production") # needed only for latency tracking, defaults to `nil`
51
+ config.high_priority_sidekiq_queue = :critical # not required, defaults to `:rails_transactional_outbox_high_priority`
49
52
  end
50
53
  end
51
54
  ```
@@ -247,10 +250,40 @@ tartarus.register do |item|
247
250
  end
248
251
  ```
249
252
 
253
+ ### Outbox Processing Latency Tracking
254
+
255
+ It's highly recommended to tracking latency of processing outbox records defined as the difference between the `processed` and `created_at` timestamps.
256
+
257
+ ``` rb
258
+ RailsTransactionalOutbox.configure do |config|
259
+ config.datadog_statsd_client = Datadog::Statsd.new("localhost", 8125, namespace: "application_name.production") # required for latency tracking, defaults to `nil`
260
+ config.high_priority_sidekiq_queue = :critical # not required, defaults to `:rails_transactional_outbox_high_priority`
261
+ end
262
+ ```
263
+
264
+ You also need to add a job to the sidekiq-cron schedule that will run every 1 minute:
265
+
266
+ ``` rb
267
+ Sidekiq.configure_server do |config|
268
+ config.on(:startup) do
269
+ RailsTransactionalOutbox::DatadogLatencyReporterScheduler.new.add_to_schedule
270
+ end
271
+ end
272
+ ```
273
+
274
+ With this setup, you will have the following metrics available on DataDog:
275
+
276
+ - `"#{namespace}.rails_transactional_outbox.latency.minimum"`
277
+ - `"#{namespace}.rails_transactional_outbox.latency.maximum"`
278
+ - `"#{namespace}.rails_transactional_outbox.latency.average"`
279
+ - `"#{namespace}.rails_transactional_outbox.latency.highest_since_creation_date`
280
+
281
+
282
+
250
283
  ### Health Checks
251
284
 
252
285
 
253
- Then, Uou need to explicitly enable the health check (e.g. in the initializer):
286
+ You need to explicitly enable the health check (e.g. in the initializer):
254
287
 
255
288
  ``` rb
256
289
  RailsTransactionalOutbox.enable_outbox_worker_healthcheck
@@ -2,11 +2,15 @@
2
2
 
3
3
  class RailsTransactionalOutbox
4
4
  class Configuration
5
- attr_accessor :database_connection_provider, :logger, :outbox_model, :transaction_provider
5
+ attr_accessor :database_connection_provider, :logger, :outbox_model, :transaction_provider, :datadog_statsd_client
6
6
  attr_writer :error_handler, :transactional_outbox_worker_sleep_seconds,
7
7
  :transactional_outbox_worker_idle_delay_multiplier, :outbox_batch_size, :outbox_entries_processor,
8
8
  :lock_client, :lock_expiry_time, :outbox_entry_causality_key_resolver,
9
- :raise_not_found_model_error, :unprocessed_causality_keys_limit
9
+ :raise_not_found_model_error, :unprocessed_causality_keys_limit, :high_priority_sidekiq_queue
10
+
11
+ def self.high_priority_sidekiq_queue
12
+ :rails_transactional_outbox_high_priority
13
+ end
10
14
 
11
15
  def error_handler
12
16
  @error_handler || RailsTransactionalOutbox::ErrorHandlers::NullErrorHandler
@@ -61,5 +65,11 @@ class RailsTransactionalOutbox
61
65
 
62
66
  10_000
63
67
  end
68
+
69
+ def high_priority_sidekiq_queue
70
+ return @high_priority_sidekiq_queue if defined?(@high_priority_sidekiq_queue)
71
+
72
+ self.class.high_priority_sidekiq_queue
73
+ end
64
74
  end
65
75
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RailsTransactionalOutbox
4
+ class DatadogLatencyReporter
5
+ attr_reader :config
6
+ private :config
7
+
8
+ def initialize(config: RailsTransactionalOutbox.configuration)
9
+ @config = config
10
+ end
11
+
12
+ def report(latency: generate_latency)
13
+ datadog_statsd_client.gauge("rails_transactional_outbox.latency.minimum", latency.minimum)
14
+ datadog_statsd_client.gauge("rails_transactional_outbox.latency.maximum", latency.maximum)
15
+ datadog_statsd_client.gauge("rails_transactional_outbox.latency.average", latency.average)
16
+ datadog_statsd_client.gauge("rails_transactional_outbox.latency.highest_since_creation_date",
17
+ latency.highest_since_creation_date)
18
+ end
19
+
20
+ private
21
+
22
+ delegate :datadog_statsd_client, :datadog_statsd_prefix, to: :config
23
+
24
+ def generate_latency
25
+ RailsTransactionalOutbox::LatencyTracker.new.calculate
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RailsTransactionalOutbox
4
+ class DatadogLatencyReporterJob
5
+ include Sidekiq::Worker
6
+
7
+ sidekiq_options queue: RailsTransactionalOutbox::Configuration.high_priority_sidekiq_queue
8
+
9
+ def perform
10
+ RailsTransactionalOutbox::DatadogLatencyReporter.new.report
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RailsTransactionalOutbox
4
+ class DatadogLatencyReporterScheduler
5
+ JOB_NAME = "rails_transactional_outbox_datadog_latency_reporter_job"
6
+ EVERY_MINUTE_IN_CRON_SYNTAX = "* * * * *"
7
+ JOB_CLASS_NAME = "RailsTransactionalOutbox::DatadogLatencyReporterJob"
8
+ JOB_DESCRIPTION = "Collect latency metrics from rails-transactional-outbox and send them to Datadog"
9
+
10
+ private_constant :JOB_NAME, :EVERY_MINUTE_IN_CRON_SYNTAX, :JOB_CLASS_NAME, :JOB_DESCRIPTION
11
+
12
+ attr_reader :config
13
+ private :config
14
+
15
+ def initialize(config: RailsTransactionalOutbox.configuration)
16
+ @config = config
17
+ end
18
+
19
+ def add_to_schedule
20
+ find || create
21
+ end
22
+
23
+ private
24
+
25
+ def find
26
+ Sidekiq::Cron::Job.find(name: JOB_NAME)
27
+ end
28
+
29
+ def create
30
+ Sidekiq::Cron::Job.create(create_job_arguments)
31
+ end
32
+
33
+ def create_job_arguments
34
+ {
35
+ name: JOB_NAME,
36
+ cron: EVERY_MINUTE_IN_CRON_SYNTAX,
37
+ class: JOB_CLASS_NAME,
38
+ queue: config.high_priority_sidekiq_queue,
39
+ active_job: false,
40
+ description: JOB_DESCRIPTION,
41
+ date_as_argument: false
42
+ }
43
+ end
44
+
45
+ def every_minute_to_cron_syntax
46
+ EVERY_MINUTE_IN_CRON_SYNTAX
47
+ end
48
+ end
49
+ end
@@ -7,7 +7,7 @@ class RailsTransactionalOutbox
7
7
  private_constant :KEY_PREFIX, :TMP_DIR
8
8
 
9
9
  def self.check(hostname: ENV.fetch("HOSTNAME", nil), expiry_time_in_seconds: 120)
10
- new(hostname: hostname, expiry_time_in_seconds: expiry_time_in_seconds).check
10
+ new(hostname:, expiry_time_in_seconds:).check
11
11
  end
12
12
 
13
13
  attr_reader :hostname, :expiry_time_in_seconds
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RailsTransactionalOutbox
4
+ class LatencyTracker
5
+ LatencyTrackerResult = Struct.new(:minimum, :maximum, :average, :highest_since_creation_date)
6
+ private_constant :LatencyTrackerResult
7
+
8
+ attr_reader :config, :clock
9
+ private :config, :clock
10
+
11
+ def initialize(config: RailsTransactionalOutbox.configuration, clock: Time)
12
+ @config = config
13
+ @clock = clock
14
+ end
15
+
16
+ def calculate(interval: 1.minute)
17
+ records = outbox_model.processed_since(interval.ago)
18
+ latencies = records.map(&:processing_latency)
19
+
20
+ LatencyTrackerResult.new(
21
+ latencies.min.to_d,
22
+ latencies.max.to_d,
23
+ calculate_average(latencies),
24
+ calculate_highest_since_creation_date.to_d
25
+ )
26
+ end
27
+
28
+ private
29
+
30
+ delegate :outbox_model, to: :config
31
+
32
+ def calculate_average(latencies)
33
+ if latencies.any?
34
+ latencies.sum.to_d / latencies.size
35
+ else
36
+ 0
37
+ end
38
+ end
39
+
40
+ def calculate_highest_since_creation_date
41
+ minimum_created_at_from_not_processed = outbox_model.not_processed.minimum(:created_at) or return 0
42
+ clock.current - minimum_created_at_from_not_processed
43
+ end
44
+ end
45
+ end
@@ -9,8 +9,8 @@ class RailsTransactionalOutbox
9
9
  @config = config
10
10
  end
11
11
 
12
- def call(&block)
13
- config.outbox_entries_processor.call(&block)
12
+ def call(&)
13
+ config.outbox_entries_processor.call(&)
14
14
  end
15
15
  end
16
16
  end
@@ -10,17 +10,17 @@ class RailsTransactionalOutbox
10
10
  @config = config
11
11
  end
12
12
 
13
- def call(&block)
13
+ def call(&)
14
14
  return [] unless outbox_model.any_records_to_process?
15
15
 
16
- execute(&block)
16
+ execute(&)
17
17
  end
18
18
 
19
19
  private
20
20
 
21
21
  delegate :outbox_model, :outbox_batch_size, to: :config
22
22
 
23
- def execute(&_block)
23
+ def execute(&)
24
24
  raise "implement me"
25
25
  end
26
26
 
@@ -8,10 +8,10 @@ class RailsTransactionalOutbox
8
8
  delegate :transaction_provider, to: :config
9
9
  delegate :transaction, to: :transaction_provider
10
10
 
11
- def execute(&block)
11
+ def execute(&)
12
12
  transaction do
13
13
  outbox_model.fetch_processable(outbox_batch_size).to_a.tap do |records_to_process|
14
- process_records(records_to_process, &block)
14
+ process_records(records_to_process, &)
15
15
  end
16
16
  end
17
17
  end
@@ -7,12 +7,12 @@ class RailsTransactionalOutbox
7
7
 
8
8
  delegate :lock_client, :lock_expiry_time, to: :config
9
9
 
10
- def execute(&block)
10
+ def execute(&)
11
11
  unprocessed_causality_keys.each_with_object([]) do |causality_key, processed_records|
12
12
  lock_client.lock(lock_name(causality_key), lock_expiry_time) do |locked|
13
13
  next unless locked
14
14
 
15
- processed_records.concat(fetch_records(causality_key).tap { |records| process_records(records, &block) })
15
+ processed_records.concat(fetch_records(causality_key).tap { |records| process_records(records, &) })
16
16
  end
17
17
  end
18
18
  end
@@ -14,7 +14,7 @@ class RailsTransactionalOutbox
14
14
 
15
15
  scope :fetch_processable_for_causality_key, lambda { |batch_size, causality_key|
16
16
  processable_now
17
- .where(causality_key: causality_key)
17
+ .where(causality_key:)
18
18
  .order(created_at: :asc)
19
19
  .limit(batch_size)
20
20
  }
@@ -24,6 +24,9 @@ class RailsTransactionalOutbox
24
24
  .where("retry_at IS NULL OR retry_at <= ?", Time.current)
25
25
  }
26
26
 
27
+ scope :processed_since, ->(time) { where("processed_at >= ?", time) }
28
+ scope :not_processed, -> { where(processed_at: nil) }
29
+
27
30
  def self.unprocessed_causality_keys(limit: RailsTransactionalOutbox.configuration.unprocessed_causality_keys_limit)
28
31
  processable_now
29
32
  .select("causality_key")
@@ -96,5 +99,11 @@ class RailsTransactionalOutbox
96
99
  rescue ActiveRecord::RecordNotFound
97
100
  model_klass.new(id: resource_id) if RailsTransactionalOutbox::EventType.new(event_type).destroy?
98
101
  end
102
+
103
+ def processing_latency
104
+ return unless processed?
105
+
106
+ processed_at - created_at
107
+ end
99
108
  end
100
109
  end
@@ -29,20 +29,20 @@ class RailsTransactionalOutbox
29
29
  reliable_after_commit_callbacks << ReliableCallback.new(callback_proc, final_options)
30
30
  end
31
31
 
32
- def self.reliable_after_create_commit(method_name = NOT_PROVIDED, options = {}, &block)
33
- reliable_after_commit(method_name, options.merge(on: :create), &block)
32
+ def self.reliable_after_create_commit(method_name = NOT_PROVIDED, options = {}, &)
33
+ reliable_after_commit(method_name, options.merge(on: :create), &)
34
34
  end
35
35
 
36
- def self.reliable_after_update_commit(method_name = NOT_PROVIDED, options = {}, &block)
37
- reliable_after_commit(method_name, options.merge(on: :update), &block)
36
+ def self.reliable_after_update_commit(method_name = NOT_PROVIDED, options = {}, &)
37
+ reliable_after_commit(method_name, options.merge(on: :update), &)
38
38
  end
39
39
 
40
- def self.reliable_after_destroy_commit(method_name = NOT_PROVIDED, options = {}, &block)
41
- reliable_after_commit(method_name, options.merge(on: :destroy), &block)
40
+ def self.reliable_after_destroy_commit(method_name = NOT_PROVIDED, options = {}, &)
41
+ reliable_after_commit(method_name, options.merge(on: :destroy), &)
42
42
  end
43
43
 
44
- def self.reliable_after_save_commit(method_name = NOT_PROVIDED, options = {}, &block)
45
- reliable_after_commit(method_name, options.merge(on: %i[create update]), &block)
44
+ def self.reliable_after_save_commit(method_name = NOT_PROVIDED, options = {}, &)
45
+ reliable_after_commit(method_name, options.merge(on: %i[create update]), &)
46
46
  end
47
47
 
48
48
  alias_method :original_previous_changes, :previous_changes
@@ -11,7 +11,7 @@ class RailsTransactionalOutbox
11
11
  end
12
12
 
13
13
  def trace(event_name)
14
- tracer.trace(event_name, span_type: "worker", service: self.class.service_name,
14
+ tracer.trace(event_name, span_type_key => "worker", service: self.class.service_name,
15
15
  on_error: error_handler) do |_span|
16
16
  yield
17
17
  end
@@ -27,6 +27,14 @@ class RailsTransactionalOutbox
27
27
  end
28
28
  end
29
29
 
30
+ def span_type_key
31
+ if defined?(DDTrace)
32
+ :span_type
33
+ else
34
+ :type
35
+ end
36
+ end
37
+
30
38
  def error_handler
31
39
  ->(span, error) { span.set_error(error) }
32
40
  end
@@ -3,5 +3,5 @@
3
3
  class RailsTransactionalOutbox
4
4
  module Version
5
5
  end
6
- VERSION = "0.4.0"
6
+ VERSION = "1.1.0"
7
7
  end
@@ -6,6 +6,9 @@ require "dry-monitor"
6
6
  require "sigurd"
7
7
  require "concurrent-ruby"
8
8
  require "file-based-healthcheck"
9
+ require "datadog/statsd"
10
+ require "sidekiq"
11
+ require "sidekiq-cron"
9
12
 
10
13
  class RailsTransactionalOutbox
11
14
  def self.loader
@@ -6,6 +6,6 @@ namespace :rails_transactional_outbox do
6
6
  $stdout.sync = true
7
7
  Rails.logger.info("Running rails_transactional_outbox:worker rake task.")
8
8
  threads_number = ENV.fetch("RAILS_TRANSACTIONAL_OUTBOX_THREADS_NUMBER", 1).to_i
9
- RailsTransactionalOutbox.start_outbox_worker(threads_number: threads_number)
9
+ RailsTransactionalOutbox.start_outbox_worker(threads_number:)
10
10
  end
11
11
  end
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = "An implementation of transactional outbox pattern to be used with Rails."
13
13
  spec.homepage = "https://github.com/BookingSync/rails-transactional-outbox"
14
14
  spec.license = "MIT"
15
- spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.1.0")
16
16
 
17
17
  # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
18
18
 
@@ -33,11 +33,17 @@ Gem::Specification.new do |spec|
33
33
  spec.add_dependency "activerecord", ">= 5"
34
34
  spec.add_dependency "activesupport", ">= 3.2"
35
35
  spec.add_dependency "concurrent-ruby"
36
+ spec.add_dependency "dogstatsd-ruby"
36
37
  spec.add_dependency "dry-monitor"
37
38
  spec.add_dependency "file-based-healthcheck"
39
+ spec.add_dependency "sidekiq", ">= 5.0"
40
+ spec.add_dependency "sidekiq-cron"
38
41
  spec.add_dependency "sigurd"
39
42
  spec.add_dependency "zeitwerk"
40
43
 
44
+ spec.add_development_dependency "redis-namespace"
45
+ spec.add_development_dependency "rspec-sidekiq"
46
+
41
47
  # For more information and examples about making a new gem, checkout our
42
48
  # guide at: https://bundler.io/guides/creating_gem.html
43
49
  spec.metadata["rubygems_mfa_required"] = "true"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-transactional-outbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karol Galanciak
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-25 00:00:00.000000000 Z
11
+ date: 2025-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dogstatsd-ruby
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: dry-monitor
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,34 @@ dependencies:
80
94
  - - ">="
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: sidekiq
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '5.0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '5.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sidekiq-cron
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
83
125
  - !ruby/object:Gem::Dependency
84
126
  name: sigurd
85
127
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +150,34 @@ dependencies:
108
150
  - - ">="
109
151
  - !ruby/object:Gem::Version
110
152
  version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: redis-namespace
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rspec-sidekiq
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
111
181
  description: An implementation of transactional outbox pattern to be used with Rails.
112
182
  email:
113
183
  - karol.galanciak@gmail.com
@@ -133,11 +203,15 @@ files:
133
203
  - lib/rails-transactional-outbox.rb
134
204
  - lib/rails_transactional_outbox.rb
135
205
  - lib/rails_transactional_outbox/configuration.rb
206
+ - lib/rails_transactional_outbox/datadog_latency_reporter.rb
207
+ - lib/rails_transactional_outbox/datadog_latency_reporter_job.rb
208
+ - lib/rails_transactional_outbox/datadog_latency_reporter_scheduler.rb
136
209
  - lib/rails_transactional_outbox/error_handlers.rb
137
210
  - lib/rails_transactional_outbox/error_handlers/null_error_handler.rb
138
211
  - lib/rails_transactional_outbox/event_type.rb
139
212
  - lib/rails_transactional_outbox/exponential_backoff.rb
140
213
  - lib/rails_transactional_outbox/health_check.rb
214
+ - lib/rails_transactional_outbox/latency_tracker.rb
141
215
  - lib/rails_transactional_outbox/monitor.rb
142
216
  - lib/rails_transactional_outbox/null_lock_client.rb
143
217
  - lib/rails_transactional_outbox/outbox_entries_processor.rb
@@ -179,14 +253,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
179
253
  requirements:
180
254
  - - ">="
181
255
  - !ruby/object:Gem::Version
182
- version: 2.7.0
256
+ version: 3.1.0
183
257
  required_rubygems_version: !ruby/object:Gem::Requirement
184
258
  requirements:
185
259
  - - ">="
186
260
  - !ruby/object:Gem::Version
187
261
  version: '0'
188
262
  requirements: []
189
- rubygems_version: 3.3.15
263
+ rubygems_version: 3.4.19
190
264
  signing_key:
191
265
  specification_version: 4
192
266
  summary: An implementation of transactional outbox pattern to be used with Rails.