sbmt-outbox 6.11.0 → 6.13.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: 9e410e7a4507b5470d10986ac8d9949c4c9460a6773a97063b0287d9e777c4d9
4
- data.tar.gz: ef17da6564faeb5f17cee5073cbecebeca083a555a9c046d0794f210f1a1b414
3
+ metadata.gz: 47ab689de26112df54d99c701fad3c6f492096173de831fc134318afa91c6662
4
+ data.tar.gz: b11e20111becd1d58cfca72a98d62adeb1b7573c0c093081b086331d90cc7b04
5
5
  SHA512:
6
- metadata.gz: 1e5fa1c14bab5bee90e4f5c664c22cdc5e8bea8fcfca391a3eb967ddf1c3b57b96080ab147cf366e5cbc9d2add8d486b8b2ca5ebfe8c9e96d0e989ab27ae685d
7
- data.tar.gz: c47678da77b2f59787dd64cd162974b7cd4f6954fc2fafae54614b930fcf44068498491aaddae77d99b86f50dc67637b2090ae7d155a844ca193b9f011cf7189
6
+ metadata.gz: 6df07282a29bf4468399747b2443965c35bcbc8590bcb5b6613fdad8c255cad9296dfdf9662399617f15ce39cd589e6f022a4da3c8611ee9d712714a10d36fcf
7
+ data.tar.gz: 0f14e1ed6c00ed9fd2c4fd4e9ff225330b507f0a9fbef4c39a32e86eeb0a7bebbf194a2b4040f5b4e2813b8438f27bb78a9a024b16784397ff0fe712143b505f
data/README.md CHANGED
@@ -267,7 +267,8 @@ default: &default
267
267
  outbox_items: # outbox items section
268
268
  my_outbox_item: # underscored model class name
269
269
  owner: my_outbox_item_team # optional, used in Yabeda metrics
270
- retention: P1W # retention period, https://en.wikipedia.org/wiki/ISO_8601#Durations
270
+ retention: P1W # for statuses: failed and discarded, retention period, https://en.wikipedia.org/wiki/ISO_8601#Durations
271
+ retention_delivered_items: PT6H # for statuses: delivered, retention period for delivered items, https://en.wikipedia.org/wiki/ISO_8601#Durations
271
272
  max_retries: 3 # default 0, the number of retries before the item will be marked as failed
272
273
  strict_order: false # optional, default
273
274
  transports: # transports section
@@ -342,7 +343,8 @@ end
342
343
  inbox_items: # inbox items section
343
344
  my_inbox_item: # underscored model class name
344
345
  owner: my_inbox_item_team # optional, used in Yabeda metrics
345
- retention: P1W # retention period, https://en.wikipedia.org/wiki/ISO_8601#Durations
346
+ retention: P1W # for statuses: failed and discarded, retention period, https://en.wikipedia.org/wiki/ISO_8601#Durations
347
+ retention_delivered_items: PT6H # for statuses: delivered, retention period for delivered items, https://en.wikipedia.org/wiki/ISO_8601#Durations
346
348
  max_retries: 3 # default 0, the number of retries before the item will be marked as failed
347
349
  transports: # transports section
348
350
  import_order: # underscored transport class name
@@ -13,7 +13,8 @@ module Sbmt
13
13
  class << self
14
14
  def enqueue
15
15
  item_classes.each do |item_class|
16
- perform_later(item_class.to_s)
16
+ delay = rand(15).seconds
17
+ set(wait: delay).perform_later(item_class.to_s)
17
18
  end
18
19
  end
19
20
 
@@ -41,12 +42,13 @@ module Sbmt
41
42
 
42
43
  lock_manager.lock("#{self.class.name}:#{item_class_name}:lock", LOCK_TTL) do |locked|
43
44
  if locked
44
- duration = item_class.config.retention
45
+ duration_failed = item_class.config.retention
46
+ duration_delivered = item_class.config.retention_delivered_items
45
47
 
46
- validate_retention!(duration)
48
+ validate_retention!(duration_failed)
47
49
 
48
50
  logger.with_tags(box_type: box_type, box_name: box_name) do
