message_bus 4.0.0 → 4.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 +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/CHANGELOG +9 -0
- data/lib/message_bus/backends/base.rb +9 -0
- data/lib/message_bus/backends/memory.rb +7 -0
- data/lib/message_bus/backends/postgres.rb +33 -9
- data/lib/message_bus/backends/redis.rb +7 -0
- data/lib/message_bus/client.rb +23 -22
- data/lib/message_bus/version.rb +1 -1
- data/lib/message_bus.rb +12 -0
- data/message_bus.gemspec +0 -2
- data/spec/lib/message_bus/backend_spec.rb +16 -0
- data/spec/performance/backlog.rb +80 -0
- metadata +4 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11a14ba0ec9ad142d3335fb7eb764be8e9b975a8dc8367fd4bf359e128a56095
|
4
|
+
data.tar.gz: bf41cede85c8e75e56829447cd3cf9ed6cd531ba27fcddfbccb6043f3a82da34
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23c1d181028194c16e98c5a3df42e445458aef4779c97ae2d6d5100adf5053680f62ea4c914bea7c84c124bd453127e141c68ffeb1c742cc208e1d040bc8558f
|
7
|
+
data.tar.gz: 7efffe711fac2520356b115820acba2d4206d53f35017ba64c86e3c571a85fb423e943cb9301a03d3b47d6f442f1626dea0495c0242220283685eaa944c890b6
|
data/.github/workflows/ci.yml
CHANGED
data/CHANGELOG
CHANGED
@@ -118,6 +118,15 @@ module MessageBus
|
|
118
118
|
raise ConcreteClassMustImplementError
|
119
119
|
end
|
120
120
|
|
121
|
+
# Get the ID of the last message published on multiple channels
|
122
|
+
#
|
123
|
+
# @param [Array<String>] channels - array of channels to fetch
|
124
|
+
#
|
125
|
+
# @return [Array<Integer>] the channel-specific IDs of the last message published to each requested channel
|
126
|
+
def last_ids(*channels)
|
127
|
+
raise ConcreteClassMustImplementError
|
128
|
+
end
|
129
|
+
|
121
130
|
# Get messages from a channel backlog
|
122
131
|
#
|
123
132
|
# @param [String] channel the name of the channel in question
|
@@ -244,6 +244,13 @@ module MessageBus
|
|
244
244
|
client.max_id(channel)
|
245
245
|
end
|
246
246
|
|
247
|
+
# (see Base#last_ids)
|
248
|
+
def last_ids(*channels)
|
249
|
+
channels.map do |c|
|
250
|
+
last_id(c)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
247
254
|
# (see Base#backlog)
|
248
255
|
def backlog(channel, last_id = 0)
|
249
256
|
items = client.backlog channel, last_id.to_i
|
@@ -132,6 +132,21 @@ module MessageBus
|
|
132
132
|
end
|
133
133
|
end
|
134
134
|
|
135
|
+
def max_ids(*channels)
|
136
|
+
block = proc do |pg_result|
|
137
|
+
ids = Array.new(channels.size, 0)
|
138
|
+
pg_result.ntuples.times do |i|
|
139
|
+
channel = pg_result.getvalue(i, 0)
|
140
|
+
max_id = pg_result.getvalue(i, 1)
|
141
|
+
channel_index = channels.index(channel)
|
142
|
+
ids[channel_index] = max_id.to_i
|
143
|
+
end
|
144
|
+
ids
|
145
|
+
end
|
146
|
+
|
147
|
+
hold { |conn| exec_prepared(conn, 'max_channel_ids', [PG::TextEncoder::Array.new.encode(channels)], &block) }
|
148
|
+
end
|
149
|
+
|
135
150
|
def publish(channel, data)
|
136
151
|
hold { |conn| exec_prepared(conn, 'publish', [channel, data]) }
|
137
152
|
end
|
@@ -175,9 +190,16 @@ module MessageBus
|
|
175
190
|
end
|
176
191
|
|
177
192
|
def create_table(conn)
|
178
|
-
|
179
|
-
|
180
|
-
|
193
|
+
sync do
|
194
|
+
begin
|
195
|
+
conn.exec("SELECT 'message_bus'::regclass")
|
196
|
+
rescue PG::UndefinedTable
|
197
|
+
conn.exec 'CREATE TABLE message_bus (id bigserial PRIMARY KEY, channel text NOT NULL, value text NOT NULL CHECK (octet_length(value) >= 2), added_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL)'
|
198
|
+
conn.exec 'CREATE INDEX table_channel_id_index ON message_bus (channel, id)'
|
199
|
+
conn.exec 'CREATE INDEX table_added_at_index ON message_bus (added_at)'
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
181
203
|
nil
|
182
204
|
end
|
183
205
|
|
@@ -214,11 +236,7 @@ module MessageBus
|
|
214
236
|
def new_pg_connection
|
215
237
|
conn = raw_pg_connection
|
216
238
|
|
217
|
-
|
218
|
-
conn.exec("SELECT 'message_bus'::regclass")
|
219
|
-
rescue PG::UndefinedTable
|
220
|
-
create_table(conn)
|
221
|
-
end
|
239
|
+
create_table(conn)
|
222
240
|
|
223
241
|
conn.exec 'PREPARE insert_message AS INSERT INTO message_bus (channel, value) VALUES ($1, $2) RETURNING id'
|
224
242
|
conn.exec 'PREPARE clear_global_backlog AS DELETE FROM message_bus WHERE (id <= $1)'
|
@@ -228,6 +246,7 @@ module MessageBus
|
|
228
246
|
conn.exec "PREPARE expire AS DELETE FROM message_bus WHERE added_at < CURRENT_TIMESTAMP - ($1::text || ' seconds')::interval"
|
229
247
|
conn.exec 'PREPARE get_message AS SELECT value FROM message_bus WHERE ((channel = $1) AND (id = $2))'
|
230
248
|
conn.exec 'PREPARE max_channel_id AS SELECT max(id) FROM message_bus WHERE (channel = $1)'
|
249
|
+
conn.exec 'PREPARE max_channel_ids AS SELECT channel, max(id) FROM message_bus WHERE (channel = ANY($1)) GROUP BY channel'
|
231
250
|
conn.exec 'PREPARE max_id AS SELECT max(id) FROM message_bus'
|
232
251
|
conn.exec 'PREPARE publish AS SELECT pg_notify($1, $2)'
|
233
252
|
|
@@ -310,7 +329,12 @@ module MessageBus
|
|
310
329
|
client.max_id(channel)
|
311
330
|
end
|
312
331
|
|
313
|
-
# (see Base#
|
332
|
+
# (see Base#last_ids)
|
333
|
+
def last_ids(*channels)
|
334
|
+
client.max_ids(*channels)
|
335
|
+
end
|
336
|
+
|
337
|
+
# (see Base#backlog)
|
314
338
|
def backlog(channel, last_id = 0)
|
315
339
|
items = client.backlog channel, last_id.to_i
|
316
340
|
|
@@ -194,6 +194,13 @@ LUA
|
|
194
194
|
pub_redis.get(backlog_id_key).to_i
|
195
195
|
end
|
196
196
|
|
197
|
+
# (see Base#last_ids)
|
198
|
+
def last_ids(*channels)
|
199
|
+
return [] if channels.size == 0
|
200
|
+
backlog_id_keys = channels.map { |c| backlog_id_key(c) }
|
201
|
+
pub_redis.mget(*backlog_id_keys).map(&:to_i)
|
202
|
+
end
|
203
|
+
|
197
204
|
# (see Base#backlog)
|
198
205
|
def backlog(channel, last_id = 0)
|
199
206
|
redis = pub_redis
|
data/lib/message_bus/client.rb
CHANGED
@@ -178,31 +178,32 @@ class MessageBus::Client
|
|
178
178
|
r = []
|
179
179
|
new_message_ids = nil
|
180
180
|
|
181
|
-
@subscriptions.
|
182
|
-
id = v.to_i
|
181
|
+
last_bus_ids = @bus.last_ids(*@subscriptions.keys, site_id: site_id)
|
183
182
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
183
|
+
@subscriptions.each do |k, v|
|
184
|
+
last_client_id = v.to_i
|
185
|
+
last_bus_id = last_bus_ids[k]
|
186
|
+
|
187
|
+
if last_client_id < -1 # Client requesting backlog relative to bus position
|
188
|
+
last_client_id = last_bus_id + last_client_id + 1
|
189
|
+
last_client_id = 0 if last_client_id < 0
|
190
|
+
elsif last_client_id == -1 # Client not requesting backlog
|
191
|
+
next
|
192
|
+
elsif last_client_id == last_bus_id # Client already up-to-date
|
193
|
+
next
|
194
|
+
elsif last_client_id > last_bus_id # Client ahead of the bus
|
195
|
+
@subscriptions[k] = -1
|
196
|
+
next
|
188
197
|
end
|
189
198
|
|
190
|
-
|
191
|
-
|
192
|
-
messages = @bus.backlog(k, id, site_id)
|
199
|
+
messages = @bus.backlog(k, last_client_id, site_id)
|
193
200
|
|
194
|
-
|
195
|
-
if
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
if allowed?(msg)
|
201
|
-
r << msg
|
202
|
-
else
|
203
|
-
new_message_ids ||= {}
|
204
|
-
new_message_ids[k] = msg.message_id
|
205
|
-
end
|
201
|
+
messages.each do |msg|
|
202
|
+
if allowed?(msg)
|
203
|
+
r << msg
|
204
|
+
else
|
205
|
+
new_message_ids ||= {}
|
206
|
+
new_message_ids[k] = msg.message_id
|
206
207
|
end
|
207
208
|
end
|
208
209
|
end
|
@@ -212,7 +213,7 @@ class MessageBus::Client
|
|
212
213
|
@subscriptions.each do |k, v|
|
213
214
|
if v.to_i == -1 || (new_message_ids && new_message_ids[k])
|
214
215
|
status_message ||= {}
|
215
|
-
@subscriptions[k] = status_message[k] =
|
216
|
+
@subscriptions[k] = status_message[k] = last_bus_ids[k]
|
216
217
|
end
|
217
218
|
end
|
218
219
|
|
data/lib/message_bus/version.rb
CHANGED
data/lib/message_bus.rb
CHANGED
@@ -503,6 +503,18 @@ module MessageBus::Implementation
|
|
503
503
|
backend_instance.last_id(encode_channel_name(channel, site_id))
|
504
504
|
end
|
505
505
|
|
506
|
+
# Get the ID of the last message published on multiple channels
|
507
|
+
#
|
508
|
+
# @param [Array<String>] channels - array of channels to fetch
|
509
|
+
# @param [String] site_id - the ID of the site by which to filter
|
510
|
+
#
|
511
|
+
# @return [Hash] the channel-specific IDs of the last message published to each requested channel
|
512
|
+
def last_ids(*channels, site_id: nil)
|
513
|
+
encoded_channel_names = channels.map { |c| encode_channel_name(c, site_id) }
|
514
|
+
ids = backend_instance.last_ids(*encoded_channel_names)
|
515
|
+
channels.zip(ids).to_h
|
516
|
+
end
|
517
|
+
|
506
518
|
# Get the last message published on a channel
|
507
519
|
#
|
508
520
|
# @param [String] channel the name of the channel in question
|
data/message_bus.gemspec
CHANGED
@@ -11,8 +11,6 @@ Gem::Specification.new do |gem|
|
|
11
11
|
gem.license = "MIT"
|
12
12
|
gem.files = `git ls-files`.split($\) +
|
13
13
|
["vendor/assets/javascripts/message-bus.js", "vendor/assets/javascripts/message-bus-ajax.js"]
|
14
|
-
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
15
|
-
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
16
14
|
gem.name = "message_bus"
|
17
15
|
gem.require_paths = ["lib"]
|
18
16
|
gem.version = MessageBus::VERSION
|
@@ -91,6 +91,22 @@ describe BACKEND_CLASS do
|
|
91
91
|
@bus.last_id("/foo").must_equal 1
|
92
92
|
end
|
93
93
|
|
94
|
+
it "should allow us to get multiple last_ids" do
|
95
|
+
@bus.last_ids("/foo", "/bar", "/foobar").must_equal [0, 0, 0]
|
96
|
+
|
97
|
+
@bus.publish("/foo", "one")
|
98
|
+
@bus.publish("/foo", "two")
|
99
|
+
@bus.publish("/foobar", "three")
|
100
|
+
|
101
|
+
@bus.last_ids("/foo", "/bar", "/foobar").must_equal(
|
102
|
+
[
|
103
|
+
@bus.last_id("/foo"),
|
104
|
+
@bus.last_id("/bar"),
|
105
|
+
@bus.last_id("/foobar")
|
106
|
+
]
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
94
110
|
it "can set backlog age" do
|
95
111
|
@bus.max_backlog_age = 1
|
96
112
|
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..', 'lib')
|
4
|
+
require 'logger'
|
5
|
+
require 'benchmark'
|
6
|
+
require 'message_bus'
|
7
|
+
|
8
|
+
require_relative "../helpers"
|
9
|
+
|
10
|
+
backends = ENV['MESSAGE_BUS_BACKENDS'].split(",").map(&:to_sym)
|
11
|
+
channel = "/foo"
|
12
|
+
iterations = 10_000
|
13
|
+
results = []
|
14
|
+
|
15
|
+
puts "Running backlog benchmark with #{iterations} iterations on backends: #{backends.inspect}"
|
16
|
+
|
17
|
+
run_benchmark = lambda do |bm, backend|
|
18
|
+
bus = MessageBus::Instance.new
|
19
|
+
bus.configure(test_config_for_backend(backend))
|
20
|
+
|
21
|
+
bus.backend_instance.max_backlog_size = 100
|
22
|
+
bus.backend_instance.max_global_backlog_size = 1000
|
23
|
+
|
24
|
+
channel_names = 10.times.map { |i| "channel#{i}" }
|
25
|
+
|
26
|
+
100.times do |i|
|
27
|
+
channel_names.each do |ch|
|
28
|
+
bus.publish(ch, { message_number_is: i })
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
last_ids = channel_names.map { |ch| [ch, bus.last_id(ch)] }.to_h
|
33
|
+
|
34
|
+
1000.times do
|
35
|
+
# Warmup
|
36
|
+
client = MessageBus::Client.new(message_bus: bus)
|
37
|
+
channel_names.each { |ch| client.subscribe(ch, -1) }
|
38
|
+
client.backlog
|
39
|
+
end
|
40
|
+
|
41
|
+
bm.report("#{backend} - #backlog with no backlogs requested") do
|
42
|
+
iterations.times do
|
43
|
+
client = MessageBus::Client.new(message_bus: bus)
|
44
|
+
channel_names.each { |ch| client.subscribe(ch, -1) }
|
45
|
+
client.backlog
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
(0..5).each do |ch_i|
|
50
|
+
channels_with_messages = (ch_i) * 2
|
51
|
+
|
52
|
+
bm.report("#{backend} - #backlog when #{channels_with_messages}/10 channels have new messages") do
|
53
|
+
iterations.times do
|
54
|
+
client = MessageBus::Client.new(message_bus: bus)
|
55
|
+
channel_names.each_with_index do |ch, i|
|
56
|
+
client.subscribe(ch, last_ids[ch] + ((i < channels_with_messages) ? -1 : 0))
|
57
|
+
end
|
58
|
+
result = client.backlog
|
59
|
+
if result.length != channels_with_messages
|
60
|
+
raise "Result has #{result.length} messages. Expected #{channels_with_messages}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
bus.reset!
|
67
|
+
bus.destroy
|
68
|
+
end
|
69
|
+
|
70
|
+
puts
|
71
|
+
|
72
|
+
Benchmark.benchmark(" duration\n", 60, "%10.2rs\n", "") do |bm|
|
73
|
+
backends.each do |backend|
|
74
|
+
run_benchmark.call(bm, backend)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
puts
|
78
|
+
results.each do |result|
|
79
|
+
puts result
|
80
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: message_bus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Saffron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -344,6 +344,7 @@ files:
|
|
344
344
|
- spec/lib/message_bus/rack/middleware_spec.rb
|
345
345
|
- spec/lib/message_bus/timer_thread_spec.rb
|
346
346
|
- spec/lib/message_bus_spec.rb
|
347
|
+
- spec/performance/backlog.rb
|
347
348
|
- spec/performance/publish.rb
|
348
349
|
- spec/spec_helper.rb
|
349
350
|
- spec/support/jasmine-browser.json
|
@@ -373,23 +374,4 @@ rubygems_version: 3.1.6
|
|
373
374
|
signing_key:
|
374
375
|
specification_version: 4
|
375
376
|
summary: ''
|
376
|
-
test_files:
|
377
|
-
- spec/assets/SpecHelper.js
|
378
|
-
- spec/assets/message-bus.spec.js
|
379
|
-
- spec/fixtures/test/Gemfile
|
380
|
-
- spec/fixtures/test/config.ru
|
381
|
-
- spec/helpers.rb
|
382
|
-
- spec/integration/http_client_spec.rb
|
383
|
-
- spec/lib/fake_async_middleware.rb
|
384
|
-
- spec/lib/message_bus/assets/asset_encoding_spec.rb
|
385
|
-
- spec/lib/message_bus/backend_spec.rb
|
386
|
-
- spec/lib/message_bus/client_spec.rb
|
387
|
-
- spec/lib/message_bus/connection_manager_spec.rb
|
388
|
-
- spec/lib/message_bus/distributed_cache_spec.rb
|
389
|
-
- spec/lib/message_bus/multi_process_spec.rb
|
390
|
-
- spec/lib/message_bus/rack/middleware_spec.rb
|
391
|
-
- spec/lib/message_bus/timer_thread_spec.rb
|
392
|
-
- spec/lib/message_bus_spec.rb
|
393
|
-
- spec/performance/publish.rb
|
394
|
-
- spec/spec_helper.rb
|
395
|
-
- spec/support/jasmine-browser.json
|
377
|
+
test_files: []
|