eventq 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/README.md +8 -9
- data/lib/eventq/eventq_aws/aws_calculate_visibility_timeout.rb +53 -25
- data/lib/eventq/eventq_aws/aws_queue_worker.rb +16 -10
- data/lib/eventq/eventq_base/queue.rb +6 -0
- data/lib/eventq/eventq_rabbitmq/rabbitmq_queue_worker.rb +26 -10
- data/lib/eventq.rb +2 -3
- metadata +29 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 830a90e777c5896d7d7d7f7d02bf400ec53b2e46d37235956f489719742724f1
|
4
|
+
data.tar.gz: 9d874da06b6190367a382ddac2cfc0dafe937bb622136474bf2430d32805af34
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ef60aecf44e22fe3ed07efd0e7d86205e3191c75c020e9a722715334598d284445fee1548cab1873868128f28556bc30fe9f5810628cb388d17add6c20bc4d9
|
7
|
+
data.tar.gz: 2157ca99b534f9d34156f444da0343814bc7f415649b7aeda6ae9d6c4e97d3b2ce131549bff9ebf57973a6d859fa95d03b89c2dbbd22d18dc3c4c84c04c8a370
|
data/README.md
CHANGED
@@ -54,6 +54,7 @@ A subscription queue should be defined to receive any events raised for the subs
|
|
54
54
|
|
55
55
|
- **allow_retry** [Bool] [Optional] [Default=false] This determines if the queue should allow processing failures to be retried.
|
56
56
|
- **allow_retry_back_off** [Bool] [Optional] [Default=false] This is used to specify if failed messages that retry should incrementally backoff.
|
57
|
+
- **allow_exponential_back_off** [Bool] [Optional] [Default=false] This is used to specify if failed messages that retry should expontentially backoff.
|
57
58
|
- **retry_back_off_grace** [Int] [Optional] [Default=0] This is the number of times to allow retries without applying retry back off if enabled.
|
58
59
|
- **dlq** [EventQ::Queue] [Optional] [Default=nil] A queue that will receive the messages which were not successfully processed after maximum number of receives by consumers. This is created at the same time as the parent queue.
|
59
60
|
- **max_retry_attempts** [Int] [Optional] [Default=5] This is used to specify the max number of times an event should be allowed to retry before failing.
|
@@ -63,6 +64,7 @@ A subscription queue should be defined to receive any events raised for the subs
|
|
63
64
|
- **require_signature** [Bool] [Optional] [Default=false] This is used to specify if messages within this queue must be signed.
|
64
65
|
- **retry_delay** [Int] [Optional] [Default=30000] This is used to specify the time delay in milliseconds before a failed message is re-added to the subscription queue.
|
65
66
|
- **retry_back_off_weight** [Int] [Optional] [Default=1] Additional multiplier for the timeout backoff. Normally used when `retry_delay` is too small (eg: 30ms) in order to get meaningful backoff values.
|
67
|
+
- **retry_jitter_ratio** [Int] [Optional] [Default=0] Amount of randomness for retry delays in percent to avoid a bulk of retries hitting again at the same time. 0% means no randomness, while 100% means full randomness. With full randomness, a random number between 0 and the calculated retry delay will be chosen for the delay.
|
66
68
|
|
67
69
|
**Example**
|
68
70
|
|
@@ -72,7 +74,7 @@ class DataChangeAddressQueue < Queue
|
|
72
74
|
def initialize
|
73
75
|
@name = 'Data.Change.Address'
|
74
76
|
@allow_retry = true
|
75
|
-
@retry_delay =
|
77
|
+
@retry_delay = 20_000
|
76
78
|
@max_retry_attempts = 3
|
77
79
|
end
|
78
80
|
end
|
@@ -109,10 +111,10 @@ This method is called to unsubscribe a queue.
|
|
109
111
|
|
110
112
|
**Example**
|
111
113
|
|
112
|
-
#create an
|
114
|
+
#create an instance of the queue definition
|
113
115
|
queue = DateChangeAddressQueue.new
|
114
116
|
|
115
|
-
#
|
117
|
+
#unsubscribe the queue definition
|
116
118
|
subscription_manager.unsubscribe(queue)
|
117
119
|
|
118
120
|
|
@@ -330,8 +332,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
330
332
|
|
331
333
|
Run the setup script of eventq to build the environment. This will create the `eventq` image.
|
332
334
|
|
333
|
-
$
|
334
|
-
$ ./setup.sh
|
335
|
+
$ ./script/setup.sh
|
335
336
|
|
336
337
|
### Running the tests
|
337
338
|
|
@@ -345,13 +346,11 @@ You will also need to comment out the AWS_* environment variables in the `docker
|
|
345
346
|
|
346
347
|
Run the whole test suite:
|
347
348
|
|
348
|
-
$
|
349
|
-
$ ./test.sh
|
349
|
+
$ ./script/test.sh
|
350
350
|
|
351
351
|
You can run the specs that don't depend on an AWS account with:
|
352
352
|
|
353
|
-
$
|
354
|
-
$ ./test.sh --tag ~integration
|
353
|
+
$ ./script/test.sh --tag ~integration
|
355
354
|
|
356
355
|
## Contributing
|
357
356
|
|
@@ -5,7 +5,7 @@ module EventQ
|
|
5
5
|
# Class responsible to know how to calculate message Visibility Timeout for Amazon SQS
|
6
6
|
class CalculateVisibilityTimeout
|
7
7
|
def initialize(max_timeout:, logger: EventQ.logger)
|
8
|
-
@max_timeout = max_timeout
|
8
|
+
@max_timeout = seconds_to_ms(max_timeout)
|
9
9
|
@logger = logger
|
10
10
|
end
|
11
11
|
|
@@ -14,28 +14,33 @@ module EventQ
|
|
14
14
|
# @param retry_attempts [Integer] Current retry
|
15
15
|
# @param queue_settings [Hash] Queue settings
|
16
16
|
# @option allow_retry_back_off [Bool] Enables/Disables backoff strategy
|
17
|
+
# @option allow_exponential_back_off [Bool] Enables/Disables exponential backoff strategy
|
17
18
|
# @option max_retry_delay [Integer] Maximum amount of time a retry will take in ms
|
18
19
|
# @option retry_back_off_grace [Integer] Amount of retries to wait before starting to backoff
|
19
20
|
# @option retry_back_off_weight [Integer] Multiplier for the backoff retry
|
21
|
+
# @option retry_jitter_ratio [Integer] Ratio of how much jitter to apply to the backoff retry
|
20
22
|
# @option retry_delay [Integer] Amount of time to wait until retry in ms
|
21
23
|
# @return [Integer] the calculated visibility timeout in seconds
|
22
24
|
def call(retry_attempts:, queue_settings:)
|
23
|
-
@retry_attempts
|
25
|
+
@retry_attempts = retry_attempts
|
24
26
|
|
25
|
-
@allow_retry_back_off
|
26
|
-
@
|
27
|
-
@
|
28
|
-
@
|
29
|
-
@
|
27
|
+
@allow_retry_back_off = queue_settings.fetch(:allow_retry_back_off)
|
28
|
+
@allow_exponential_back_off = queue_settings.fetch(:allow_exponential_back_off)
|
29
|
+
@max_retry_delay = queue_settings.fetch(:max_retry_delay)
|
30
|
+
@retry_back_off_grace = queue_settings.fetch(:retry_back_off_grace)
|
31
|
+
@retry_back_off_weight = queue_settings.fetch(:retry_back_off_weight)
|
32
|
+
@retry_jitter_ratio = queue_settings.fetch(:retry_jitter_ratio)
|
33
|
+
@retry_delay = queue_settings.fetch(:retry_delay)
|
30
34
|
|
31
|
-
if @allow_retry_back_off && retry_past_grace_period?
|
32
|
-
|
33
|
-
visibility_timeout = check_for_max_timeout(visibility_timeout)
|
35
|
+
visibility_timeout = if @allow_retry_back_off && retry_past_grace_period?
|
36
|
+
timeout_with_back_off
|
34
37
|
else
|
35
|
-
|
38
|
+
timeout_without_back_off
|
36
39
|
end
|
37
40
|
|
38
|
-
visibility_timeout
|
41
|
+
visibility_timeout = apply_jitter(visibility_timeout) if @retry_jitter_ratio > 0
|
42
|
+
|
43
|
+
ms_to_seconds(visibility_timeout)
|
39
44
|
end
|
40
45
|
|
41
46
|
private
|
@@ -47,34 +52,57 @@ module EventQ
|
|
47
52
|
end
|
48
53
|
|
49
54
|
def timeout_without_back_off
|
50
|
-
|
55
|
+
@retry_delay
|
51
56
|
end
|
52
57
|
|
53
58
|
def timeout_with_back_off
|
54
59
|
factor = @retry_attempts - @retry_back_off_grace
|
60
|
+
weighted_retry_delay = @retry_delay * @retry_back_off_weight
|
55
61
|
|
56
|
-
visibility_timeout =
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
logger.debug { "[#{self.class}] - Max message back off retry delay reached: #{max_retry_delay}" }
|
61
|
-
visibility_timeout = max_retry_delay
|
62
|
+
visibility_timeout = if @allow_exponential_back_off
|
63
|
+
weighted_retry_delay * 2 ** (factor - 1)
|
64
|
+
else
|
65
|
+
weighted_retry_delay * factor
|
62
66
|
end
|
63
67
|
|
64
|
-
visibility_timeout
|
68
|
+
visibility_timeout = check_for_max_retry_delay(visibility_timeout)
|
69
|
+
check_for_max_timeout(visibility_timeout)
|
70
|
+
end
|
71
|
+
|
72
|
+
def apply_jitter(visibility_timeout)
|
73
|
+
ratio = @retry_jitter_ratio / 100.0
|
74
|
+
min_visibility_timeout = (visibility_timeout * (1 - ratio)).to_i
|
75
|
+
rand(min_visibility_timeout..visibility_timeout)
|
65
76
|
end
|
66
77
|
|
67
78
|
def ms_to_seconds(value)
|
68
79
|
value / 1000
|
69
80
|
end
|
70
81
|
|
82
|
+
def seconds_to_ms(value)
|
83
|
+
value * 1000
|
84
|
+
end
|
85
|
+
|
86
|
+
def check_for_max_retry_delay(visibility_timeout)
|
87
|
+
return visibility_timeout if visibility_timeout <= @max_retry_delay
|
88
|
+
|
89
|
+
logger.debug do
|
90
|
+
"[#{self.class}] - Max message back off retry delay reached: #{ms_to_seconds(@max_retry_delay)}"
|
91
|
+
end
|
92
|
+
|
93
|
+
@max_retry_delay
|
94
|
+
end
|
95
|
+
|
71
96
|
def check_for_max_timeout(visibility_timeout)
|
72
|
-
if visibility_timeout
|
73
|
-
|
74
|
-
|
97
|
+
return visibility_timeout if visibility_timeout <= @max_timeout
|
98
|
+
|
99
|
+
logger.debug do
|
100
|
+
"[#{self.class}] - AWS max visibility timeout of 12 hours has been exceeded. "\
|
101
|
+
"Setting message retry delay to 12 hours."
|
75
102
|
end
|
76
|
-
|
103
|
+
|
104
|
+
@max_timeout
|
77
105
|
end
|
78
106
|
end
|
79
107
|
end
|
80
|
-
end
|
108
|
+
end
|
@@ -119,16 +119,7 @@ module EventQ
|
|
119
119
|
|
120
120
|
EventQ.logger.warn("[#{self.class}] - Message Id: #{args.id}. Rejected requesting retry. Attempts: #{retry_attempts}")
|
121
121
|
|
122
|
-
visibility_timeout =
|
123
|
-
retry_attempts: retry_attempts,
|
124
|
-
queue_settings: {
|
125
|
-
allow_retry_back_off: queue.allow_retry_back_off,
|
126
|
-
max_retry_delay: queue.max_retry_delay,
|
127
|
-
retry_back_off_grace: queue.retry_back_off_grace,
|
128
|
-
retry_back_off_weight: queue.retry_back_off_weight,
|
129
|
-
retry_delay: queue.retry_delay
|
130
|
-
}
|
131
|
-
)
|
122
|
+
visibility_timeout = calculate_visibility_timeout(retry_attempts, queue)
|
132
123
|
|
133
124
|
EventQ.logger.debug { "[#{self.class}] - Sending message for retry. Message TTL: #{visibility_timeout}" }
|
134
125
|
poller.change_message_visibility_timeout(msg, visibility_timeout)
|
@@ -136,6 +127,21 @@ module EventQ
|
|
136
127
|
context.call_on_retry_block(message)
|
137
128
|
end
|
138
129
|
end
|
130
|
+
|
131
|
+
def calculate_visibility_timeout(retry_attempts, queue)
|
132
|
+
@calculate_visibility_timeout.call(
|
133
|
+
retry_attempts: retry_attempts,
|
134
|
+
queue_settings: {
|
135
|
+
allow_retry_back_off: queue.allow_retry_back_off,
|
136
|
+
allow_exponential_back_off: queue.allow_exponential_back_off,
|
137
|
+
max_retry_delay: queue.max_retry_delay,
|
138
|
+
retry_back_off_grace: queue.retry_back_off_grace,
|
139
|
+
retry_back_off_weight: queue.retry_back_off_weight,
|
140
|
+
retry_jitter_ratio: queue.retry_jitter_ratio,
|
141
|
+
retry_delay: queue.retry_delay
|
142
|
+
}
|
143
|
+
)
|
144
|
+
end
|
139
145
|
end
|
140
146
|
end
|
141
147
|
end
|
@@ -2,6 +2,7 @@ module EventQ
|
|
2
2
|
class Queue
|
3
3
|
attr_accessor :allow_retry
|
4
4
|
attr_accessor :allow_retry_back_off
|
5
|
+
attr_accessor :allow_exponential_back_off
|
5
6
|
attr_accessor :dlq
|
6
7
|
attr_accessor :max_retry_attempts
|
7
8
|
attr_accessor :max_retry_delay
|
@@ -11,6 +12,7 @@ module EventQ
|
|
11
12
|
attr_accessor :retry_delay
|
12
13
|
attr_accessor :retry_back_off_grace
|
13
14
|
attr_accessor :retry_back_off_weight
|
15
|
+
attr_accessor :retry_jitter_ratio
|
14
16
|
# Character delimiter between namespace and queue name. Default = '-'
|
15
17
|
attr_accessor :namespace_delimiter
|
16
18
|
# Flag to control that the queue runs in isolation of auto creating the topic it belongs to
|
@@ -20,6 +22,8 @@ module EventQ
|
|
20
22
|
@allow_retry = false
|
21
23
|
# Default retry back off settings
|
22
24
|
@allow_retry_back_off = false
|
25
|
+
# Default exponential back off settings
|
26
|
+
@allow_exponential_back_off = false
|
23
27
|
# Default max receive count is 30
|
24
28
|
@max_receive_count = 30
|
25
29
|
# Default max retry attempts is 5
|
@@ -34,6 +38,8 @@ module EventQ
|
|
34
38
|
@retry_back_off_grace = 0
|
35
39
|
# Multiplier for the backoff retry in case retry_delay is too small
|
36
40
|
@retry_back_off_weight = 1
|
41
|
+
# Ratio of how much jitter to apply to the retry delay
|
42
|
+
@retry_jitter_ratio = 0
|
37
43
|
@isolated = false
|
38
44
|
end
|
39
45
|
end
|
@@ -78,16 +78,7 @@ module EventQ
|
|
78
78
|
message.retry_attempts += 1
|
79
79
|
retry_attempts = message.retry_attempts - queue.retry_back_off_grace
|
80
80
|
retry_attempts = 1 if retry_attempts < 1
|
81
|
-
|
82
|
-
if queue.allow_retry_back_off == true
|
83
|
-
message_ttl = retry_attempts * queue.retry_delay
|
84
|
-
if (retry_attempts * queue.retry_delay) > queue.max_retry_delay
|
85
|
-
EventQ.logger.debug { "[#{self.class}] - Max message back off retry delay reached." }
|
86
|
-
message_ttl = queue.max_retry_delay
|
87
|
-
end
|
88
|
-
else
|
89
|
-
message_ttl = queue.retry_delay
|
90
|
-
end
|
81
|
+
message_ttl = retry_delay(queue, retry_attempts)
|
91
82
|
|
92
83
|
EventQ.logger.debug { "[#{self.class}] - Sending message for retry. Message TTL: #{message_ttl}" }
|
93
84
|
retry_exchange.publish(serialize_message(message), :expiration => message_ttl)
|
@@ -128,6 +119,31 @@ module EventQ
|
|
128
119
|
raise "Unrecognized status: #{status}"
|
129
120
|
end
|
130
121
|
end
|
122
|
+
|
123
|
+
def retry_delay(queue, retry_attempts)
|
124
|
+
return queue.retry_delay unless queue.allow_retry_back_off
|
125
|
+
|
126
|
+
message_ttl = if queue.allow_exponential_back_off
|
127
|
+
queue.retry_delay * 2 ** (retry_attempts - 1)
|
128
|
+
else
|
129
|
+
queue.retry_delay * retry_attempts
|
130
|
+
end
|
131
|
+
|
132
|
+
if message_ttl > queue.max_retry_delay
|
133
|
+
EventQ.logger.debug { "[#{self.class}] - Max message back off retry delay reached." }
|
134
|
+
message_ttl = queue.max_retry_delay
|
135
|
+
end
|
136
|
+
|
137
|
+
apply_jitter(queue.retry_jitter_ratio, message_ttl)
|
138
|
+
end
|
139
|
+
|
140
|
+
def apply_jitter(retry_jitter_ratio, message_ttl)
|
141
|
+
return message_ttl if retry_jitter_ratio == 0
|
142
|
+
|
143
|
+
ratio = retry_jitter_ratio / 100.0
|
144
|
+
min_message_ttl = (message_ttl * (1 - ratio)).to_i
|
145
|
+
rand(min_message_ttl..message_ttl)
|
146
|
+
end
|
131
147
|
end
|
132
148
|
end
|
133
149
|
end
|
data/lib/eventq.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'securerandom'
|
2
4
|
require 'redlock'
|
3
5
|
require 'class_kit'
|
@@ -22,6 +24,3 @@ require_relative 'eventq/eventq_base/nonce_manager'
|
|
22
24
|
require_relative 'eventq/eventq_base/signature_providers'
|
23
25
|
require_relative 'eventq/eventq_base/exceptions'
|
24
26
|
require_relative 'eventq/queue_worker'
|
25
|
-
|
26
|
-
|
27
|
-
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: eventq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- SageOne
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '6'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '6'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '13'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '13'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: rspec
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -123,33 +123,47 @@ dependencies:
|
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: 0.18.0
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
|
-
name: aws-sdk-
|
126
|
+
name: aws-sdk-core
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- - "
|
129
|
+
- - ">="
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
131
|
+
version: '0'
|
132
132
|
type: :runtime
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
|
-
- - "
|
136
|
+
- - ">="
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
138
|
+
version: '0'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: aws-sdk-sns
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
|
-
- - "
|
143
|
+
- - ">="
|
144
144
|
- !ruby/object:Gem::Version
|
145
|
-
version: '
|
145
|
+
version: '0'
|
146
146
|
type: :runtime
|
147
147
|
prerelease: false
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
|
-
- - "
|
150
|
+
- - ">="
|
151
151
|
- !ruby/object:Gem::Version
|
152
|
-
version: '
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: aws-sdk-sqs
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
153
167
|
- !ruby/object:Gem::Dependency
|
154
168
|
name: bunny
|
155
169
|
requirement: !ruby/object:Gem::Requirement
|