waterdrop 2.6.7 → 2.6.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci.yml +16 -9
- data/CHANGELOG.md +17 -8
- data/Gemfile.lock +6 -6
- data/README.md +19 -500
- data/docker-compose.yml +19 -12
- data/lib/waterdrop/clients/buffered.rb +59 -3
- data/lib/waterdrop/clients/dummy.rb +20 -0
- data/lib/waterdrop/clients/rdkafka.rb +6 -1
- data/lib/waterdrop/config.rb +5 -0
- data/lib/waterdrop/contracts/message.rb +4 -1
- data/lib/waterdrop/errors.rb +3 -0
- data/lib/waterdrop/instrumentation/callbacks/delivery.rb +6 -4
- data/lib/waterdrop/instrumentation/logger_listener.rb +21 -1
- data/lib/waterdrop/instrumentation/notifications.rb +5 -0
- data/lib/waterdrop/producer/async.rb +4 -2
- data/lib/waterdrop/producer/sync.rb +4 -2
- data/lib/waterdrop/producer/transactions.rb +165 -0
- data/lib/waterdrop/producer.rb +42 -3
- data/lib/waterdrop/version.rb +1 -1
- data/waterdrop.gemspec +3 -3
- data.tar.gz.sig +0 -0
- metadata +7 -6
- metadata.gz.sig +0 -0
@@ -25,6 +25,26 @@ module WaterDrop
|
|
25
25
|
true
|
26
26
|
end
|
27
27
|
|
28
|
+
# Yields the code pretending it is in a transaction
|
29
|
+
# Supports our aborting transaction flow
|
30
|
+
def transaction
|
31
|
+
result = nil
|
32
|
+
commit = false
|
33
|
+
|
34
|
+
catch(:abort) do
|
35
|
+
result = yield
|
36
|
+
commit = true
|
37
|
+
end
|
38
|
+
|
39
|
+
commit || raise(WaterDrop::Errors::AbortTransaction)
|
40
|
+
|
41
|
+
result
|
42
|
+
rescue StandardError => e
|
43
|
+
return if e.is_a?(WaterDrop::Errors::AbortTransaction)
|
44
|
+
|
45
|
+
raise
|
46
|
+
end
|
47
|
+
|
28
48
|
# @param _args [Object] anything really, this dummy is suppose to support anything
|
29
49
|
# @return [self] returns self for chaining cases
|
30
50
|
def method_missing(*_args)
|
@@ -11,7 +11,9 @@ module WaterDrop
|
|
11
11
|
# @param producer [WaterDrop::Producer] producer instance with its config, etc
|
12
12
|
# @note We overwrite this that way, because we do not care
|
13
13
|
def new(producer)
|
14
|
-
|
14
|
+
config = producer.config.kafka.to_h
|
15
|
+
|
16
|
+
client = ::Rdkafka::Config.new(config).producer
|
15
17
|
|
16
18
|
# This callback is not global and is per client, thus we do not have to wrap it with a
|
17
19
|
# callbacks manager to make it work
|
@@ -20,6 +22,9 @@ module WaterDrop
|
|
20
22
|
producer.config.monitor
|
21
23
|
)
|
22
24
|
|
25
|
+
# Switch to the transactional mode if user provided the transactional id
|
26
|
+
client.init_transactions if config.key?(:'transactional.id')
|
27
|
+
|
23
28
|
client
|
24
29
|
end
|
25
30
|
end
|
data/lib/waterdrop/config.rb
CHANGED
@@ -64,6 +64,11 @@ module WaterDrop
|
|
64
64
|
# option [Numeric] how many seconds should we wait with the backoff on queue having space for
|
65
65
|
# more messages before re-raising the error.
|
66
66
|
setting :wait_timeout_on_queue_full, default: 10
|
67
|
+
|
68
|
+
setting :wait_backoff_on_transaction_command, default: 0.5
|
69
|
+
|
70
|
+
setting :max_attempts_on_transaction_command, default: 5
|
71
|
+
|
67
72
|
# option [Boolean] should we send messages. Setting this to false can be really useful when
|
68
73
|
# testing and or developing because when set to false, won't actually ping Kafka but will
|
69
74
|
# run all the validations, etc
|
@@ -26,7 +26,10 @@ module WaterDrop
|
|
26
26
|
@max_payload_size = max_payload_size
|
27
27
|
end
|
28
28
|
|
29
|
-
required(:topic)
|
29
|
+
required(:topic) do |val|
|
30
|
+
(val.is_a?(String) || val.is_a?(Symbol)) && TOPIC_REGEXP.match?(val.to_s)
|
31
|
+
end
|
32
|
+
|
30
33
|
required(:payload) { |val| val.nil? || val.is_a?(String) }
|
31
34
|
optional(:key) { |val| val.nil? || (val.is_a?(String) && !val.empty?) }
|
32
35
|
optional(:partition) { |val| val.is_a?(Integer) && val >= -1 }
|
data/lib/waterdrop/errors.rb
CHANGED
@@ -32,6 +32,9 @@ module WaterDrop
|
|
32
32
|
# Raised when there is an inline error during single message produce operations
|
33
33
|
ProduceError = Class.new(BaseError)
|
34
34
|
|
35
|
+
# Raise it within a transaction to abort it
|
36
|
+
AbortTransaction = Class.new(BaseError)
|
37
|
+
|
35
38
|
# Raised when during messages producing something bad happened inline
|
36
39
|
class ProduceManyError < ProduceError
|
37
40
|
attr_reader :dispatched
|
@@ -17,10 +17,10 @@ module WaterDrop
|
|
17
17
|
# Emits delivery details to the monitor
|
18
18
|
# @param delivery_report [Rdkafka::Producer::DeliveryReport] delivery report
|
19
19
|
def call(delivery_report)
|
20
|
-
if delivery_report.error.to_i.
|
21
|
-
instrument_error(delivery_report)
|
22
|
-
else
|
20
|
+
if delivery_report.error.to_i.zero?
|
23
21
|
instrument_acknowledged(delivery_report)
|
22
|
+
else
|
23
|
+
instrument_error(delivery_report)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -36,6 +36,7 @@ module WaterDrop
|
|
36
36
|
offset: delivery_report.offset,
|
37
37
|
partition: delivery_report.partition,
|
38
38
|
topic: delivery_report.topic_name,
|
39
|
+
delivery_report: delivery_report,
|
39
40
|
type: 'librdkafka.dispatch_error'
|
40
41
|
)
|
41
42
|
end
|
@@ -47,7 +48,8 @@ module WaterDrop
|
|
47
48
|
producer_id: @producer_id,
|
48
49
|
offset: delivery_report.offset,
|
49
50
|
partition: delivery_report.partition,
|
50
|
-
topic: delivery_report.topic_name
|
51
|
+
topic: delivery_report.topic_name,
|
52
|
+
delivery_report: delivery_report
|
51
53
|
)
|
52
54
|
end
|
53
55
|
end
|
@@ -112,9 +112,14 @@ module WaterDrop
|
|
112
112
|
debug(event, messages)
|
113
113
|
end
|
114
114
|
|
115
|
+
# @param event [Dry::Events::Event] event that happened with the details
|
116
|
+
def on_buffer_purged(event)
|
117
|
+
info(event, 'Successfully purging buffer')
|
118
|
+
end
|
119
|
+
|
115
120
|
# @param event [Dry::Events::Event] event that happened with the details
|
116
121
|
def on_producer_closed(event)
|
117
|
-
info
|
122
|
+
info(event, 'Closing producer')
|
118
123
|
end
|
119
124
|
|
120
125
|
# @param event [Dry::Events::Event] event that happened with the error details
|
@@ -125,6 +130,21 @@ module WaterDrop
|
|
125
130
|
error(event, "Error occurred: #{error} - #{type}")
|
126
131
|
end
|
127
132
|
|
133
|
+
# @param event [Dry::Events::Event] event that happened with the details
|
134
|
+
def on_transaction_started(event)
|
135
|
+
info(event, 'Starting transaction')
|
136
|
+
end
|
137
|
+
|
138
|
+
# @param event [Dry::Events::Event] event that happened with the details
|
139
|
+
def on_transaction_aborted(event)
|
140
|
+
info(event, 'Aborting transaction')
|
141
|
+
end
|
142
|
+
|
143
|
+
# @param event [Dry::Events::Event] event that happened with the details
|
144
|
+
def on_transaction_committed(event)
|
145
|
+
info(event, 'Committing transaction')
|
146
|
+
end
|
147
|
+
|
128
148
|
private
|
129
149
|
|
130
150
|
# @return [Boolean] should we report the messages details in the debug mode.
|
@@ -60,8 +60,10 @@ module WaterDrop
|
|
60
60
|
producer_id: id,
|
61
61
|
messages: messages
|
62
62
|
) do
|
63
|
-
|
64
|
-
|
63
|
+
with_transaction_if_transactional do
|
64
|
+
messages.each do |message|
|
65
|
+
dispatched << produce(message)
|
66
|
+
end
|
65
67
|
end
|
66
68
|
|
67
69
|
dispatched
|
@@ -63,8 +63,10 @@ module WaterDrop
|
|
63
63
|
dispatched = []
|
64
64
|
|
65
65
|
@monitor.instrument('messages.produced_sync', producer_id: id, messages: messages) do
|
66
|
-
|
67
|
-
|
66
|
+
with_transaction_if_transactional do
|
67
|
+
messages.each do |message|
|
68
|
+
dispatched << produce(message)
|
69
|
+
end
|
68
70
|
end
|
69
71
|
|
70
72
|
dispatched.map! do |handler|
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WaterDrop
|
4
|
+
class Producer
|
5
|
+
# Transactions related producer functionalities
|
6
|
+
module Transactions
|
7
|
+
# Creates a transaction.
|
8
|
+
#
|
9
|
+
# Karafka transactions work in a similar manner to SQL db transactions though there are some
|
10
|
+
# crucial differences. When you start a transaction, all messages produced during it will
|
11
|
+
# be delivered together or will fail together. The difference is, that messages from within
|
12
|
+
# a single transaction can be delivered and will have a delivery handle but will be then
|
13
|
+
# compacted prior to moving the LSO forward. This means, that not every delivery handle for
|
14
|
+
# async dispatches will emit a queue purge error. None for sync as the delivery has happened
|
15
|
+
# but they will never be visible by the transactional consumers.
|
16
|
+
#
|
17
|
+
# Transactions **are** thread-safe however they lock a mutex. This means, that for
|
18
|
+
# high-throughput transactional messages production in multiple threads
|
19
|
+
# (for example in Karafka), it may be much better to use few instances that can work in
|
20
|
+
# parallel.
|
21
|
+
#
|
22
|
+
# Please note, that if a producer is configured as transactional, it **cannot** produce
|
23
|
+
# messages outside of transactions, that is why by default all dispatches will be wrapped
|
24
|
+
# with a transaction. One transaction per single dispatch and for `produce_many` it will be
|
25
|
+
# a single transaction wrapping all messages dispatches (not one per message).
|
26
|
+
#
|
27
|
+
# @return Block result
|
28
|
+
#
|
29
|
+
# @example Simple transaction
|
30
|
+
# producer.transaction do
|
31
|
+
# producer.produce_async(topic: 'topic', payload: 'data')
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# @example Aborted transaction - messages producer won't be visible by consumers
|
35
|
+
# producer.transaction do
|
36
|
+
# producer.produce_sync(topic: 'topic', payload: 'data')
|
37
|
+
# throw(:abort)
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# @example Use block result last handler to wait on all messages ack
|
41
|
+
# handler = producer.transaction do
|
42
|
+
# producer.produce_async(topic: 'topic', payload: 'data')
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# handler.wait
|
46
|
+
def transaction
|
47
|
+
# This will safely allow us to support one operation transactions so a transactional
|
48
|
+
# producer can work without the transactional block if needed
|
49
|
+
return yield if @transaction_mutex.owned?
|
50
|
+
|
51
|
+
@transaction_mutex.synchronize do
|
52
|
+
transactional_instrument(:committed) do
|
53
|
+
with_transactional_error_handling(:begin) do
|
54
|
+
transactional_instrument(:started) { client.begin_transaction }
|
55
|
+
end
|
56
|
+
|
57
|
+
result = nil
|
58
|
+
commit = false
|
59
|
+
|
60
|
+
catch(:abort) do
|
61
|
+
result = yield
|
62
|
+
commit = true
|
63
|
+
end
|
64
|
+
|
65
|
+
commit || raise(WaterDrop::Errors::AbortTransaction)
|
66
|
+
|
67
|
+
with_transactional_error_handling(:commit) do
|
68
|
+
client.commit_transaction
|
69
|
+
end
|
70
|
+
|
71
|
+
result
|
72
|
+
rescue StandardError => e
|
73
|
+
with_transactional_error_handling(:abort) do
|
74
|
+
transactional_instrument(:aborted) { client.abort_transaction }
|
75
|
+
end
|
76
|
+
|
77
|
+
raise unless e.is_a?(WaterDrop::Errors::AbortTransaction)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Boolean] Is this producer a transactional one
|
83
|
+
def transactional?
|
84
|
+
return @transactional if instance_variable_defined?(:'@transactional')
|
85
|
+
|
86
|
+
@transactional = config.kafka.to_h.key?(:'transactional.id')
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Runs provided code with a transaction wrapper if transactions are enabled.
|
92
|
+
# This allows us to simplify the async and sync batch dispatchers because we can ensure that
|
93
|
+
# their internal dispatches will be wrapped only with a single transaction and not
|
94
|
+
# a transaction per message
|
95
|
+
# @param block [Proc] code we want to run
|
96
|
+
def with_transaction_if_transactional(&block)
|
97
|
+
transactional? ? transaction(&block) : yield
|
98
|
+
end
|
99
|
+
|
100
|
+
# Instruments the transactional operation with producer id
|
101
|
+
#
|
102
|
+
# @param key [Symbol] transaction operation key
|
103
|
+
# @param block [Proc] block to run inside the instrumentation or nothing if not given
|
104
|
+
def transactional_instrument(key, &block)
|
105
|
+
@monitor.instrument("transaction.#{key}", producer_id: id, &block)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Error handling for transactional operations is a bit special. There are three types of
|
109
|
+
# errors coming from librdkafka:
|
110
|
+
# - retryable - indicates that a given operation (like offset commit) can be retried after
|
111
|
+
# a backoff and that is should be operating later as expected. We try to retry those
|
112
|
+
# few times before finally failing.
|
113
|
+
# - fatal - errors that will not recover no matter what (for example being fenced out)
|
114
|
+
# - abortable - error from which we cannot recover but for which we should abort the
|
115
|
+
# current transaction.
|
116
|
+
#
|
117
|
+
# The code below handles this logic also publishing the appropriate notifications via our
|
118
|
+
# notifications pipeline.
|
119
|
+
#
|
120
|
+
# @param action [Symbol] action type
|
121
|
+
# @param allow_abortable [Boolean] should we allow for the abortable flow. This is set to
|
122
|
+
# false internally to prevent attempts to abort from failed abort operations
|
123
|
+
def with_transactional_error_handling(action, allow_abortable: true)
|
124
|
+
attempt ||= 0
|
125
|
+
attempt += 1
|
126
|
+
|
127
|
+
yield
|
128
|
+
rescue ::Rdkafka::RdkafkaError => e
|
129
|
+
# Decide if there is a chance to retry given error
|
130
|
+
do_retry = e.retryable? && attempt < config.max_attempts_on_transaction_command
|
131
|
+
|
132
|
+
@monitor.instrument(
|
133
|
+
'error.occurred',
|
134
|
+
producer_id: id,
|
135
|
+
caller: self,
|
136
|
+
error: e,
|
137
|
+
type: "transaction.#{action}",
|
138
|
+
retry: do_retry,
|
139
|
+
attempt: attempt
|
140
|
+
)
|
141
|
+
|
142
|
+
raise if e.fatal?
|
143
|
+
|
144
|
+
if do_retry
|
145
|
+
# Backoff more and more before retries
|
146
|
+
sleep(config.wait_backoff_on_transaction_command * attempt)
|
147
|
+
|
148
|
+
retry
|
149
|
+
end
|
150
|
+
|
151
|
+
if e.abortable? && allow_abortable
|
152
|
+
# Always attempt to abort but if aborting fails with an abortable error, do not attempt
|
153
|
+
# to abort from abort as this could create an infinite loop
|
154
|
+
with_transactional_error_handling(:abort, allow_abortable: false) do
|
155
|
+
transactional_instrument(:aborted) { @client.abort_transaction }
|
156
|
+
end
|
157
|
+
|
158
|
+
raise
|
159
|
+
end
|
160
|
+
|
161
|
+
raise
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
data/lib/waterdrop/producer.rb
CHANGED
@@ -7,6 +7,7 @@ module WaterDrop
|
|
7
7
|
include Sync
|
8
8
|
include Async
|
9
9
|
include Buffer
|
10
|
+
include Transactions
|
10
11
|
include ::Karafka::Core::Helpers::Time
|
11
12
|
|
12
13
|
# Which of the inline flow errors do we want to intercept and re-bind
|
@@ -38,6 +39,7 @@ module WaterDrop
|
|
38
39
|
@buffer_mutex = Mutex.new
|
39
40
|
@connecting_mutex = Mutex.new
|
40
41
|
@operating_mutex = Mutex.new
|
42
|
+
@transaction_mutex = Mutex.new
|
41
43
|
|
42
44
|
@status = Status.new
|
43
45
|
@messages = Concurrent::Array.new
|
@@ -117,8 +119,25 @@ module WaterDrop
|
|
117
119
|
@client
|
118
120
|
end
|
119
121
|
|
122
|
+
# Purges data from both the buffer queue as well as the librdkafka queue.
|
123
|
+
#
|
124
|
+
# @note This is an operation that can cause data loss. Keep that in mind. It will not only
|
125
|
+
# purge the internal WaterDrop buffer but will also purge the librdkafka queue as well as
|
126
|
+
# will cancel any outgoing messages dispatches.
|
127
|
+
def purge
|
128
|
+
@monitor.instrument('buffer.purged', producer_id: id) do
|
129
|
+
@buffer_mutex.synchronize do
|
130
|
+
@messages = Concurrent::Array.new
|
131
|
+
end
|
132
|
+
|
133
|
+
@client.purge
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
120
137
|
# Flushes the buffers in a sync way and closes the producer
|
121
|
-
|
138
|
+
# @param force [Boolean] should we force closing even with outstanding messages after the
|
139
|
+
# max wait timeout
|
140
|
+
def close(force: false)
|
122
141
|
@operating_mutex.synchronize do
|
123
142
|
return unless @status.active?
|
124
143
|
|
@@ -156,12 +175,19 @@ module WaterDrop
|
|
156
175
|
# `max_wait_timeout` is in seconds at the moment
|
157
176
|
@client.flush(@config.max_wait_timeout * 1_000) unless @client.closed?
|
158
177
|
# We can safely ignore timeouts here because any left outstanding requests
|
159
|
-
# will anyhow force wait on close
|
178
|
+
# will anyhow force wait on close if not forced.
|
179
|
+
# If forced, we will purge the queue and just close
|
160
180
|
rescue ::Rdkafka::RdkafkaError, Rdkafka::AbstractHandle::WaitTimeoutError
|
161
181
|
nil
|
182
|
+
ensure
|
183
|
+
# Purge fully the local queue in case of a forceful shutdown just to be sure, that
|
184
|
+
# there are no dangling messages. In case flush was successful, there should be
|
185
|
+
# none but we do it just in case it timed out
|
186
|
+
purge if force
|
162
187
|
end
|
163
188
|
|
164
189
|
@client.close
|
190
|
+
|
165
191
|
@client = nil
|
166
192
|
end
|
167
193
|
|
@@ -174,6 +200,11 @@ module WaterDrop
|
|
174
200
|
end
|
175
201
|
end
|
176
202
|
|
203
|
+
# Closes the producer with forced close after timeout, purging any outgoing data
|
204
|
+
def close!
|
205
|
+
close(force: true)
|
206
|
+
end
|
207
|
+
|
177
208
|
private
|
178
209
|
|
179
210
|
# Ensures that we don't run any operations when the producer is not configured or when it
|
@@ -223,7 +254,15 @@ module WaterDrop
|
|
223
254
|
ensure_active!
|
224
255
|
end
|
225
256
|
|
226
|
-
|
257
|
+
# In case someone defines topic as a symbol, we need to convert it into a string as
|
258
|
+
# librdkafka does not accept symbols
|
259
|
+
message = message.merge(topic: message[:topic].to_s) if message[:topic].is_a?(Symbol)
|
260
|
+
|
261
|
+
if transactional?
|
262
|
+
transaction { client.produce(**message) }
|
263
|
+
else
|
264
|
+
client.produce(**message)
|
265
|
+
end
|
227
266
|
rescue SUPPORTED_FLOW_ERRORS.first => e
|
228
267
|
# Unless we want to wait and retry and it's a full queue, we raise normally
|
229
268
|
raise unless @config.wait_on_queue_full
|
data/lib/waterdrop/version.rb
CHANGED
data/waterdrop.gemspec
CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.description = spec.summary
|
17
17
|
spec.license = 'MIT'
|
18
18
|
|
19
|
-
spec.add_dependency 'karafka-core', '>= 2.
|
19
|
+
spec.add_dependency 'karafka-core', '>= 2.2.3', '< 3.0.0'
|
20
20
|
spec.add_dependency 'zeitwerk', '~> 2.3'
|
21
21
|
|
22
22
|
if $PROGRAM_NAME.end_with?('gem')
|
@@ -31,10 +31,10 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.metadata = {
|
32
32
|
'funding_uri' => 'https://karafka.io/#become-pro',
|
33
33
|
'homepage_uri' => 'https://karafka.io',
|
34
|
-
'changelog_uri' => 'https://
|
34
|
+
'changelog_uri' => 'https://karafka.io/docs/Changelog-WaterDrop',
|
35
35
|
'bug_tracker_uri' => 'https://github.com/karafka/waterdrop/issues',
|
36
36
|
'source_code_uri' => 'https://github.com/karafka/waterdrop',
|
37
|
-
'documentation_uri' => 'https://
|
37
|
+
'documentation_uri' => 'https://karafka.io/docs/#waterdrop',
|
38
38
|
'rubygems_mfa_required' => 'true'
|
39
39
|
}
|
40
40
|
end
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: waterdrop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.6.
|
4
|
+
version: 2.6.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maciej Mensfeld
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
AnG1dJU+yL2BK7vaVytLTstJME5mepSZ46qqIJXMuWob/YPDmVaBF39TDSG9e34s
|
36
36
|
msG3BiCqgOgHAnL23+CN3Rt8MsuRfEtoTKpJVcCfoEoNHOkc
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date: 2023-
|
38
|
+
date: 2023-10-20 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: karafka-core
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
requirements:
|
44
44
|
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 2.
|
46
|
+
version: 2.2.3
|
47
47
|
- - "<"
|
48
48
|
- !ruby/object:Gem::Version
|
49
49
|
version: 3.0.0
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
requirements:
|
54
54
|
- - ">="
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version: 2.
|
56
|
+
version: 2.2.3
|
57
57
|
- - "<"
|
58
58
|
- !ruby/object:Gem::Version
|
59
59
|
version: 3.0.0
|
@@ -118,6 +118,7 @@ files:
|
|
118
118
|
- lib/waterdrop/producer/builder.rb
|
119
119
|
- lib/waterdrop/producer/status.rb
|
120
120
|
- lib/waterdrop/producer/sync.rb
|
121
|
+
- lib/waterdrop/producer/transactions.rb
|
121
122
|
- lib/waterdrop/version.rb
|
122
123
|
- log/.gitkeep
|
123
124
|
- renovate.json
|
@@ -128,10 +129,10 @@ licenses:
|
|
128
129
|
metadata:
|
129
130
|
funding_uri: https://karafka.io/#become-pro
|
130
131
|
homepage_uri: https://karafka.io
|
131
|
-
changelog_uri: https://
|
132
|
+
changelog_uri: https://karafka.io/docs/Changelog-WaterDrop
|
132
133
|
bug_tracker_uri: https://github.com/karafka/waterdrop/issues
|
133
134
|
source_code_uri: https://github.com/karafka/waterdrop
|
134
|
-
documentation_uri: https://
|
135
|
+
documentation_uri: https://karafka.io/docs/#waterdrop
|
135
136
|
rubygems_mfa_required: 'true'
|
136
137
|
post_install_message:
|
137
138
|
rdoc_options: []
|
metadata.gz.sig
CHANGED
Binary file
|