49
- delete_stale_items(Time.current - duration)
51
+ delete_stale_items(Time.current - duration_failed, Time.current - duration_delivered)
50
52
  end
51
53
  else
52
54
  logger.log_info("Failed to acquire lock #{self.class.name}:#{item_class_name}")
@@ -58,25 +60,25 @@ module Sbmt
58
60
 
59
61
  private
60
62
 
61
- def validate_retention!(duration)
62
- return if duration >= MIN_RETENTION_PERIOD
63
+ def validate_retention!(duration_failed)
64
+ return if duration_failed >= MIN_RETENTION_PERIOD
63
65
 
64
66
  raise "Retention period for #{box_name} must be longer than #{MIN_RETENTION_PERIOD.inspect}"
65
67
  end
66
68
 
67
- def delete_stale_items(waterline)
68
- logger.log_info("Start deleting #{box_type} items for #{box_name} older than #{waterline}")
69
+ def delete_stale_items(waterline_failed, waterline_delivered)
70
+ logger.log_info("Start deleting #{box_type} items for #{box_name} older than: failed and discarded items #{waterline_failed} and delivered items #{waterline_delivered}")
69
71
 
70
72
  case database_type
71
73
  when :postgresql
72
- postgres_delete_in_batches(waterline)
74
+ postgres_delete_in_batches(waterline_failed, waterline_delivered)
73
75
  when :mysql
74
- mysql_delete_in_batches(waterline)
76
+ mysql_delete_in_batches(waterline_failed, waterline_delivered)
75
77
  else
76
78
  raise "Unsupported database type"
77
79
  end
78
80
 
79
- logger.log_info("Successfully deleted #{box_type} items for #{box_name} older than #{waterline}")
81
+ logger.log_info("Successfully deleted #{box_type} items for #{box_name} older than: failed and discarded items #{waterline_failed} and delivered items #{waterline_delivered}")
80
82
  end
81
83
 
82
84
  # Deletes stale items from PostgreSQL database in batches
@@ -90,12 +92,22 @@ module Sbmt
90
92
  # WHERE "items"."id" IN (
91
93
  # SELECT "items"."id"
92
94
  # FROM "items"
93
- # WHERE "items"."created_at" < '2023-05-01 00:00:00'
95
+ # WHERE (
96
+ # "items"."status" = 1 AND "items"."created_at" < '2023-05-01 00:00:00'
97
+ # )
94
98
  # LIMIT 1000
95
99
  # )
96
- def postgres_delete_in_batches(waterline)
100
+ def postgres_delete_in_batches(waterline_failed, waterline_delivered)
97
101
  table = item_class.arel_table
98
- condition = table[:created_at].lt(waterline)
102
+
103
+ status_delivered = item_class.statuses[:delivered]
104
+ status_failed_discarded = item_class.statuses.values_at(:failed, :discarded)
105
+
106
+ delete_items_in_batches(table, table[:status].eq(status_delivered).and(table[:created_at].lt(waterline_delivered)))
107
+ delete_items_in_batches(table, table[:status].in(status_failed_discarded).and(table[:created_at].lt(waterline_failed)))
108
+ end
109
+
110
+ def delete_items_in_batches(table, condition)
99
111
  subquery = table
100
112
  .project(table[:id])
101
113
  .where(condition)
@@ -104,12 +116,17 @@ module Sbmt
104
116
  delete_statement = Arel::Nodes::DeleteStatement.new
105
117
  delete_statement.relation = table
106
118
  delete_statement.wheres = [table[:id].in(subquery)]
119
+ deleted_count = nil
107
120
 
108
121
  loop do
109
- deleted_count = item_class
110
- .connection
111
- .execute(delete_statement.to_sql)
112
- .cmd_tuples
122
+ track_deleted_latency do
123
+ deleted_count = item_class
124
+ .connection
125
+ .execute(delete_statement.to_sql)
126
+ .cmd_tuples
127
+ end
128
+
129
+ track_deleted_counter(deleted_count)
113
130
 
114
131
  logger.log_info("Deleted #{deleted_count} #{box_type} items for #{box_name} items")
