waterdrop 2.0.7 → 2.6.14
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
- checksums.yaml.gz.sig +0 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/ci.yml +39 -13
- data/.ruby-version +1 -1
- data/CHANGELOG.md +212 -0
- data/Gemfile +0 -2
- data/Gemfile.lock +45 -75
- data/README.md +22 -275
- data/certs/cert_chain.pem +26 -0
- data/config/locales/errors.yml +39 -0
- data/docker-compose.yml +21 -12
- data/lib/waterdrop/clients/buffered.rb +95 -0
- data/lib/waterdrop/clients/dummy.rb +69 -0
- data/lib/waterdrop/clients/rdkafka.rb +34 -0
- data/lib/{water_drop → waterdrop}/config.rb +39 -16
- data/lib/waterdrop/contracts/config.rb +43 -0
- data/lib/waterdrop/contracts/message.rb +64 -0
- data/lib/waterdrop/contracts/transactional_offset.rb +21 -0
- data/lib/{water_drop → waterdrop}/errors.rb +23 -7
- data/lib/waterdrop/helpers/counter.rb +27 -0
- data/lib/waterdrop/instrumentation/callbacks/delivery.rb +106 -0
- data/lib/{water_drop → waterdrop}/instrumentation/callbacks/error.rb +6 -2
- data/lib/{water_drop → waterdrop}/instrumentation/callbacks/statistics.rb +1 -1
- data/lib/{water_drop/instrumentation/stdout_listener.rb → waterdrop/instrumentation/logger_listener.rb} +91 -21
- data/lib/waterdrop/instrumentation/monitor.rb +20 -0
- data/lib/{water_drop/instrumentation/monitor.rb → waterdrop/instrumentation/notifications.rb} +15 -14
- data/lib/waterdrop/instrumentation/vendors/datadog/dashboard.json +1 -0
- data/lib/waterdrop/instrumentation/vendors/datadog/metrics_listener.rb +210 -0
- data/lib/waterdrop/middleware.rb +50 -0
- data/lib/{water_drop → waterdrop}/producer/async.rb +40 -4
- data/lib/{water_drop → waterdrop}/producer/buffer.rb +13 -31
- data/lib/{water_drop → waterdrop}/producer/builder.rb +6 -11
- data/lib/{water_drop → waterdrop}/producer/sync.rb +44 -15
- data/lib/waterdrop/producer/transactions.rb +219 -0
- data/lib/waterdrop/producer.rb +324 -0
- data/lib/{water_drop → waterdrop}/version.rb +1 -1
- data/lib/waterdrop.rb +27 -2
- data/renovate.json +6 -0
- data/waterdrop.gemspec +14 -11
- data.tar.gz.sig +0 -0
- metadata +73 -111
- metadata.gz.sig +0 -0
- data/certs/mensfeld.pem +0 -25
- data/config/errors.yml +0 -6
- data/lib/water_drop/contracts/config.rb +0 -26
- data/lib/water_drop/contracts/message.rb +0 -42
- data/lib/water_drop/instrumentation/callbacks/delivery.rb +0 -30
- data/lib/water_drop/instrumentation/callbacks/statistics_decorator.rb +0 -77
- data/lib/water_drop/instrumentation/callbacks_manager.rb +0 -39
- data/lib/water_drop/instrumentation.rb +0 -20
- data/lib/water_drop/patches/rdkafka/bindings.rb +0 -42
- data/lib/water_drop/patches/rdkafka/producer.rb +0 -20
- data/lib/water_drop/producer/dummy_client.rb +0 -32
- data/lib/water_drop/producer.rb +0 -162
- data/lib/water_drop.rb +0 -36
- /data/lib/{water_drop → waterdrop}/contracts.rb +0 -0
- /data/lib/{water_drop → waterdrop}/producer/status.rb +0 -0
data/lib/{water_drop/instrumentation/monitor.rb → waterdrop/instrumentation/notifications.rb}
RENAMED
@@ -2,43 +2,44 @@
|
|
2
2
|
|
3
3
|
module WaterDrop
|
4
4
|
module Instrumentation
|
5
|
-
#
|
6
|
-
|
7
|
-
# same time, which means that you might have for example file logging and NewRelic at the same
|
8
|
-
# time
|
9
|
-
# @note This class acts as a singleton because we are only permitted to have single monitor
|
10
|
-
# per running process (just as logger)
|
11
|
-
class Monitor < Dry::Monitor::Notifications
|
5
|
+
# Instrumented is used to hookup external monitoring services to monitor how WaterDrop works
|
6
|
+
class Notifications < ::Karafka::Core::Monitoring::Notifications
|
12
7
|
# List of events that we support in the system and to which a monitor client can hook up
|
13
8
|
# @note The non-error once support timestamp benchmarking
|
14
9
|
EVENTS = %w[
|
10
|
+
producer.connected
|
11
|
+
producer.closing
|
15
12
|
producer.closed
|
16
13
|
|
17
14
|
message.produced_async
|
18
15
|
message.produced_sync
|
19
16
|
message.acknowledged
|
17
|
+
message.purged
|
20
18
|
message.buffered
|
21
19
|
|
22
20
|
messages.produced_async
|
23
21
|
messages.produced_sync
|
24
22
|
messages.buffered
|
25
23
|
|
24
|
+
transaction.started
|
25
|
+
transaction.committed
|
26
|
+
transaction.aborted
|
27
|
+
transaction.marked_as_consumed
|
28
|
+
transaction.finished
|
29
|
+
|
26
30
|
buffer.flushed_async
|
27
|
-
buffer.flushed_async.error
|
28
31
|
buffer.flushed_sync
|
29
|
-
buffer.
|
32
|
+
buffer.purged
|
30
33
|
|
31
34
|
statistics.emitted
|
32
35
|
|
33
|
-
error.
|
36
|
+
error.occurred
|
34
37
|
].freeze
|
35
38
|
|
36
|
-
private_constant :EVENTS
|
37
|
-
|
38
39
|
# @return [WaterDrop::Instrumentation::Monitor] monitor instance for system instrumentation
|
39
40
|
def initialize
|
40
|
-
super
|
41
|
-
EVENTS.each(
|
41
|
+
super
|
42
|
+
EVENTS.each { |event| register_event(event) }
|
42
43
|
end
|
43
44
|
end
|
44
45
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
{"title":"WaterDrop producer example dashboard","description":"This dashboard include example setup for monitoring activity of your WaterDrop producer","widgets":[{"id":243951318,"definition":{"title":"Messages produced","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"produced sync","formula":"query1"},{"alias":"produced async","formula":"query2"},{"alias":"flushed sync","formula":"query3"},{"alias":"flushed async","formula":"query4"},{"alias":"acknowledged","formula":"query5"}],"response_format":"timeseries","queries":[{"query":"sum:waterdrop.produced_sync{*}.as_count()","data_source":"metrics","name":"query1"},{"query":"sum:waterdrop.produced_async{*}.as_count()","data_source":"metrics","name":"query2"},{"query":"sum:waterdrop.flushed_sync{*}.as_count()","data_source":"metrics","name":"query3"},{"query":"sum:waterdrop.flushed_async{*}.as_count()","data_source":"metrics","name":"query4"},{"query":"sum:waterdrop.acknowledged{*}.as_count()","data_source":"metrics","name":"query5"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}],"yaxis":{"include_zero":true,"scale":"linear","label":"","min":"auto","max":"auto"}}},{"id":1979626566852990,"definition":{"title":"Messages buffer size","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"max","formula":"query1"}],"response_format":"timeseries","queries":[{"query":"avg:waterdrop.buffer.size.max{*}","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]}},{"id":243951221,"definition":{"title":"Kafka broker API calls","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"API calls","formula":"query1"}],"response_format":"timeseries","queries":[{"query":"sum:waterdrop.calls{*}","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}],"yaxis":{"include_zero":true,"scale":"linear","label":"","min":"auto","max":"auto"}}},{"id":243951952,"definition":{"title":"Producer queue size","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"Queue size average","formula":"query1"}],"response_format":"timeseries","queries":[{"query":"max:waterdrop.queue.size.avg{*}","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"},{"formulas":[{"alias":"Queue size max","formula":"query1"}],"response_format":"timeseries","queries":[{"query":"max:waterdrop.queue.size.max{*}","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}],"yaxis":{"include_zero":true,"scale":"linear","label":"","min":"auto","max":"auto"}}},{"id":243951263,"definition":{"title":"Producer queue latency","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"Average latency","formula":"query1"}],"response_format":"timeseries","queries":[{"query":"avg:waterdrop.queue.latency.avg{*}","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"},{"formulas":[{"alias":"Latency p95","formula":"query1"}],"response_format":"timeseries","queries":[{"query":"avg:waterdrop.queue.latency.p95{*}","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"},{"formulas":[{"alias":"Latency p99","formula":"query1"}],"response_format":"timeseries","queries":[{"query":"avg:waterdrop.queue.latency.p99{*}","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}],"yaxis":{"include_zero":true,"scale":"linear","label":"","min":"auto","max":"auto"}}},{"id":243951276,"definition":{"title":"Producer network latency","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"Average latency","formula":"query1"}],"response_format":"timeseries","queries":[{"query":"avg:waterdrop.request_size.avg{*}","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"},{"formulas":[{"alias":"Latency p95","formula":"query1"}],"response_format":"timeseries","queries":[{"query":"avg:waterdrop.network.latency.p95{*}","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"},{"formulas":[{"alias":"Latency p99","formula":"query1"}],"response_format":"timeseries","queries":[{"query":"avg:waterdrop.network.latency.p99{*}","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}],"yaxis":{"include_zero":true,"scale":"linear","label":"","min":"auto","max":"auto"}}},{"id":243954928,"definition":{"title":"Producer errors","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"formula":"query1"}],"response_format":"timeseries","queries":[{"query":"sum:waterdrop.error_occurred{*}.as_count()","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}],"yaxis":{"include_zero":true,"scale":"linear","label":"","min":"auto","max":"auto"}}}],"template_variables":[],"layout_type":"ordered","is_read_only":false,"notify_list":[],"reflow_type":"auto","id":"rnr-kgh-dna"}
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WaterDrop
|
4
|
+
module Instrumentation
|
5
|
+
# Namespace for vendor specific instrumentation
|
6
|
+
module Vendors
|
7
|
+
# Datadog specific instrumentation
|
8
|
+
module Datadog
|
9
|
+
# Listener that can be used to subscribe to WaterDrop producer to receive stats via StatsD
|
10
|
+
# and/or Datadog
|
11
|
+
#
|
12
|
+
# @note You need to setup the `dogstatsd-ruby` client and assign it
|
13
|
+
class MetricsListener
|
14
|
+
include ::Karafka::Core::Configurable
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
def_delegators :config, :client, :rd_kafka_metrics, :namespace, :default_tags
|
18
|
+
|
19
|
+
# Value object for storing a single rdkafka metric publishing details
|
20
|
+
RdKafkaMetric = Struct.new(:type, :scope, :name, :key_location)
|
21
|
+
|
22
|
+
# Namespace under which the DD metrics should be published
|
23
|
+
setting :namespace, default: 'waterdrop'
|
24
|
+
|
25
|
+
# Datadog client that we should use to publish the metrics
|
26
|
+
setting :client
|
27
|
+
|
28
|
+
# Default tags we want to publish (for example hostname)
|
29
|
+
# Format as followed (example for hostname): `["host:#{Socket.gethostname}"]`
|
30
|
+
setting :default_tags, default: []
|
31
|
+
|
32
|
+
# All the rdkafka metrics we want to publish
|
33
|
+
#
|
34
|
+
# By default we publish quite a lot so this can be tuned
|
35
|
+
# Note, that the once with `_d` come from WaterDrop, not rdkafka or Kafka
|
36
|
+
setting :rd_kafka_metrics, default: [
|
37
|
+
# Client metrics
|
38
|
+
RdKafkaMetric.new(:count, :root, 'calls', 'tx_d'),
|
39
|
+
RdKafkaMetric.new(:histogram, :root, 'queue.size', 'msg_cnt_d'),
|
40
|
+
|
41
|
+
# Broker metrics
|
42
|
+
RdKafkaMetric.new(:count, :brokers, 'deliver.attempts', 'txretries_d'),
|
43
|
+
RdKafkaMetric.new(:count, :brokers, 'deliver.errors', 'txerrs_d'),
|
44
|
+
RdKafkaMetric.new(:count, :brokers, 'receive.errors', 'rxerrs_d'),
|
45
|
+
RdKafkaMetric.new(:gauge, :brokers, 'queue.latency.avg', %w[outbuf_latency avg]),
|
46
|
+
RdKafkaMetric.new(:gauge, :brokers, 'queue.latency.p95', %w[outbuf_latency p95]),
|
47
|
+
RdKafkaMetric.new(:gauge, :brokers, 'queue.latency.p99', %w[outbuf_latency p99]),
|
48
|
+
RdKafkaMetric.new(:gauge, :brokers, 'network.latency.avg', %w[rtt avg]),
|
49
|
+
RdKafkaMetric.new(:gauge, :brokers, 'network.latency.p95', %w[rtt p95]),
|
50
|
+
RdKafkaMetric.new(:gauge, :brokers, 'network.latency.p99', %w[rtt p99])
|
51
|
+
].freeze
|
52
|
+
|
53
|
+
configure
|
54
|
+
|
55
|
+
# @param block [Proc] configuration block
|
56
|
+
def initialize(&block)
|
57
|
+
configure
|
58
|
+
setup(&block) if block
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param block [Proc] configuration block
|
62
|
+
# @note We define this alias to be consistent with `WaterDrop#setup`
|
63
|
+
def setup(&block)
|
64
|
+
configure(&block)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Hooks up to WaterDrop instrumentation for emitted statistics
|
68
|
+
#
|
69
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
70
|
+
def on_statistics_emitted(event)
|
71
|
+
statistics = event[:statistics]
|
72
|
+
|
73
|
+
rd_kafka_metrics.each do |metric|
|
74
|
+
report_metric(metric, statistics)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Increases the errors count by 1
|
79
|
+
#
|
80
|
+
# @param _event [Karafka::Core::Monitoring::Event]
|
81
|
+
def on_error_occurred(_event)
|
82
|
+
count('error_occurred', 1, tags: default_tags)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Increases acknowledged messages counter
|
86
|
+
# @param _event [Karafka::Core::Monitoring::Event]
|
87
|
+
def on_message_acknowledged(_event)
|
88
|
+
increment('acknowledged', tags: default_tags)
|
89
|
+
end
|
90
|
+
|
91
|
+
%i[
|
92
|
+
produced_sync
|
93
|
+
produced_async
|
94
|
+
].each do |event_scope|
|
95
|
+
class_eval <<~METHODS, __FILE__, __LINE__ + 1
|
96
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
97
|
+
def on_message_#{event_scope}(event)
|
98
|
+
report_message(event[:message][:topic], :#{event_scope})
|
99
|
+
end
|
100
|
+
|
101
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
102
|
+
def on_messages_#{event_scope}(event)
|
103
|
+
event[:messages].each do |message|
|
104
|
+
report_message(message[:topic], :#{event_scope})
|
105
|
+
end
|
106
|
+
end
|
107
|
+
METHODS
|
108
|
+
end
|
109
|
+
|
110
|
+
# Reports the buffer usage when anything is added to the buffer
|
111
|
+
%i[
|
112
|
+
message_buffered
|
113
|
+
messages_buffered
|
114
|
+
].each do |event_scope|
|
115
|
+
class_eval <<~METHODS, __FILE__, __LINE__ + 1
|
116
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
117
|
+
def on_#{event_scope}(event)
|
118
|
+
histogram(
|
119
|
+
'buffer.size',
|
120
|
+
event[:buffer].size,
|
121
|
+
tags: default_tags
|
122
|
+
)
|
123
|
+
end
|
124
|
+
METHODS
|
125
|
+
end
|
126
|
+
|
127
|
+
# Events that support many messages only
|
128
|
+
# Reports data flushing operation (production from the buffer)
|
129
|
+
%i[
|
130
|
+
flushed_sync
|
131
|
+
flushed_async
|
132
|
+
].each do |event_scope|
|
133
|
+
class_eval <<~METHODS, __FILE__, __LINE__ + 1
|
134
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
135
|
+
def on_buffer_#{event_scope}(event)
|
136
|
+
event[:messages].each do |message|
|
137
|
+
report_message(message[:topic], :#{event_scope})
|
138
|
+
end
|
139
|
+
end
|
140
|
+
METHODS
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
%i[
|
146
|
+
count
|
147
|
+
gauge
|
148
|
+
histogram
|
149
|
+
increment
|
150
|
+
decrement
|
151
|
+
].each do |metric_type|
|
152
|
+
class_eval <<~METHODS, __FILE__, __LINE__ + 1
|
153
|
+
def #{metric_type}(key, *args)
|
154
|
+
client.#{metric_type}(
|
155
|
+
namespaced_metric(key),
|
156
|
+
*args
|
157
|
+
)
|
158
|
+
end
|
159
|
+
METHODS
|
160
|
+
end
|
161
|
+
|
162
|
+
# Report that a message has been produced to a topic.
|
163
|
+
# @param topic [String] Kafka topic
|
164
|
+
# @param method_name [Symbol] method from which this message operation comes
|
165
|
+
def report_message(topic, method_name)
|
166
|
+
increment(method_name, tags: default_tags + ["topic:#{topic}"])
|
167
|
+
end
|
168
|
+
|
169
|
+
# Wraps metric name in listener's namespace
|
170
|
+
# @param metric_name [String] RdKafkaMetric name
|
171
|
+
# @return [String]
|
172
|
+
def namespaced_metric(metric_name)
|
173
|
+
"#{namespace}.#{metric_name}"
|
174
|
+
end
|
175
|
+
|
176
|
+
# Reports a given metric statistics to Datadog
|
177
|
+
# @param metric [RdKafkaMetric] metric value object
|
178
|
+
# @param statistics [Hash] hash with all the statistics emitted
|
179
|
+
def report_metric(metric, statistics)
|
180
|
+
case metric.scope
|
181
|
+
when :root
|
182
|
+
public_send(
|
183
|
+
metric.type,
|
184
|
+
metric.name,
|
185
|
+
statistics.fetch(*metric.key_location),
|
186
|
+
tags: default_tags
|
187
|
+
)
|
188
|
+
when :brokers
|
189
|
+
statistics.fetch('brokers').each_value do |broker_statistics|
|
190
|
+
# Skip bootstrap nodes
|
191
|
+
# Bootstrap nodes have nodeid -1, other nodes have positive
|
192
|
+
# node ids
|
193
|
+
next if broker_statistics['nodeid'] == -1
|
194
|
+
|
195
|
+
public_send(
|
196
|
+
metric.type,
|
197
|
+
metric.name,
|
198
|
+
broker_statistics.dig(*metric.key_location),
|
199
|
+
tags: default_tags + ["broker:#{broker_statistics['nodename']}"]
|
200
|
+
)
|
201
|
+
end
|
202
|
+
else
|
203
|
+
raise ArgumentError, metric.scope
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WaterDrop
|
4
|
+
# Simple middleware layer for manipulating messages prior to their validation
|
5
|
+
class Middleware
|
6
|
+
def initialize
|
7
|
+
@mutex = Mutex.new
|
8
|
+
@steps = []
|
9
|
+
end
|
10
|
+
|
11
|
+
# Runs middleware on a single message prior to validation
|
12
|
+
#
|
13
|
+
# @param message [Hash] message hash
|
14
|
+
# @return [Hash] message hash. Either the same if transformed in place, or a copy if modified
|
15
|
+
# into a new object.
|
16
|
+
# @note You need to decide yourself whether you don't use the message hash data anywhere else
|
17
|
+
# and you want to save on memory by modifying it in place or do you want to do a deep copy
|
18
|
+
def run(message)
|
19
|
+
@steps.each do |step|
|
20
|
+
message = step.call(message)
|
21
|
+
end
|
22
|
+
|
23
|
+
message
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param messages [Array<Hash>] messages on which we want to run middlewares
|
27
|
+
# @return [Array<Hash>] transformed messages
|
28
|
+
def run_many(messages)
|
29
|
+
messages.map do |message|
|
30
|
+
run(message)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Register given middleware as the first one in the chain
|
35
|
+
# @param step [#call] step that needs to return the message
|
36
|
+
def prepend(step)
|
37
|
+
@mutex.synchronize do
|
38
|
+
@steps.prepend step
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Register given middleware as the last one in the chain
|
43
|
+
# @param step [#call] step that needs to return the message
|
44
|
+
def append(step)
|
45
|
+
@mutex.synchronize do
|
46
|
+
@steps.append step
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -14,14 +14,30 @@ module WaterDrop
|
|
14
14
|
# @raise [Errors::MessageInvalidError] When provided message details are invalid and the
|
15
15
|
# message could not be sent to Kafka
|
16
16
|
def produce_async(message)
|
17
|
-
|
17
|
+
message = middleware.run(message)
|
18
18
|
validate_message!(message)
|
19
19
|
|
20
20
|
@monitor.instrument(
|
21
21
|
'message.produced_async',
|
22
22
|
producer_id: id,
|
23
23
|
message: message
|
24
|
-
) {
|
24
|
+
) { produce(message) }
|
25
|
+
rescue *SUPPORTED_FLOW_ERRORS => e
|
26
|
+
# We use this syntax here because we want to preserve the original `#cause` when we
|
27
|
+
# instrument the error and there is no way to manually assign `#cause` value
|
28
|
+
begin
|
29
|
+
raise Errors::ProduceError, e.inspect
|
30
|
+
rescue Errors::ProduceError => ex
|
31
|
+
@monitor.instrument(
|
32
|
+
'error.occurred',
|
33
|
+
producer_id: id,
|
34
|
+
message: message,
|
35
|
+
error: ex,
|
36
|
+
type: 'message.produce_async'
|
37
|
+
)
|
38
|
+
|
39
|
+
raise ex
|
40
|
+
end
|
25
41
|
end
|
26
42
|
|
27
43
|
# Produces many messages to Kafka and does not wait for them to be delivered
|
@@ -35,7 +51,8 @@ module WaterDrop
|
|
35
51
|
# @raise [Errors::MessageInvalidError] When any of the provided messages details are invalid
|
36
52
|
# and the message could not be sent to Kafka
|
37
53
|
def produce_many_async(messages)
|
38
|
-
|
54
|
+
dispatched = []
|
55
|
+
messages = middleware.run_many(messages)
|
39
56
|
messages.each { |message| validate_message!(message) }
|
40
57
|
|
41
58
|
@monitor.instrument(
|
@@ -43,8 +60,27 @@ module WaterDrop
|
|
43
60
|
producer_id: id,
|
44
61
|
messages: messages
|
45
62
|
) do
|
46
|
-
|
63
|
+
with_transaction_if_transactional do
|
64
|
+
messages.each do |message|
|
65
|
+
dispatched << produce(message)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
dispatched
|
47
70
|
end
|
71
|
+
rescue *SUPPORTED_FLOW_ERRORS => e
|
72
|
+
re_raised = Errors::ProduceManyError.new(dispatched, e.inspect)
|
73
|
+
|
74
|
+
@monitor.instrument(
|
75
|
+
'error.occurred',
|
76
|
+
producer_id: id,
|
77
|
+
messages: messages,
|
78
|
+
dispatched: dispatched,
|
79
|
+
error: re_raised,
|
80
|
+
type: 'messages.produce_many_async'
|
81
|
+
)
|
82
|
+
|
83
|
+
raise re_raised
|
48
84
|
end
|
49
85
|
end
|
50
86
|
end
|
@@ -4,14 +4,6 @@ module WaterDrop
|
|
4
4
|
class Producer
|
5
5
|
# Component for buffered operations
|
6
6
|
module Buffer
|
7
|
-
# Exceptions we catch when dispatching messages from a buffer
|
8
|
-
RESCUED_ERRORS = [
|
9
|
-
Rdkafka::RdkafkaError,
|
10
|
-
Rdkafka::Producer::DeliveryHandle::WaitTimeoutError
|
11
|
-
].freeze
|
12
|
-
|
13
|
-
private_constant :RESCUED_ERRORS
|
14
|
-
|
15
7
|
# Adds given message into the internal producer buffer without flushing it to Kafka
|
16
8
|
#
|
17
9
|
# @param message [Hash] hash that complies with the {Contracts::Message} contract
|
@@ -19,12 +11,15 @@ module WaterDrop
|
|
19
11
|
# message could not be sent to Kafka
|
20
12
|
def buffer(message)
|
21
13
|
ensure_active!
|
14
|
+
|
15
|
+
message = middleware.run(message)
|
22
16
|
validate_message!(message)
|
23
17
|
|
24
18
|
@monitor.instrument(
|
25
19
|
'message.buffered',
|
26
20
|
producer_id: id,
|
27
|
-
message: message
|
21
|
+
message: message,
|
22
|
+
buffer: @messages
|
28
23
|
) { @messages << message }
|
29
24
|
end
|
30
25
|
|
@@ -36,12 +31,15 @@ module WaterDrop
|
|
36
31
|
# and the message could not be sent to Kafka
|
37
32
|
def buffer_many(messages)
|
38
33
|
ensure_active!
|
34
|
+
|
35
|
+
messages = middleware.run_many(messages)
|
39
36
|
messages.each { |message| validate_message!(message) }
|
40
37
|
|
41
38
|
@monitor.instrument(
|
42
39
|
'messages.buffered',
|
43
40
|
producer_id: id,
|
44
|
-
messages: messages
|
41
|
+
messages: messages,
|
42
|
+
buffer: @messages
|
45
43
|
) do
|
46
44
|
messages.each { |message| @messages << message }
|
47
45
|
messages
|
@@ -52,8 +50,6 @@ module WaterDrop
|
|
52
50
|
# @return [Array<Rdkafka::Producer::DeliveryHandle>] delivery handles for messages that were
|
53
51
|
# flushed
|
54
52
|
def flush_async
|
55
|
-
ensure_active!
|
56
|
-
|
57
53
|
@monitor.instrument(
|
58
54
|
'buffer.flushed_async',
|
59
55
|
producer_id: id,
|
@@ -65,8 +61,6 @@ module WaterDrop
|
|
65
61
|
# @return [Array<Rdkafka::Producer::DeliveryReport>] delivery reports for messages that were
|
66
62
|
# flushed
|
67
63
|
def flush_sync
|
68
|
-
ensure_active!
|
69
|
-
|
70
64
|
@monitor.instrument(
|
71
65
|
'buffer.flushed_sync',
|
72
66
|
producer_id: id,
|
@@ -80,33 +74,21 @@ module WaterDrop
|
|
80
74
|
# @param sync [Boolean] should it flush in a sync way
|
81
75
|
# @return [Array<Rdkafka::Producer::DeliveryHandle, Rdkafka::Producer::DeliveryReport>]
|
82
76
|
# delivery handles for async or delivery reports for sync
|
83
|
-
# @raise [Errors::
|
77
|
+
# @raise [Errors::ProduceManyError] when there was a failure in flushing
|
84
78
|
# @note We use this method underneath to provide a different instrumentation for sync and
|
85
79
|
# async flushing within the public API
|
86
80
|
def flush(sync)
|
87
81
|
data_for_dispatch = nil
|
88
|
-
dispatched = []
|
89
82
|
|
90
83
|
@buffer_mutex.synchronize do
|
91
84
|
data_for_dispatch = @messages
|
92
|
-
@messages =
|
85
|
+
@messages = []
|
93
86
|
end
|
94
87
|
|
95
|
-
|
96
|
-
|
97
|
-
return dispatched unless sync
|
98
|
-
|
99
|
-
dispatched.map do |handler|
|
100
|
-
handler.wait(
|
101
|
-
max_wait_timeout: @config.max_wait_timeout,
|
102
|
-
wait_timeout: @config.wait_timeout
|
103
|
-
)
|
104
|
-
end
|
105
|
-
rescue *RESCUED_ERRORS => e
|
106
|
-
key = sync ? 'buffer.flushed_sync.error' : 'buffer.flush_async.error'
|
107
|
-
@monitor.instrument(key, producer_id: id, error: e, dispatched: dispatched)
|
88
|
+
# Do nothing if nothing to flush
|
89
|
+
return data_for_dispatch if data_for_dispatch.empty?
|
108
90
|
|
109
|
-
|
91
|
+
sync ? produce_many_sync(data_for_dispatch) : produce_many_async(data_for_dispatch)
|
110
92
|
end
|
111
93
|
end
|
112
94
|
end
|
@@ -10,18 +10,13 @@ module WaterDrop
|
|
10
10
|
# @return [Rdkafka::Producer, Producer::DummyClient] raw rdkafka producer or a dummy producer
|
11
11
|
# when we don't want to dispatch any messages
|
12
12
|
def call(producer, config)
|
13
|
-
|
13
|
+
klass = config.client_class
|
14
|
+
# This allows us to have backwards compatibility.
|
15
|
+
# If it is the default client and delivery is set to false, we use dummy as we used to
|
16
|
+
# before `client_class` was introduced
|
17
|
+
klass = Clients::Dummy if klass == Clients::Rdkafka && !config.deliver
|
14
18
|
|
15
|
-
|
16
|
-
|
17
|
-
# This callback is not global and is per client, thus we do not have to wrap it with a
|
18
|
-
# callbacks manager to make it work
|
19
|
-
client.delivery_callback = Instrumentation::Callbacks::Delivery.new(
|
20
|
-
producer.id,
|
21
|
-
config.monitor
|
22
|
-
)
|
23
|
-
|
24
|
-
client
|
19
|
+
klass.new(producer)
|
25
20
|
end
|
26
21
|
end
|
27
22
|
end
|
@@ -16,7 +16,7 @@ module WaterDrop
|
|
16
16
|
# @raise [Errors::MessageInvalidError] When provided message details are invalid and the
|
17
17
|
# message could not be sent to Kafka
|
18
18
|
def produce_sync(message)
|
19
|
-
|
19
|
+
message = middleware.run(message)
|
20
20
|
validate_message!(message)
|
21
21
|
|
22
22
|
@monitor.instrument(
|
@@ -24,12 +24,23 @@ module WaterDrop
|
|
24
24
|
producer_id: id,
|
25
25
|
message: message
|
26
26
|
) do
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
wait(produce(message))
|
28
|
+
end
|
29
|
+
rescue *SUPPORTED_FLOW_ERRORS => e
|
30
|
+
# We use this syntax here because we want to preserve the original `#cause` when we
|
31
|
+
# instrument the error and there is no way to manually assign `#cause` value
|
32
|
+
begin
|
33
|
+
raise Errors::ProduceError, e.inspect
|
34
|
+
rescue Errors::ProduceError => ex
|
35
|
+
@monitor.instrument(
|
36
|
+
'error.occurred',
|
37
|
+
producer_id: id,
|
38
|
+
message: message,
|
39
|
+
error: ex,
|
40
|
+
type: 'message.produce_sync'
|
41
|
+
)
|
42
|
+
|
43
|
+
raise ex
|
33
44
|
end
|
34
45
|
end
|
35
46
|
|
@@ -46,19 +57,37 @@ module WaterDrop
|
|
46
57
|
# @raise [Errors::MessageInvalidError] When any of the provided messages details are invalid
|
47
58
|
# and the message could not be sent to Kafka
|
48
59
|
def produce_many_sync(messages)
|
49
|
-
|
60
|
+
messages = middleware.run_many(messages)
|
50
61
|
messages.each { |message| validate_message!(message) }
|
51
62
|
|
63
|
+
dispatched = []
|
64
|
+
|
52
65
|
@monitor.instrument('messages.produced_sync', producer_id: id, messages: messages) do
|
53
|
-
|
54
|
-
.
|
55
|
-
|
56
|
-
handler.wait(
|
57
|
-
max_wait_timeout: @config.max_wait_timeout,
|
58
|
-
wait_timeout: @config.wait_timeout
|
59
|
-
)
|
66
|
+
with_transaction_if_transactional do
|
67
|
+
messages.each do |message|
|
68
|
+
dispatched << produce(message)
|
60
69
|
end
|
70
|
+
end
|
71
|
+
|
72
|
+
dispatched.map! do |handler|
|
73
|
+
wait(handler)
|
74
|
+
end
|
75
|
+
|
76
|
+
dispatched
|
61
77
|
end
|
78
|
+
rescue *SUPPORTED_FLOW_ERRORS => e
|
79
|
+
re_raised = Errors::ProduceManyError.new(dispatched, e.inspect)
|
80
|
+
|
81
|
+
@monitor.instrument(
|
82
|
+
'error.occurred',
|
83
|
+
producer_id: id,
|
84
|
+
messages: messages,
|
85
|
+
dispatched: dispatched,
|
86
|
+
error: re_raised,
|
87
|
+
type: 'messages.produce_many_sync'
|
88
|
+
)
|
89
|
+
|
90
|
+
raise re_raised
|
62
91
|
end
|
63
92
|
end
|
64
93
|
end
|