ruby_event_store-outbox 0.1.0 → 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 +4 -4
- data/lib/ruby_event_store/outbox/cleanup_strategies/none.rb +2 -1
- data/lib/ruby_event_store/outbox/cleanup_strategies.rb +1 -1
- data/lib/ruby_event_store/outbox/cli.rb +24 -26
- data/lib/ruby_event_store/outbox/configuration.rb +8 -8
- data/lib/ruby_event_store/outbox/consumer.rb +11 -8
- data/lib/ruby_event_store/outbox/metrics/influx.rb +5 -6
- data/lib/ruby_event_store/outbox/metrics/null.rb +4 -2
- data/lib/ruby_event_store/outbox/metrics/test.rb +1 -1
- data/lib/ruby_event_store/outbox/repository.rb +39 -47
- data/lib/ruby_event_store/outbox/runner.rb +1 -0
- data/lib/ruby_event_store/outbox/sidekiq_processor.rb +1 -1
- data/lib/ruby_event_store/outbox/sidekiq_producer.rb +1 -1
- data/lib/ruby_event_store/outbox/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67529756ee35bbaf8447ed5ed8afbc6b6e80e2eaae62a1c396492fe4f6fcb7e0
|
4
|
+
data.tar.gz: 617091c19ad0d7bd1a688178740b43aa41165ed7cd9a5a371da33ff7734d3873
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ec5edfc468a99b05f38a9fc6f2db83bb30f2fd86a1c995f98476dee542a99b8f811a83e46e6915b20873eb1c291e5dc7f95a996542c3017a026fbad93eeb383
|
7
|
+
data.tar.gz: 63ee434baae965c530ac947820d0b1f5d2cf682268da3b7c377dde42eedd8eea91b3d56dbd6a96a0e10ec288d4ce6bedbb3cbac9bc874ea03642572f977f7b2a
|
@@ -32,10 +32,9 @@ module RubyEventStore
|
|
32
32
|
.new do |option_parser|
|
33
33
|
option_parser.banner = "Usage: res_outbox [options]"
|
34
34
|
|
35
|
-
option_parser.on(
|
36
|
-
|
37
|
-
|
38
|
-
) { |database_url| options.database_url = database_url }
|
35
|
+
option_parser.on("--database-url=DATABASE_URL", "Database where outbox table is stored") do |database_url|
|
36
|
+
options.database_url = database_url
|
37
|
+
end
|
39
38
|
|
40
39
|
option_parser.on("--redis-url=REDIS_URL", "URL to redis database") do |redis_url|
|
41
40
|
options.redis_url = redis_url
|
@@ -44,25 +43,25 @@ module RubyEventStore
|
|
44
43
|
option_parser.on(
|
45
44
|
"--log-level=LOG_LEVEL",
|
46
45
|
%i[fatal error warn info debug],
|
47
|
-
"Logging level, one of: fatal, error, warn, info, debug. Default: warn"
|
46
|
+
"Logging level, one of: fatal, error, warn, info, debug. Default: warn",
|
48
47
|
) { |log_level| options.log_level = log_level.to_sym }
|
49
48
|
|
50
49
|
option_parser.on(
|
51
50
|
"--message-format=FORMAT",
|
52
51
|
["sidekiq5"],
|
53
|
-
"Message format, supported: sidekiq5. Default: sidekiq5"
|
52
|
+
"Message format, supported: sidekiq5. Default: sidekiq5",
|
54
53
|
) { |message_format| options.message_format = message_format }
|
55
54
|
|
56
55
|
option_parser.on(
|
57
56
|
"--split-keys=SPLIT_KEYS",
|
58
57
|
Array,
|
59
|
-
"Split keys which should be handled, all if not specified"
|
58
|
+
"Split keys which should be handled, all if not specified",
|
60
59
|
) { |split_keys| options.split_keys = split_keys if !split_keys.empty? }
|
61
60
|
|
62
61
|
option_parser.on(
|
63
62
|
"--batch-size=BATCH_SIZE",
|
64
63
|
Integer,
|
65
|
-
"Amount of records fetched in one fetch. Bigger value means more duplicated messages when network problems occur. Default: 100"
|
64
|
+
"Amount of records fetched in one fetch. Bigger value means more duplicated messages when network problems occur. Default: 100",
|
66
65
|
) { |batch_size| options.batch_size = batch_size }
|
67
66
|
|
68
67
|
option_parser.on("--metrics-url=METRICS_URL", "URI to metrics collector, optional") do |metrics_url|
|
@@ -71,18 +70,18 @@ module RubyEventStore
|
|
71
70
|
|
72
71
|
option_parser.on(
|
73
72
|
"--cleanup=STRATEGY",
|
74
|
-
"A strategy for cleaning old records. One of: none or iso8601 duration format how old enqueued records should be removed. Default: none"
|
73
|
+
"A strategy for cleaning old records. One of: none or iso8601 duration format how old enqueued records should be removed. Default: none",
|
75
74
|
) { |cleanup_strategy| options.cleanup_strategy = cleanup_strategy }
|
76
75
|
|
77
76
|
option_parser.on(
|
78
77
|
"--cleanup-limit=LIMIT",
|
79
|
-
"Amount of records removed in single cleanup run. One of: all or number of records that should be removed. Default: all"
|
78
|
+
"Amount of records removed in single cleanup run. One of: all or number of records that should be removed. Default: all",
|
80
79
|
) { |cleanup_limit| options.cleanup_limit = cleanup_limit }
|
81
80
|
|
82
81
|
option_parser.on(
|
83
82
|
"--sleep-on-empty=SLEEP_TIME",
|
84
83
|
Float,
|
85
|
-
"How long to sleep before next check when there was nothing to do. Default: 0.5"
|
84
|
+
"How long to sleep before next check when there was nothing to do. Default: 0.5",
|
86
85
|
) { |sleep_on_empty| options.sleep_on_empty = sleep_on_empty }
|
87
86
|
|
88
87
|
option_parser.on("-l", "--[no-]lock", "Lock split key in consumer") do |locking|
|
@@ -101,27 +100,26 @@ module RubyEventStore
|
|
101
100
|
|
102
101
|
def run(argv)
|
103
102
|
options = Parser.parse(argv)
|
104
|
-
build_runner(options)
|
105
|
-
.run
|
103
|
+
build_runner(options).run
|
106
104
|
end
|
107
105
|
|
108
106
|
def build_runner(options)
|
109
107
|
consumer_uuid = SecureRandom.uuid
|
110
108
|
logger = Logger.new(STDOUT, level: options.log_level, progname: "RES-Outbox #{consumer_uuid}")
|
111
|
-
consumer_configuration =
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
109
|
+
consumer_configuration =
|
110
|
+
Configuration.new(
|
111
|
+
split_keys: options.split_keys,
|
112
|
+
message_format: options.message_format,
|
113
|
+
batch_size: options.batch_size,
|
114
|
+
database_url: options.database_url,
|
115
|
+
redis_url: options.redis_url,
|
116
|
+
cleanup: options.cleanup_strategy,
|
117
|
+
cleanup_limit: options.cleanup_limit,
|
118
|
+
sleep_on_empty: options.sleep_on_empty,
|
119
|
+
locking: options.locking,
|
120
|
+
)
|
122
121
|
metrics = Metrics.from_url(options.metrics_url)
|
123
|
-
outbox_consumer =
|
124
|
-
Outbox::Consumer.new(consumer_uuid, consumer_configuration, logger: logger, metrics: metrics)
|
122
|
+
outbox_consumer = Outbox::Consumer.new(consumer_uuid, consumer_configuration, logger: logger, metrics: metrics)
|
125
123
|
Runner.new(outbox_consumer, consumer_configuration, logger: logger)
|
126
124
|
end
|
127
125
|
end
|
@@ -41,14 +41,14 @@ module RubyEventStore
|
|
41
41
|
end
|
42
42
|
|
43
43
|
attr_reader :split_keys,
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
44
|
+
:message_format,
|
45
|
+
:batch_size,
|
46
|
+
:database_url,
|
47
|
+
:redis_url,
|
48
|
+
:cleanup,
|
49
|
+
:cleanup_limit,
|
50
|
+
:sleep_on_empty,
|
51
|
+
:locking
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
@@ -44,14 +44,17 @@ module RubyEventStore
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def handle_split(fetch_specification)
|
47
|
-
repository
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
47
|
+
repository
|
48
|
+
.with_next_batch(fetch_specification, tempo.batch_size, consumer_uuid, locking, @clock) do |record|
|
49
|
+
now = @clock.now.utc
|
50
|
+
processor.process(record, now)
|
51
|
+
repository.mark_as_enqueued(record, now)
|
52
|
+
end
|
53
|
+
.tap do
|
54
|
+
cleanup(fetch_specification)
|
55
|
+
processor.after_batch
|
56
|
+
end
|
57
|
+
.success_count > 0
|
55
58
|
end
|
56
59
|
|
57
60
|
private
|
@@ -7,7 +7,6 @@ module RubyEventStore
|
|
7
7
|
module Metrics
|
8
8
|
class Influx
|
9
9
|
def initialize(url)
|
10
|
-
uri = URI.parse(url)
|
11
10
|
options = { url: url, async: true, time_precision: "ns" }
|
12
11
|
@influxdb_client = InfluxDB::Client.new(**options)
|
13
12
|
end
|
@@ -15,7 +14,7 @@ module RubyEventStore
|
|
15
14
|
def write_operation_result(operation, result)
|
16
15
|
write_point(
|
17
16
|
"ruby_event_store.outbox.lock",
|
18
|
-
{ values: { value: 1 }, tags: { operation: operation, result: result } }
|
17
|
+
{ values: { value: 1 }, tags: { operation: operation, result: result } },
|
19
18
|
)
|
20
19
|
end
|
21
20
|
|
@@ -26,13 +25,13 @@ module RubyEventStore
|
|
26
25
|
values: {
|
27
26
|
enqueued: enqueued,
|
28
27
|
failed: failed,
|
29
|
-
remaining: remaining
|
28
|
+
remaining: remaining,
|
30
29
|
},
|
31
30
|
tags: {
|
32
31
|
format: format,
|
33
|
-
split_key: split_key
|
34
|
-
}
|
35
|
-
}
|
32
|
+
split_key: split_key,
|
33
|
+
},
|
34
|
+
},
|
36
35
|
)
|
37
36
|
end
|
38
37
|
|
@@ -4,9 +4,11 @@ module RubyEventStore
|
|
4
4
|
module Outbox
|
5
5
|
module Metrics
|
6
6
|
class Null
|
7
|
-
def write_operation_result(operation, result)
|
7
|
+
def write_operation_result(operation, result)
|
8
|
+
end
|
8
9
|
|
9
|
-
def write_point_queue(**kwargs)
|
10
|
+
def write_point_queue(**kwargs)
|
11
|
+
end
|
10
12
|
end
|
11
13
|
end
|
12
14
|
end
|
@@ -151,57 +151,53 @@ module RubyEventStore
|
|
151
151
|
BatchResult.empty.tap do |result|
|
152
152
|
obtained_lock = obtain_lock_for_process(fetch_specification, consumer_uuid, clock: clock)
|
153
153
|
case obtained_lock
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
154
|
+
when :deadlocked
|
155
|
+
logger.warn "Obtaining lock for split_key '#{fetch_specification.split_key}' failed (deadlock)"
|
156
|
+
metrics.write_operation_result("obtain", "deadlocked")
|
157
|
+
return BatchResult.empty
|
158
|
+
when :lock_timeout
|
159
|
+
logger.warn "Obtaining lock for split_key '#{fetch_specification.split_key}' failed (lock timeout)"
|
160
|
+
metrics.write_operation_result("obtain", "lock_timeout")
|
161
|
+
return BatchResult.empty
|
162
|
+
when :taken
|
163
|
+
logger.debug "Obtaining lock for split_key '#{fetch_specification.split_key}' unsuccessful (taken)"
|
164
|
+
metrics.write_operation_result("obtain", "taken")
|
165
|
+
return BatchResult.empty
|
166
166
|
end
|
167
167
|
|
168
168
|
Consumer::MAXIMUM_BATCH_FETCHES_IN_ONE_LOCK.times do
|
169
169
|
batch = retrieve_batch(fetch_specification, batch_size).to_a
|
170
170
|
break if batch.empty?
|
171
|
-
batch.each
|
172
|
-
handle_execution(result) do
|
173
|
-
block.call(record)
|
174
|
-
end
|
175
|
-
end
|
171
|
+
batch.each { |record| handle_execution(result) { block.call(record) } }
|
176
172
|
case (refresh_result = obtained_lock.refresh(clock: clock))
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
173
|
+
when :ok
|
174
|
+
when :deadlocked
|
175
|
+
logger.warn "Refreshing lock for split_key '#{lock.split_key}' failed (deadlock)"
|
176
|
+
metrics.write_operation_result("refresh", "deadlocked")
|
177
|
+
break
|
178
|
+
when :lock_timeout
|
179
|
+
logger.warn "Refreshing lock for split_key '#{lock.split_key}' failed (lock timeout)"
|
180
|
+
metrics.write_operation_result("refresh", "lock_timeout")
|
181
|
+
break
|
182
|
+
when :stolen
|
183
|
+
logger.debug "Refreshing lock for split_key '#{lock.split_key}' unsuccessful (stolen)"
|
184
|
+
metrics.write_operation_result("refresh", "stolen")
|
185
|
+
break
|
186
|
+
else
|
187
|
+
raise "Unexpected result #{refresh_result}"
|
192
188
|
end
|
193
189
|
end
|
194
190
|
|
195
191
|
case release_lock_for_process(fetch_specification, consumer_uuid)
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
192
|
+
when :deadlocked
|
193
|
+
logger.warn "Releasing lock for split_key '#{fetch_specification.split_key}' failed (deadlock)"
|
194
|
+
metrics.write_operation_result("release", "deadlocked")
|
195
|
+
when :lock_timeout
|
196
|
+
logger.warn "Releasing lock for split_key '#{fetch_specification.split_key}' failed (lock timeout)"
|
197
|
+
metrics.write_operation_result("release", "lock_timeout")
|
198
|
+
when :not_taken_by_this_process
|
199
|
+
logger.debug "Releasing lock for split_key '#{fetch_specification.split_key}' failed (not taken by this process)"
|
200
|
+
metrics.write_operation_result("release", "not_taken_by_this_process")
|
205
201
|
end
|
206
202
|
instrument_batch_result(fetch_specification, result)
|
207
203
|
end
|
@@ -212,11 +208,7 @@ module RubyEventStore
|
|
212
208
|
Record.transaction do
|
213
209
|
batch = retrieve_batch(fetch_specification, batch_size).lock("FOR UPDATE SKIP LOCKED")
|
214
210
|
break if batch.empty?
|
215
|
-
batch.each
|
216
|
-
handle_execution(result) do
|
217
|
-
block.call(record)
|
218
|
-
end
|
219
|
-
end
|
211
|
+
batch.each { |record| handle_execution(result) { block.call(record) } }
|
220
212
|
end
|
221
213
|
|
222
214
|
instrument_batch_result(fetch_specification, result)
|
@@ -229,7 +221,7 @@ module RubyEventStore
|
|
229
221
|
failed: result.failed_count,
|
230
222
|
format: fetch_specification.message_format,
|
231
223
|
split_key: fetch_specification.split_key,
|
232
|
-
remaining: Record.remaining_for(fetch_specification).count
|
224
|
+
remaining: Record.remaining_for(fetch_specification).count,
|
233
225
|
)
|
234
226
|
|
235
227
|
logger.info "Sent #{result.success_count} messages from outbox table"
|
@@ -20,7 +20,7 @@ module RubyEventStore
|
|
20
20
|
|
21
21
|
queue = parsed_record["queue"]
|
22
22
|
raise InvalidPayload.new("Missing queue") if queue.nil? || queue.empty?
|
23
|
-
payload = JSON.generate(parsed_record.merge({ "enqueued_at" =>
|
23
|
+
payload = JSON.generate(parsed_record.merge({ "enqueued_at" => record.created_at.to_f }))
|
24
24
|
|
25
25
|
redis.call("LPUSH", "queue:#{queue}", payload)
|
26
26
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_event_store-outbox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arkency
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: ruby_event_store
|
@@ -91,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
91
|
- !ruby/object:Gem::Version
|
92
92
|
version: '0'
|
93
93
|
requirements: []
|
94
|
-
rubygems_version: 3.6.
|
94
|
+
rubygems_version: 3.6.8
|
95
95
|
specification_version: 4
|
96
96
|
summary: Active Record based outbox for Ruby Event Store
|
97
97
|
test_files: []
|