tobox 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +29 -0
- data/lib/tobox/configuration.rb +6 -4
- data/lib/tobox/fetcher.rb +39 -33
- data/lib/tobox/plugins/event_grouping.rb +3 -3
- data/lib/tobox/plugins/inbox.rb +31 -8
- data/lib/tobox/version.rb +1 -1
- data/lib/tobox/worker.rb +8 -7
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8f6e85f173d4636a64617ede574141d4b2365799b774a6e56a76b70a59db6ba
|
4
|
+
data.tar.gz: 49bb80cc99034ba4db846964988c8d1fc2a1d8e062decbe57d5b786e892afb8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1e3f588dbe62ceaf50d6d1e67e3fbcecf0798027728907961d2bd92d18ad5cf779d215c6cad614ace6bdf0c6bc00e8c32a2e3f0ddd30ae1c7f6c2f6524b3133
|
7
|
+
data.tar.gz: 6170813875d41ed03012ded3a418290446abfeac34a80fb5a7e21df3741032df23b05261d7b87c4c9ff3c2d230738ff20e93c7d178969606e092617d6f91357c
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -219,6 +219,20 @@ exponential_retry_factor 2
|
|
219
219
|
|
220
220
|
**Note**: the new attempt will be retried in `n ** 4`, where `n` is the number of past attempts for that event.
|
221
221
|
|
222
|
+
### `batch_size`
|
223
|
+
|
224
|
+
Number of events fetched in each outbox loop.
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
batch_size 10
|
228
|
+
```
|
229
|
+
|
230
|
+
**Note**: event handlers will receive all events covered by the given callback in its arguments:
|
231
|
+
|
232
|
+
```ruby
|
233
|
+
on(:event1, :event2) do |*events| # at most 10 events, may contain events of type 1 and 2
|
234
|
+
```
|
235
|
+
|
222
236
|
### `concurrency`
|
223
237
|
|
224
238
|
Number of workers processing events.
|
@@ -229,6 +243,21 @@ concurrency 4
|
|
229
243
|
|
230
244
|
**Note**: the default concurrency is adapted and different for each worker pool type, so make sure you understand how this tweak may affect you.
|
231
245
|
|
246
|
+
### `max_connections`
|
247
|
+
|
248
|
+
Number of database connections the workers will share to do their work.
|
249
|
+
|
250
|
+
In the (default) threaded worker mode, it'll default to the number of workers (set by the `concurrency` configuration).
|
251
|
+
|
252
|
+
```ruby
|
253
|
+
# 10 workers sharing 4 database connections
|
254
|
+
max_connections 4
|
255
|
+
concurrency 10
|
256
|
+
```
|
257
|
+
|
258
|
+
This can be useful when the database presents overhead in managing write intensive workload (such as the one an outbox generates), and you want to be able to scale without putting more work on the database.
|
259
|
+
|
260
|
+
|
232
261
|
### `worker`
|
233
262
|
|
234
263
|
Type of the worker used to process events. Can be `:thread` (default), `:fiber`, or a class implementing the `Tobox::Pool` protocol (TBD: define what this protocol is).
|
data/lib/tobox/configuration.rb
CHANGED
@@ -20,6 +20,7 @@ module Tobox
|
|
20
20
|
database_options: nil,
|
21
21
|
table: :outbox,
|
22
22
|
created_at_column: nil,
|
23
|
+
batch_size: 1,
|
23
24
|
max_attempts: 10,
|
24
25
|
exponential_retry_factor: 2,
|
25
26
|
wait_for_events_delay: 5,
|
@@ -64,7 +65,7 @@ module Tobox
|
|
64
65
|
|
65
66
|
@database = if @config[:database_uri]
|
66
67
|
database_opts = @config[:database_options] || {}
|
67
|
-
database_opts[:max_connections]
|
68
|
+
database_opts[:max_connections] ||= (@config[:concurrency] if @config[:worker] == :thread)
|
68
69
|
db = Sequel.connect(@config[:database_uri].to_s, database_opts)
|
69
70
|
Array(@lifecycle_events[:database_connect]).each { |cb| cb.call(db) }
|
70
71
|
db
|
@@ -83,9 +84,10 @@ module Tobox
|
|
83
84
|
freeze
|
84
85
|
end
|
85
86
|
|
86
|
-
def on(*
|
87
|
-
|
88
|
-
|
87
|
+
def on(*event_types, &callback)
|
88
|
+
callback_events = (@handlers[callback] ||= [])
|
89
|
+
event_types.each do |event_type|
|
90
|
+
callback_events << event_type.to_sym
|
89
91
|
end
|
90
92
|
self
|
91
93
|
end
|
data/lib/tobox/fetcher.rb
CHANGED
@@ -28,6 +28,8 @@ module Tobox
|
|
28
28
|
.where(run_at_conds)
|
29
29
|
.order(Sequel.desc(:run_at, nulls: :first), :id)
|
30
30
|
|
31
|
+
@batch_size = configuration[:batch_size]
|
32
|
+
|
31
33
|
@mark_as_fetched_params = { attempts: Sequel[@table][:attempts] + 1, last_error: nil }
|
32
34
|
|
33
35
|
@before_event_handlers = Array(@configuration.lifecycle_events[:before_event])
|
@@ -38,18 +40,19 @@ module Tobox
|
|
38
40
|
def fetch_events(&blk)
|
39
41
|
num_events = 0
|
40
42
|
events_tr do
|
41
|
-
|
43
|
+
event_ids = nil
|
44
|
+
# @type var event_ids: Array[Integer]
|
42
45
|
|
43
46
|
event_id_tr do
|
44
|
-
|
45
|
-
mark_as_fetched(
|
47
|
+
event_ids = fetch_event_ids
|
48
|
+
mark_as_fetched(event_ids) unless event_ids.empty?
|
46
49
|
end
|
47
50
|
|
48
|
-
if
|
49
|
-
|
50
|
-
num_events =
|
51
|
+
if event_ids && !event_ids.empty?
|
52
|
+
with_events(event_ids) do |events|
|
53
|
+
num_events = events.size
|
51
54
|
|
52
|
-
|
55
|
+
prepare_events(events, &blk)
|
53
56
|
end
|
54
57
|
end
|
55
58
|
end
|
@@ -59,20 +62,23 @@ module Tobox
|
|
59
62
|
|
60
63
|
private
|
61
64
|
|
62
|
-
def
|
63
|
-
|
64
|
-
|
65
|
-
|
65
|
+
def prepare_events(events)
|
66
|
+
prepared_events = events.map do |event|
|
67
|
+
event[:metadata] = try_json_parse(event[:metadata]) if event[:metadata]
|
68
|
+
handle_before_event(event)
|
69
|
+
to_message(event)
|
70
|
+
end
|
71
|
+
yield(prepared_events)
|
66
72
|
end
|
67
73
|
|
68
|
-
def
|
74
|
+
def fetch_event_ids
|
69
75
|
@pick_next_sql.for_update
|
70
76
|
.skip_locked
|
71
|
-
.limit(
|
77
|
+
.limit(@batch_size).select_map(:id) # lock starts here
|
72
78
|
end
|
73
79
|
|
74
|
-
def mark_as_fetched(
|
75
|
-
@ds.where(id:
|
80
|
+
def mark_as_fetched(event_ids)
|
81
|
+
@ds.where(id: event_ids).update(@mark_as_fetched_params)
|
76
82
|
end
|
77
83
|
|
78
84
|
def events_tr(&block)
|
@@ -83,32 +89,34 @@ module Tobox
|
|
83
89
|
yield
|
84
90
|
end
|
85
91
|
|
86
|
-
def
|
87
|
-
|
92
|
+
def with_events(event_ids, &blk)
|
93
|
+
events, error = yield_events(event_ids, &blk)
|
88
94
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
95
|
+
events.each do |event|
|
96
|
+
if error
|
97
|
+
event.merge!(mark_as_error(event, error))
|
98
|
+
handle_error_event(event, error)
|
99
|
+
else
|
100
|
+
handle_after_event(event)
|
101
|
+
end
|
94
102
|
end
|
95
103
|
end
|
96
104
|
|
97
|
-
def
|
98
|
-
events_ds = @ds.where(id:
|
99
|
-
|
105
|
+
def yield_events(event_ids)
|
106
|
+
events_ds = @ds.where(id: event_ids)
|
107
|
+
events = error = nil
|
100
108
|
|
101
109
|
begin
|
102
|
-
|
110
|
+
events = events_ds.all
|
103
111
|
|
104
|
-
yield
|
112
|
+
yield events
|
105
113
|
|
106
|
-
events_ds.delete
|
114
|
+
events_ds.delete unless events.empty?
|
107
115
|
rescue StandardError => e
|
108
116
|
error = e
|
109
117
|
end
|
110
118
|
|
111
|
-
[
|
119
|
+
[events, error]
|
112
120
|
end
|
113
121
|
|
114
122
|
def log_message(msg)
|
@@ -141,15 +149,13 @@ module Tobox
|
|
141
149
|
{
|
142
150
|
id: event[:id],
|
143
151
|
type: event[:type],
|
144
|
-
before: try_json_parse(event[:data_before]),
|
145
|
-
after: try_json_parse(event[:data_after]),
|
152
|
+
before: (try_json_parse(event[:data_before]) if event[:data_before]),
|
153
|
+
after: (try_json_parse(event[:data_after]) if event[:data_after]),
|
146
154
|
at: event[:created_at]
|
147
155
|
}
|
148
156
|
end
|
149
157
|
|
150
158
|
def try_json_parse(data)
|
151
|
-
return unless data
|
152
|
-
|
153
159
|
data = JSON.parse(data.to_s) unless data.respond_to?(:to_hash)
|
154
160
|
|
155
161
|
data
|
@@ -16,7 +16,7 @@ module Tobox
|
|
16
16
|
|
17
17
|
private
|
18
18
|
|
19
|
-
def
|
19
|
+
def fetch_event_ids
|
20
20
|
group = @pick_next_sql.for_update
|
21
21
|
.skip_locked
|
22
22
|
.limit(1)
|
@@ -36,8 +36,8 @@ module Tobox
|
|
36
36
|
event_ids = []
|
37
37
|
end
|
38
38
|
|
39
|
-
# lock all
|
40
|
-
event_ids.first
|
39
|
+
# lock all
|
40
|
+
event_ids.first(@batch_size)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
data/lib/tobox/plugins/inbox.rb
CHANGED
@@ -20,24 +20,47 @@ module Tobox
|
|
20
20
|
|
21
21
|
private
|
22
22
|
|
23
|
-
def
|
24
|
-
try_insert_inbox(
|
23
|
+
def prepare_events(events)
|
24
|
+
try_insert_inbox(events) do |deduped_events|
|
25
|
+
super(deduped_events)
|
26
|
+
end
|
25
27
|
end
|
26
28
|
|
27
|
-
def try_insert_inbox(
|
29
|
+
def try_insert_inbox(events)
|
30
|
+
inboxed = nil
|
31
|
+
|
28
32
|
if @inbox_ds.respond_to?(:supports_insert_conflict?) && @inbox_ds.supports_insert_conflict?
|
29
|
-
|
33
|
+
if @inbox_ds.supports_returning?(:insert)
|
34
|
+
inboxed = @inbox_ds.insert_conflict
|
35
|
+
.returning(@inbox_column)
|
36
|
+
.multi_insert(events.map { |event| { @inbox_column => event[@inbox_column] } })
|
37
|
+
else
|
38
|
+
ret = @inbox_ds.insert_conflict.multi_insert(events.map do |event|
|
39
|
+
{ @inbox_column => event[@inbox_column] }
|
40
|
+
end)
|
30
41
|
|
31
|
-
|
42
|
+
if ret
|
43
|
+
inboxed = @inbox_ds.where(@inbox_column => events.map do |ev|
|
44
|
+
ev[@inbox_column]
|
45
|
+
end).select_map(@inbox_column)
|
46
|
+
end
|
47
|
+
end
|
32
48
|
else
|
33
|
-
|
49
|
+
inboxed = []
|
50
|
+
events.each do |event|
|
34
51
|
@inbox_ds.insert(@inbox_column => event[@inbox_column])
|
52
|
+
inboxed << event[@inbox_column]
|
35
53
|
rescue Sequel::UniqueConstraintViolation
|
36
|
-
|
54
|
+
# ignore
|
37
55
|
end
|
56
|
+
return events if inboxed.empty?
|
38
57
|
end
|
39
58
|
|
40
|
-
|
59
|
+
return events if inboxed && inboxed.empty?
|
60
|
+
|
61
|
+
yield events.select { |ev| inboxed.include?(ev[@inbox_column]) }
|
62
|
+
|
63
|
+
events
|
41
64
|
end
|
42
65
|
end
|
43
66
|
end
|
data/lib/tobox/version.rb
CHANGED
data/lib/tobox/worker.rb
CHANGED
@@ -33,15 +33,16 @@ module Tobox
|
|
33
33
|
def do_work
|
34
34
|
return if @finished
|
35
35
|
|
36
|
-
sum_fetched_events = @fetcher.fetch_events do |
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
if @handlers.key?(event_type)
|
41
|
-
@handlers[event_type].each do |handler|
|
42
|
-
handler.call(args)
|
36
|
+
sum_fetched_events = @fetcher.fetch_events do |events|
|
37
|
+
cs = @handlers.each_with_object({}) do |(callback, event_types), bucket|
|
38
|
+
events.each do |event|
|
39
|
+
(bucket[callback] ||= []) << message_to_arguments(event) if event_types.include?(event[:type].to_sym)
|
43
40
|
end
|
44
41
|
end
|
42
|
+
|
43
|
+
cs.each do |callback, evs|
|
44
|
+
callback.call(*evs)
|
45
|
+
end
|
45
46
|
end
|
46
47
|
|
47
48
|
return if @finished
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tobox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- HoneyryderChuck
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-10-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: logger
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: sequel
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|