message_bus 4.0.0 → 4.1.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: 65630299cab9aae5df5b4f3131043d42063575978d5a83e9267436bed2b7d729
4
- data.tar.gz: 2bd69d120552b01ae82c73a775d065c23575ac623016d2bd2ac25a5ebd637d6f
3
+ metadata.gz: 11a14ba0ec9ad142d3335fb7eb764be8e9b975a8dc8367fd4bf359e128a56095
4
+ data.tar.gz: bf41cede85c8e75e56829447cd3cf9ed6cd531ba27fcddfbccb6043f3a82da34
5
5
  SHA512:
6
- metadata.gz: 65a5d250126fc935053dfffef037baaf780678764ac95e4924603b901016f82b451a79a51f18fc699f2cfd8006cb805cd4887d206c12ca68ac68f059f1f13409
7
- data.tar.gz: 2ebcc465985827a16866a948690a7bb4c03252050c8bfb1ee458809c6f0eda8c8fd581ba2f9bb1b496d3c7952456a11ad6ac51dd63e69c465238cffa17d7c2da
6
+ metadata.gz: 23c1d181028194c16e98c5a3df42e445458aef4779c97ae2d6d5100adf5053680f62ea4c914bea7c84c124bd453127e141c68ffeb1c742cc208e1d040bc8558f
7
+ data.tar.gz: 7efffe711fac2520356b115820acba2d4206d53f35017ba64c86e3c571a85fb423e943cb9301a03d3b47d6f442f1626dea0495c0242220283685eaa944c890b6
@@ -20,7 +20,7 @@ jobs:
20
20
  strategy:
21
21
  fail-fast: false
22
22
  matrix:
23
- ruby: [2.6, 2.7, 3.0]
23
+ ruby: [2.6, 2.7, '3.0', 3.1]
24
24
  redis: [5, 6]
25
25
 
26
26
  services:
data/CHANGELOG CHANGED
@@ -1,3 +1,12 @@
1
+ 16-02-2022
2
+
3
+ - Version 4.1.0
4
+
5
+ - PERF: Optimize Client#backlog for up-to-date clients
6
+
7
+ Also introduces a new MessageBus#last_ids(*channels) api for fetching the last_ids of
8
+ multiple channels simultaneously
9
+
1
10
  11-01-2022
2
11
 
3
12
  - Version 4.0.0
@@ -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
- 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)'
179
- conn.exec 'CREATE INDEX table_channel_id_index ON message_bus (channel, id)'
180
- conn.exec 'CREATE INDEX table_added_at_index ON message_bus (added_at)'
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
- begin
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#last_id)
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
@@ -178,31 +178,32 @@ class MessageBus::Client
178
178
  r = []
179
179
  new_message_ids = nil
180
180
 
181
- @subscriptions.each do |k, v|
182
- id = v.to_i
181
+ last_bus_ids = @bus.last_ids(*@subscriptions.keys, site_id: site_id)
183
182
 
184
- if id < -1
185
- last_id = @bus.last_id(k, site_id)
186
- id = last_id + id + 1
187
- id = 0 if id < 0
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
- next if id < 0
191
-
192
- messages = @bus.backlog(k, id, site_id)
199
+ messages = @bus.backlog(k, last_client_id, site_id)
193
200
 
194
- if messages.length == 0
195
- if id > @bus.last_id(k, site_id)
196
- @subscriptions[k] = -1
197
- end
198
- else
199
- messages.each do |msg|
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] = @bus.last_id(k, site_id)
216
+ @subscriptions[k] = status_message[k] = last_bus_ids[k]
216
217
  end
217
218
  end
218
219
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MessageBus
4
- VERSION = "4.0.0"
4
+ VERSION = "4.1.0"
5
5
  end
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.0.0
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-01-11 00:00:00.000000000 Z
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: []