115
132
  break if deleted_count == 0
@@ -129,14 +146,31 @@ module Sbmt
129
146
  #
130
147
  # Example SQL generated for deletion:
131
148
  # DELETE FROM `items`
132
- # WHERE `items`.`created_at` < '2023-05-01 00:00:00'
149
+ # WHERE (
150
+ # `items`.`status` = 1 AND `items`.`created_at` < '2023-05-01 00:00:00'
151
+ # )
133
152
  # LIMIT 1000
134
- def mysql_delete_in_batches(waterline)
153
+ def mysql_delete_in_batches(waterline_failed, waterline_delivered)
154
+ status_delivered = item_class.statuses[:delivered]
155
+ status_failed_discarded = [item_class.statuses.values_at(:failed, :discarded)]
156
+
157
+ delete_items_in_batches_mysql(
158
+ item_class.where(status: status_delivered, created_at: ...waterline_delivered)
159
+ )
160
+ delete_items_in_batches_mysql(
161
+ item_class.where(status: status_failed_discarded).where(created_at: ...waterline_failed)
162
+ )
163
+ end
164
+
165
+ def delete_items_in_batches_mysql(query)
166
+ deleted_count = nil
167
+
135
168
  loop do
136
- deleted_count = item_class
137
- .where(created_at: ...waterline)
138
- .limit(BATCH_SIZE)
139
- .delete_all
169
+ track_deleted_latency do
170
+ deleted_count = query.limit(BATCH_SIZE).delete_all
171
+ end
172
+
173
+ track_deleted_counter(deleted_count)
140
174
 
141
175
  logger.log_info("Deleted #{deleted_count} #{box_type} items for #{box_name} items")
142
176
  break if deleted_count == 0
@@ -156,6 +190,22 @@ module Sbmt
156
190
  :unknown
157
191
  end
158
192
  end
193
+
194
+ def track_deleted_counter(deleted_count)
195
+ ::Yabeda
196
+ .outbox
197
+ .deleted_counter
198
+ .increment({box_type: box_type, box_name: box_name}, by: deleted_count)
199
+ end
200
+
201
+ def track_deleted_latency
202
+ ::Yabeda
203
+ .outbox
204
+ .delete_latency
205
+ .measure({box_type: box_type, box_name: box_name}) do
206
+ yield
207
+ end
208
+ end
159
209
  end
160
210
  end
161
211
  end
@@ -37,6 +37,13 @@ module Sbmt
37
37
  @retention ||= ActiveSupport::Duration.parse(options[:retention] || "P1W")
38
38
  end
39
39
 
40
+ def retention_delivered_items
41
+ @retention_delivered_items ||= begin
42
+ value = options[:retention_delivered_items] || retention
43
+ value.is_a?(String) ? ActiveSupport::Duration.parse(value) : value
44
+ end
45
+ end
46
+
40
47
  def max_retries
41
48
  @max_retries ||= (options[:max_retries] || 0).to_i
42
49
  end
@@ -43,6 +43,16 @@ Yabeda.configure do
43
43
  unit: :seconds,
44
44
  buckets: [0.5, 1, 2.5, 5, 10, 15, 20, 30, 45, 60, 300].freeze,
45
45
  comment: "A histogram outbox process latency"
46
+
47
+ histogram :delete_latency,
48
+ tags: %i[box_type box_name],
49
+ unit: :seconds,
50
+ buckets: [0.005, 0.01, 0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 20, 30].freeze,
51
+ comment: "A histogram for outbox/inbox deletion latency"
52
+
53
+ counter :deleted_counter,
54
+ tags: %i[box_type box_name],
55
+ comment: "A counter for the number of deleted outbox/inbox items"
46
56
  end
47
57
 
48
58
  group :box_worker do
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sbmt
4
4
  module Outbox
5
- VERSION = "6.11.0"
5
+ VERSION = "6.13.0"
6
6
  end
7
7
  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.11.0
4
+ version: 6.13.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: 2024-12-27 00:00:00.000000000 Z
11
+ date: 2025-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: connection_pool