sbmt-outbox 6.15.0 → 6.17.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89c324ebe083d7dece71ae19ee9011a8da91843b31997ddbde85722e9f8209a5
4
- data.tar.gz: fedd73ddecdfc54da1728bf19cdce13632e40ada5c94047a783d15a7e82370f1
3
+ metadata.gz: ad4860726f28975383df9bf2d522e994f80644dfe9a3d3476cf517ee90443bf4
4
+ data.tar.gz: 16925e325aeab7dc040a46940e34c7318f526bb39c633fb3dd7be53d187d6e96
5
5
  SHA512:
6
- metadata.gz: 671f178a2b39285be8adb846ce823652cf1c6f2a68f4ce7e7e327015ea69b22858f95bd8403a637bd38c8ebd981e1423341d420bd308f2fae0d8f9bf151550cb
7
- data.tar.gz: bea9bbb10ced61b77814e708528deada7058cf7afc8c90726d3d2c4068c619a1ddecb39e5caa836b9620dcfa9fda28cc20e3d4a083e8a83163f6146d9b28b00b
6
+ metadata.gz: c4a2fb0a085133946fa0a741545ab76f173b096d2c817deaac12511207f695e93e15c40fdda475ac747ccaf79c8bb9895c73ce5594afc7e71db7fc0a4427ebb4
7
+ data.tar.gz: 6baa8c4f01d13859ba3879de9c8f9a749251331045b0df35aee4a0dc52660e136a46653db416e5d9eaf78711864a79da6e3f788711b5474b46be1f62cb505d15
data/README.md CHANGED
@@ -273,6 +273,7 @@ default: &default
273
273
  delivered_min_retention_period: PT1H #optional, default: PT1H, for statuses: delivered, retention period for delivered items, https://en.wikipedia.org/wiki/ISO_8601#Durations
274
274
  deletion_batch_size: 1_000 #optional, default: 1_000
275
275
  deletion_sleep_time: 0.5 #optional, default: 0.5
276
+ deletion_time_window: PT4H #optional, default: PT4H, for statuses: delivered, retention period for delivered items, https://en.wikipedia.org/wiki/ISO_8601#Durations
276
277
  max_retries: 3 # default 0, the number of retries before the item will be marked as failed
277
278
  strict_order: false # optional, default
278
279
  transports: # transports section
@@ -353,6 +354,7 @@ inbox_items: # inbox items section
353
354
  delivered_min_retention_period: PT1H #optional, default: PT1H, for statuses: delivered, retention period for delivered items, https://en.wikipedia.org/wiki/ISO_8601#Durations
354
355
  deletion_batch_size: 1_000 #optional, default: 1_000
355
356
  deletion_sleep_time: 0.5 #optional, default: 0.5
357
+ deletion_time_window: PT4H #optional, default: PT4H, for statuses: delivered, retention period for delivered items, https://en.wikipedia.org/wiki/ISO_8601#Durations
356
358
  max_retries: 3 # default 0, the number of retries before the item will be marked as failed
357
359
  transports: # transports section
358
360
  import_order: # underscored transport class name
@@ -493,6 +495,7 @@ You can wrap item processing within middlewares. There are three types:
493
495
  - server middlewares – triggered inside a daemon; divided into two types:
494
496
  - batch middlewares – executed alongside a batch being fetched from the database
495
497
  - item middlewares – execute alongside an item during processing
498
+ - polling middlewares - execute with element during pooling
496
499
 
497
500
  The order of execution depends on the order specified in the outbox configuration:
498
501
 
@@ -580,6 +583,25 @@ class MyItemMiddleware
580
583
  end
581
584
  ```
582
585
 
586
+ Example of an polling middleware:
587
+
588
+ ```ruby
589
+ # config/initializers/outbox.rb
590
+ Rails.application.config.outbox.tap do |config|
591
+ config.polling_item_middlewares.push(
592
+ 'MyItemMiddleware'
593
+ )
594
+ end
595
+
596
+ # my_create_polling_middleware.rb
597
+ class MyPollingItemMiddleware
598
+ def call(item)
599
+ # your code
600
+ yield
601
+ # your code
602
+ end
603
+ end
604
+
583
605
  ## Tracing
584
606
 
585
607
  The gem is optionally integrated with OpenTelemetry. If your main application has `opentelemetry-*` gems, the tracing will be configured automatically.
@@ -102,45 +102,50 @@ module Sbmt
102
102
  # SELECT "items"."id"
103
103
  # FROM "items"
104
104
  # WHERE (
105
- # "items"."status" = 1 AND "items"."created_at" < '2023-05-01 00:00:00'
105
+ # "items"."status" IN (2) AND "items"."created_at" BETWEEN "2025-01-29 12:18:32.917836" AND "2025-01-29 12:18:32.927596" LIMIT 1000
106
106
  # )
107
- # LIMIT 1000
108
107
  # )
