beetle 3.2.0 → 3.3.0.rc1
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/RELEASE_NOTES.rdoc +9 -1
- data/Rakefile +1 -1
- data/features/redis_auto_failover.feature +10 -10
- data/features/support/beetle_handler +1 -1
- data/lib/beetle/configuration.rb +12 -0
- data/lib/beetle/deduplication_store.rb +23 -4
- data/lib/beetle/message.rb +7 -3
- data/lib/beetle/version.rb +1 -1
- data/test/beetle/message_test.rb +17 -8
- data/test/test_helper.rb +1 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df35415e0cf7a4d4915a56aa594fe4aa1e63be2d536eab5589a5b80c349fdb96
|
4
|
+
data.tar.gz: 6f621ab1501f71cdc7ee84d8989dd446ebd17676d53f9e23ba9d8278206908ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51e61e992d734f636b5a86b176ab1150530c8e7c18dbbaf8c4f8e685eaf48b2531133bb0770b9b8f50bea45e95588cad6b892866a6cd6644dc8d5a3e67e71b7d
|
7
|
+
data.tar.gz: 4ef44c5608cfe93adc0bf9fefea0cfdfda59f358323b5ed93158fb57e0122241c45a4deff76a8a098664a4e027ff1394e135541857a4682286ab135481c012a9
|
data/RELEASE_NOTES.rdoc
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
= Release Notes
|
2
2
|
|
3
|
-
== Version 3.
|
3
|
+
== Version 3.3.0.rc1
|
4
|
+
* protect against duplicate handler execution by keeping the status of
|
5
|
+
handler exectution for both redundant and non-redundant messages in
|
6
|
+
the dedup store for a configurable time window. The config option
|
7
|
+
is named redis_status_key_expiry_interval. Note that this will
|
8
|
+
significantly increase the cpu load and memory usage of the Redis
|
9
|
+
server for applications using a large number of non-redundant messages.
|
10
|
+
|
11
|
+
== Version 3.2.0
|
4
12
|
* added currently processed message to the handler pre-processing step
|
5
13
|
|
6
14
|
== Version 3.1.0
|
data/Rakefile
CHANGED
@@ -27,7 +27,7 @@ Feature: Redis auto failover
|
|
27
27
|
Scenario: Successful single redis master switch with multiple failover sets
|
28
28
|
Given a redis server "redis-3" exists as master
|
29
29
|
And a redis server "redis-4" exists as slave of "redis-3"
|
30
|
-
Given a redis configuration server using redis servers "
|
30
|
+
Given a redis configuration server using redis servers "system/redis-1,redis-2;system2/redis-3,redis-4" with clients "rc-client-1,rc-client-2" exists
|
31
31
|
And a redis configuration client "rc-client-1" using redis servers "redis-1,redis-2" exists
|
32
32
|
And a redis configuration client "rc-client-2" using redis servers "redis-1,redis-2" exists
|
33
33
|
And a beetle handler using the redis-master file from "rc-client-1" exists
|
@@ -37,10 +37,10 @@ Feature: Redis auto failover
|
|
37
37
|
And the role of redis server "redis-2" should be "master"
|
38
38
|
And the redis master file of the redis configuration server should contain "redis-2"
|
39
39
|
And the redis master file of the redis configuration server should contain "redis-3"
|
40
|
-
And the redis master of "rc-client-1" in system "
|
41
|
-
And the redis master of "rc-client-2" in system "
|
42
|
-
And the redis master of "rc-client-1" in system "
|
43
|
-
And the redis master of "rc-client-2" in system "
|
40
|
+
And the redis master of "rc-client-1" in system "system" should be "redis-2"
|
41
|
+
And the redis master of "rc-client-2" in system "system" should be "redis-2"
|
42
|
+
And the redis master of "rc-client-1" in system "system2" should be "redis-3"
|
43
|
+
And the redis master of "rc-client-2" in system "system2" should be "redis-3"
|
44
44
|
And the redis master of the beetle handler should be "redis-2"
|
45
45
|
And a system notification for switching from "redis-1" to "redis-2" should be sent
|
46
46
|
Given a redis server "redis-1" exists as master
|
@@ -49,7 +49,7 @@ Feature: Redis auto failover
|
|
49
49
|
Scenario: Successful double redis master switch with multiple failover sets
|
50
50
|
Given a redis server "redis-3" exists as master
|
51
51
|
And a redis server "redis-4" exists as slave of "redis-3"
|
52
|
-
Given a redis configuration server using redis servers "
|
52
|
+
Given a redis configuration server using redis servers "system/redis-1,redis-2;system2/redis-3,redis-4" with clients "rc-client-1,rc-client-2" exists
|
53
53
|
And a redis configuration client "rc-client-1" using redis servers "redis-1,redis-2" exists
|
54
54
|
And a redis configuration client "rc-client-2" using redis servers "redis-1,redis-2" exists
|
55
55
|
And a beetle handler using the redis-master file from "rc-client-1" exists
|
@@ -60,10 +60,10 @@ Feature: Redis auto failover
|
|
60
60
|
And the role of redis server "redis-4" should be "master"
|
61
61
|
And the redis master file of the redis configuration server should contain "redis-2"
|
62
62
|
And the redis master file of the redis configuration server should contain "redis-4"
|
63
|
-
And the redis master of "rc-client-1" in system "
|
64
|
-
And the redis master of "rc-client-2" in system "
|
65
|
-
And the redis master of "rc-client-1" in system "
|
66
|
-
And the redis master of "rc-client-2" in system "
|
63
|
+
And the redis master of "rc-client-1" in system "system" should be "redis-2"
|
64
|
+
And the redis master of "rc-client-2" in system "system" should be "redis-2"
|
65
|
+
And the redis master of "rc-client-1" in system "system2" should be "redis-4"
|
66
|
+
And the redis master of "rc-client-2" in system "system2" should be "redis-4"
|
67
67
|
And the redis master of the beetle handler should be "redis-2"
|
68
68
|
Given a redis server "redis-1" exists as master
|
69
69
|
Then the role of redis server "redis-1" should be "slave"
|
@@ -36,7 +36,7 @@ Daemons.run_proc("beetle_handler", :log_output => true, :dir_mode => :normal, :d
|
|
36
36
|
config.handler(Beetle.config.beetle_policy_updates_queue_name) do |message|
|
37
37
|
begin
|
38
38
|
Beetle.config.logger.info "received beetle policy update message': #{message.data}"
|
39
|
-
client.
|
39
|
+
client.update_queue_properties!(JSON.parse(message.data))
|
40
40
|
rescue => e
|
41
41
|
Beetle.config.logger.error("#{e}:#{e.backtrace.join("\n")}")
|
42
42
|
end
|
data/lib/beetle/configuration.rb
CHANGED
@@ -16,6 +16,8 @@ module Beetle
|
|
16
16
|
# default logger (defaults to <tt>Logger.new(log_file)</tt>)
|
17
17
|
attr_accessor :logger
|
18
18
|
# defaults to <tt>STDOUT</tt>
|
19
|
+
attr_accessor :redis_logger
|
20
|
+
# set this to a logger instance if you want redis operations to be logged. defaults to <tt>nil</tt>.
|
19
21
|
attr_accessor :log_file
|
20
22
|
# number of seconds after which keys are removed from the message deduplication store (defaults to <tt>1.hour</tt>)
|
21
23
|
attr_accessor :gc_threshold
|
@@ -35,6 +37,15 @@ module Beetle
|
|
35
37
|
# handler timeout.
|
36
38
|
attr_accessor :redis_failover_timeout
|
37
39
|
|
40
|
+
# how long we want status keys to survive after we have seen the second message of a
|
41
|
+
# redundant message pair. Defaults to 5 minutes. Setting this to a high value (hours)
|
42
|
+
# will reduce the likelihood of executing handler logic more than once, but can cause
|
43
|
+
# a higher redis database size with all associated problems. A handler can be
|
44
|
+
# executed more than once if the ack to the RabbitMQ server gets lost between the consumer
|
45
|
+
# and the RabbitMQ instance. This happens extremely seldom in our environment, but we cannot
|
46
|
+
# rule it out and we also don't quite understand (yet) why it happens.
|
47
|
+
attr_accessor :redis_status_key_expiry_interval
|
48
|
+
|
38
49
|
# how often heartbeat messages are exchanged between failover
|
39
50
|
# daemons. defaults to 10 seconds.
|
40
51
|
attr_accessor :redis_failover_client_heartbeat_interval
|
@@ -140,6 +151,7 @@ module Beetle
|
|
140
151
|
self.redis_servers = ""
|
141
152
|
self.redis_db = 4
|
142
153
|
self.redis_failover_timeout = 180.seconds
|
154
|
+
self.redis_status_key_expiry_interval = 5.minutes
|
143
155
|
self.redis_failover_client_heartbeat_interval = 10.seconds
|
144
156
|
self.redis_failover_client_dead_interval = 60.seconds
|
145
157
|
|
@@ -30,7 +30,8 @@ module Beetle
|
|
30
30
|
redis
|
31
31
|
end
|
32
32
|
|
33
|
-
# list of key suffixes to use for storing values in Redis.
|
33
|
+
# list of key suffixes to use for storing values in Redis. 'status'
|
34
|
+
# always needs to be the first element of the array.
|
34
35
|
KEY_SUFFIXES = [:status, :ack_count, :timeout, :delay, :attempts, :exceptions, :mutex, :expires]
|
35
36
|
|
36
37
|
# build a Redis key out of a message id and a given suffix
|
@@ -58,6 +59,19 @@ module Beetle
|
|
58
59
|
with_failover { redis.setnx(key(msg_id, suffix), value) }
|
59
60
|
end
|
60
61
|
|
62
|
+
# store completion status for given <tt>msg_id</tt> if it doesn't exist yet. Returns whether the
|
63
|
+
# operation was successful.
|
64
|
+
def setnx_completed!(msg_id)
|
65
|
+
with_failover do
|
66
|
+
redis.set(
|
67
|
+
key(msg_id, :status),
|
68
|
+
"completed",
|
69
|
+
:nx => true,
|
70
|
+
:ex => @config.redis_status_key_expiry_interval,
|
71
|
+
)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
61
75
|
# store some key/value pairs
|
62
76
|
def mset(msg_id, values)
|
63
77
|
values = values.inject([]){|a,(k,v)| a.concat([key(msg_id, k), v])}
|
@@ -93,7 +107,12 @@ module Beetle
|
|
93
107
|
|
94
108
|
# delete all keys associated with the given <tt>msg_id</tt>.
|
95
109
|
def del_keys(msg_id)
|
96
|
-
|
110
|
+
keys = keys(msg_id)
|
111
|
+
status_key = keys.shift
|
112
|
+
with_failover do
|
113
|
+
redis.del(*keys)
|
114
|
+
redis.expire(status_key, @config.redis_status_key_expiry_interval)
|
115
|
+
end
|
97
116
|
end
|
98
117
|
|
99
118
|
# check whether key with given suffix exists for a given <tt>msg_id</tt>.
|
@@ -128,7 +147,7 @@ module Beetle
|
|
128
147
|
|
129
148
|
# set current redis master instance (as specified in the Beetle::Configuration)
|
130
149
|
def redis_master_from_server_string
|
131
|
-
@current_master ||= Redis.from_server_string(@config.redis_server, :db => @config.redis_db)
|
150
|
+
@current_master ||= Redis.from_server_string(@config.redis_server, :db => @config.redis_db, :logger => @config.redis_logger)
|
132
151
|
end
|
133
152
|
|
134
153
|
# set current redis master from master file
|
@@ -146,7 +165,7 @@ module Beetle
|
|
146
165
|
def set_current_redis_master_from_master_file
|
147
166
|
@last_time_master_file_changed = File.mtime(@config.redis_server)
|
148
167
|
server_string = extract_redis_master(read_master_file)
|
149
|
-
@current_master = !server_string.blank? ? Redis.from_server_string(server_string, :db => @config.redis_db) : nil
|
168
|
+
@current_master = !server_string.blank? ? Redis.from_server_string(server_string, :db => @config.redis_db, :logger => @config.redis_logger) : nil
|
150
169
|
end
|
151
170
|
|
152
171
|
# extract redis master from file content and return the server for our system
|
data/lib/beetle/message.rb
CHANGED
@@ -265,7 +265,7 @@ module Beetle
|
|
265
265
|
Beetle::reraise_expectation_errors!
|
266
266
|
logger.error "Beetle: preprocessing error #{@pre_exception.class}(#{@pre_exception}) for #{msg_id}"
|
267
267
|
end
|
268
|
-
logger.debug "Beetle: processing message #{msg_id}(#{timestamp})"
|
268
|
+
logger.debug "Beetle: processing message #{msg_id}(#{timestamp}) redelivered: #{header.redelivered?}"
|
269
269
|
begin
|
270
270
|
result = process_internal(handler)
|
271
271
|
handler.process_exception(@exception || @pre_exception) if (@exception || @pre_exception)
|
@@ -294,7 +294,11 @@ module Beetle
|
|
294
294
|
RC::Ancient
|
295
295
|
elsif simple?
|
296
296
|
ack!
|
297
|
-
|
297
|
+
if @store.setnx_completed!(msg_id)
|
298
|
+
run_handler(handler) == RC::HandlerCrash ? RC::AttemptsLimitReached : RC::OK
|
299
|
+
else
|
300
|
+
RC::OK
|
301
|
+
end
|
298
302
|
elsif !key_exists?
|
299
303
|
run_handler!(handler)
|
300
304
|
else
|
@@ -383,7 +387,7 @@ module Beetle
|
|
383
387
|
#:doc:
|
384
388
|
logger.debug "Beetle: ack! for message #{msg_id}"
|
385
389
|
header.ack
|
386
|
-
return if simple?
|
390
|
+
return if simple?
|
387
391
|
if !redundant? || @store.incr(msg_id, :ack_count) >= 2
|
388
392
|
# we test for >= 2 here, because we retry increments in the
|
389
393
|
# dedup store so the counter could be greater than two
|
data/lib/beetle/version.rb
CHANGED
data/test/beetle/message_test.rb
CHANGED
@@ -115,7 +115,8 @@ module Beetle
|
|
115
115
|
|
116
116
|
class KeyManagementTest < Minitest::Test
|
117
117
|
def setup
|
118
|
-
@
|
118
|
+
@config = Configuration.new
|
119
|
+
@store = DeduplicationStore.new(@config)
|
119
120
|
@store.flushdb
|
120
121
|
@null_handler = Handler.create(lambda {|*args|})
|
121
122
|
end
|
@@ -128,33 +129,42 @@ module Beetle
|
|
128
129
|
end
|
129
130
|
end
|
130
131
|
|
131
|
-
test "successful processing of a non redundant message should delete all keys from the database" do
|
132
|
+
test "successful processing of a non redundant message should delete all keys from the database (except the status key, which should be set to expire)" do
|
132
133
|
header = header_with_params({})
|
133
134
|
header.expects(:ack)
|
134
135
|
message = Message.new("somequeue", header, 'foo', :store => @store)
|
136
|
+
message.stubs(:simple?).returns(false)
|
135
137
|
|
136
138
|
assert !message.expired?
|
137
139
|
assert !message.redundant?
|
138
140
|
|
139
141
|
message.process(@null_handler)
|
140
|
-
|
141
|
-
|
142
|
+
keys = @store.keys(message.msg_id)
|
143
|
+
status_key = keys.shift
|
144
|
+
assert @store.redis.exists(status_key)
|
145
|
+
assert @store.redis.ttl(status_key) <= @config.redis_status_key_expiry_interval
|
146
|
+
keys.each do |key|
|
142
147
|
assert !@store.redis.exists(key)
|
143
148
|
end
|
144
149
|
end
|
145
150
|
|
146
|
-
test "
|
151
|
+
test "successful processing of a redundant message twice should delete all keys from the database (except the status key, which should be set to expire)" do
|
147
152
|
header = header_with_params({:redundant => true})
|
148
153
|
header.expects(:ack).twice
|
149
154
|
message = Message.new("somequeue", header, 'foo', :store => @store)
|
150
155
|
|
151
156
|
assert !message.expired?
|
152
157
|
assert message.redundant?
|
158
|
+
assert !message.simple?
|
153
159
|
|
154
160
|
message.process(@null_handler)
|
155
161
|
message.process(@null_handler)
|
156
162
|
|
157
|
-
@store.keys(message.msg_id)
|
163
|
+
keys = @store.keys(message.msg_id)
|
164
|
+
status_key = keys.shift
|
165
|
+
assert @store.redis.exists(status_key)
|
166
|
+
assert @store.redis.ttl(status_key) <= @config.redis_status_key_expiry_interval
|
167
|
+
keys.each do |key|
|
158
168
|
assert !@store.redis.exists(key)
|
159
169
|
end
|
160
170
|
end
|
@@ -294,10 +304,9 @@ module Beetle
|
|
294
304
|
def setup
|
295
305
|
@store = DeduplicationStore.new
|
296
306
|
@store.flushdb
|
297
|
-
@store.expects(:redis).never
|
298
307
|
end
|
299
308
|
|
300
|
-
test "when processing a simple message, ack should
|
309
|
+
test "when processing a simple message, ack should follow calling the handler" do
|
301
310
|
header = header_with_params({})
|
302
311
|
message = Message.new("somequeue", header, 'foo', :attempts => 1, :store => @store)
|
303
312
|
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: beetle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.3.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Kaes
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2019-07-
|
15
|
+
date: 2019-07-17 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: bunny
|