109
108
  def postgres_delete_in_batches(waterline_failed, waterline_delivered)
110
- table = item_class.arel_table
111
-
112
109
  status_delivered = item_class.statuses[:delivered]
113
110
  status_failed_discarded = item_class.statuses.values_at(:failed, :discarded)
114
111
 
115
- delete_items_in_batches(table, table[:status].eq(status_delivered).and(table[:created_at].lt(waterline_delivered)))
116
- delete_items_in_batches(table, table[:status].in(status_failed_discarded).and(table[:created_at].lt(waterline_failed)))
112
+ delete_items_in_batches_with_between(waterline_delivered, status_delivered)
113
+ delete_items_in_batches_with_between(waterline_failed, status_failed_discarded)
117
114
  end
118
115
 
119
- def delete_items_in_batches(table, condition)
120
- subquery = table
121
- .project(table[:id])
122
- .where(condition)
123
- .take(item_class.config.deletion_batch_size)
124
-
125
- delete_statement = Arel::Nodes::DeleteStatement.new
126
- delete_statement.relation = table
127
- delete_statement.wheres = [table[:id].in(subquery)]
116
+ def delete_items_in_batches_with_between(waterline, statuses)
117
+ table = item_class.arel_table
118
+ batch_size = item_class.config.deletion_batch_size
119
+ time_window = item_class.config.deletion_time_window
120
+ min_date = item_class.where(table[:status].in(statuses)).minimum(:created_at)
128
121
  deleted_count = nil
129
122
 
130
- loop do
131
- track_deleted_latency do
132
- deleted_count = item_class
133
- .connection
134
- .execute(delete_statement.to_sql)
135
- .cmd_tuples
136
- end
123
+ while min_date && min_date < waterline
124
+ max_date = [min_date + time_window, waterline].min
125
+
126
+ loop do
127
+ subquery = table
128
+ .project(table[:id])
129
+ .where(table[:status].in(statuses))
130
+ .where(table[:created_at].between(min_date..max_date))
131
+ .take(batch_size)
132
+
133
+ delete_statement = Arel::Nodes::DeleteStatement.new
134
+ delete_statement.relation = table
135
+ delete_statement.wheres = [table[:id].in(subquery)]
136
+
137
+ track_deleted_latency do
138
+ deleted_count = item_class.connection.execute(delete_statement.to_sql).cmd_tuples
139
+ end
137
140
 
138
- track_deleted_counter(deleted_count)
141
+ track_deleted_counter(deleted_count)
139
142
 
140
- logger.log_info("Deleted #{deleted_count} #{box_type} items for #{box_name} items")
141
- break if deleted_count == 0
142
- lock_timer.checkpoint!
143
- sleep(item_class.config.deletion_sleep_time)
143
+ logger.log_info("Deleted #{deleted_count} #{box_type} items for #{box_name} between #{min_date} and #{max_date}")
144
+ break if deleted_count < batch_size
145
+ lock_timer.checkpoint!
146
+ sleep(item_class.config.deletion_sleep_time) if deleted_count > 0
147
+ end
148
+ min_date = max_date
144
149
  end
145
150
  end
146
151
 
@@ -154,37 +159,43 @@ module Sbmt
154
159
  # This approach doesn't require a subquery, making it more straightforward.
155
160
  #
156
161
  # Example SQL generated for deletion:
157
- # DELETE FROM `items`
162
+ # DELETE FROM "items"
158
163
  # WHERE (
159
- # `items`.`status` = 1 AND `items`.`created_at` < '2023-05-01 00:00:00'
164
+ # "items"."status" IN (2) AND "items"."created_at" BETWEEN "2024-12-29 18:34:25.369234" AND "2024-12-29 22:34:25.369234" LIMIT 1000
160
165
  # )
161
- # LIMIT 1000
162
166
  def mysql_delete_in_batches(waterline_failed, waterline_delivered)
163
167
  status_delivered = item_class.statuses[:delivered]
164
168
  status_failed_discarded = [item_class.statuses.values_at(:failed, :discarded)]
165
169
 
166
- delete_items_in_batches_mysql(
167
- item_class.where(status: status_delivered, created_at: ...waterline_delivered)
168
- )
169
- delete_items_in_batches_mysql(
170
- item_class.where(status: status_failed_discarded).where(created_at: ...waterline_failed)
171
- )
170
+ delete_items_in_batches_with_between_mysql(waterline_delivered, status_delivered)
171
+ delete_items_in_batches_with_between_mysql(waterline_failed, status_failed_discarded)
172
172
  end
173
173
 
174
- def delete_items_in_batches_mysql(query)
174
+ def delete_items_in_batches_with_between_mysql(waterline, statuses)
175
+ batch_size = item_class.config.deletion_batch_size
176
+ time_window = item_class.config.deletion_time_window
177
+ min_date = item_class.where(status: statuses).minimum(:created_at)
175
178
  deleted_count = nil
176
179
 
177
- loop do
178
- track_deleted_latency do
179
- deleted_count = query.limit(item_class.config.deletion_batch_size).delete_all
180
- end
180
+ while min_date && min_date < waterline
181
+ max_date = [min_date + time_window, waterline].min
182
+
183
+ loop do
184
+ track_deleted_latency do
185
+ deleted_count = item_class
186
+ .where(status: statuses, created_at: min_date..max_date)
187
+ .limit(batch_size)
188
+ .delete_all
189
+ end
181
190
 
182
- track_deleted_counter(deleted_count)
191
+ track_deleted_counter(deleted_count)
183
192
 
184
- logger.log_info("Deleted #{deleted_count} #{box_type} items for #{box_name} items")
185
- break if deleted_count == 0
186
- lock_timer.checkpoint!
187
- sleep(item_class.config.deletion_sleep_time)
193
+ logger.log_info("Deleted #{deleted_count} #{box_type} items for #{box_name} between #{min_date} and #{max_date}")
194
+ break if deleted_count < batch_size
195
+ lock_timer.checkpoint!
196
+ sleep(item_class.config.deletion_sleep_time) if deleted_count > 0
197
+ end
198
+ min_date = max_date
188
199
  end
189
200
  end
190
201
 
@@ -60,6 +60,10 @@ module Sbmt
60
60
  @delivered_min_retention_period ||= ActiveSupport::Duration.parse(options[:delivered_min_retention_period] || "PT1H")
61
61
  end
62
62
 
63
+ def deletion_time_window
64
+ @deletion_time_window ||= ActiveSupport::Duration.parse(options[:deletion_time_window] || "PT4H")
65
+ end
66
+
63
67
  def max_retries
64
68
  @max_retries ||= (options[:max_retries] || 0).to_i
65
69
  end
@@ -64,6 +64,7 @@ module Sbmt
64
64
  c.item_process_middlewares = []
65
65
  c.create_item_middlewares = []
66
66
  c.create_batch_middlewares = []
67
+ c.polling_item_middlewares = []
67
68
 
68
69
  if defined?(::Sentry)
69
70
  c.batch_process_middlewares.push("Sbmt::Outbox::Middleware::Sentry::TracingBatchProcessMiddleware")
@@ -10,9 +10,9 @@ module Sbmt
10
10
  module Outbox
11
11
  module V2
12
12
  class Poller < BoxProcessor
13
- delegate :poller_config, :logger, to: "Sbmt::Outbox"
13
+ delegate :poller_config, :polling_item_middlewares, :logger, to: "Sbmt::Outbox"
14
14
  delegate :box_worker, to: "Yabeda"
15
- attr_reader :partitions_count, :lock_timeout, :regular_items_batch_size, :retryable_items_batch_size, :max_buffer_size, :max_batch_size, :throttler
15
+ attr_reader :partitions_count, :lock_timeout, :regular_items_batch_size, :retryable_items_batch_size, :max_buffer_size, :max_batch_size, :throttler, :middleware_builder
16
16
 
17
17
  def initialize(
18
18
  boxes,
@@ -35,6 +35,7 @@ module Sbmt
35
35
  super(boxes: boxes, threads_count: threads_count || poller_config.threads_count, name: "poller", redis: redis)
36
36
 
37
37
  @throttler = PollThrottler.build(throttler_tactic || poller_config.tactic || "default", self.redis, poller_config)
38
+ @middleware_builder = Middleware::Builder.new(polling_item_middlewares)
38
39
  end
39
40
 
40
41
  def throttle(worker_number, poll_task, result)
@@ -42,7 +43,7 @@ module Sbmt
42
43
  end
43
44
 
44
45
  def process_task(_worker_number, task)
45
- poll(task)
46
+ middleware_builder.call(task) { poll(task) }
46
47
  end
47
48
 
48
49
  private
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sbmt
4
4
  module Outbox
5
- VERSION = "6.15.0"
5
+ VERSION = "6.17.0"
6
6
  end
7
7
  end
data/lib/sbmt/outbox.rb CHANGED
@@ -170,5 +170,9 @@ module Sbmt
170
170
  def create_batch_middlewares
171
171
  @create_batch_middlewares ||= config.create_batch_middlewares.map(&:constantize)
172
172
  end
173
+
174
+ def polling_item_middlewares
175
+ @polling_item_middlewares ||= config.polling_item_middlewares.map(&:constantize)
176
+ end
173
177
  end
174
178
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sbmt-outbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.15.0
4
+ version: 6.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sbermarket Ruby-Platform Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-01-23 00:00:00.000000000 Z
11
+ date: 2025-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: connection_pool
@@ -629,7 +629,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
629
629
  - !ruby/object:Gem::Version
630
630
  version: '0'
631
631
  requirements: []
632
- rubygems_version: 3.1.6
632
+ rubygems_version: 3.5.21
633
633
  signing_key:
634
634
  specification_version: 4
635
635
  summary: Outbox service