waterdrop 2.4.6 → 2.4.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 +3 -2
- data/.ruby-version +1 -1
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +9 -9
- data/README.md +33 -0
- data/config/{errors.yml → locales/errors.yml} +0 -0
- data/lib/waterdrop/config.rb +6 -0
- data/lib/waterdrop/contracts/config.rb +1 -1
- data/lib/waterdrop/contracts/message.rb +1 -1
- data/lib/waterdrop/instrumentation/logger_listener.rb +1 -0
- data/lib/waterdrop/middleware.rb +50 -0
- data/lib/waterdrop/patches/rdkafka/metadata.rb +9 -1
- data/lib/waterdrop/patches/rdkafka/producer.rb +4 -7
- data/lib/waterdrop/producer/async.rb +4 -0
- data/lib/waterdrop/producer/buffer.rb +3 -0
- data/lib/waterdrop/producer/sync.rb +4 -0
- data/lib/waterdrop/producer.rb +3 -0
- data/lib/waterdrop/version.rb +1 -1
- data/waterdrop.gemspec +1 -1
- data.tar.gz.sig +0 -0
- metadata +7 -6
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8fab3f2b565a128bcc34f8d8bd10b36b0bd9546d1c52109a97ad87449fcd1731
|
4
|
+
data.tar.gz: 891f381590986451b330f74ac0ab56e6d2873d84a778d33bf470db1fe5e8447d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de29a31f2d498bfd04577ddd7edec90603d1226bc9b355d3e65e62fae0e2cb0f89bc5708a0734425df0fc5458adcb01e5b59775fda3daaceab1de586b41ed07d
|
7
|
+
data.tar.gz: 052ea95a7c819c42ba62c1275b4895cf9b034e429ef34af0d4556965dd28cd0a9307a0a5613f3a126b4e41da6b0cfe8042e3f614045929203faca67c496d4352
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/.github/workflows/ci.yml
CHANGED
@@ -16,11 +16,12 @@ jobs:
|
|
16
16
|
fail-fast: false
|
17
17
|
matrix:
|
18
18
|
ruby:
|
19
|
+
- '3.2'
|
19
20
|
- '3.1'
|
20
21
|
- '3.0'
|
21
22
|
- '2.7'
|
22
23
|
include:
|
23
|
-
- ruby: '3.
|
24
|
+
- ruby: '3.2'
|
24
25
|
coverage: 'true'
|
25
26
|
steps:
|
26
27
|
- uses: actions/checkout@v3
|
@@ -62,7 +63,7 @@ jobs:
|
|
62
63
|
- name: Set up Ruby
|
63
64
|
uses: ruby/setup-ruby@v1
|
64
65
|
with:
|
65
|
-
ruby-version: 3.
|
66
|
+
ruby-version: 3.2
|
66
67
|
- name: Install latest bundler
|
67
68
|
run: gem install bundler --no-document
|
68
69
|
- name: Install Diffend plugin
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.2.0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# WaterDrop changelog
|
2
2
|
|
3
|
+
## 2.4.8 (2023-01-07)
|
4
|
+
- Use monotonic time from Karafka core.
|
5
|
+
|
6
|
+
## 2.4.7 (2022-12-18)
|
7
|
+
- Add support to customizable middlewares that can modify message hash prior to validation and dispatch.
|
8
|
+
- Fix a case where upon not-available leader, metadata request would not be retried
|
9
|
+
- Require `karafka-core` 2.0.7.
|
10
|
+
|
3
11
|
## 2.4.6 (2022-12-10)
|
4
12
|
- Set `statistics.interval.ms` to 5 seconds by default, so the defaults cover all the instrumentation out of the box.
|
5
13
|
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
waterdrop (2.4.
|
5
|
-
karafka-core (>= 2.0.
|
4
|
+
waterdrop (2.4.8)
|
5
|
+
karafka-core (>= 2.0.8, < 3.0.0)
|
6
6
|
zeitwerk (~> 2.3)
|
7
7
|
|
8
8
|
GEM
|
@@ -22,11 +22,11 @@ GEM
|
|
22
22
|
ffi (1.15.5)
|
23
23
|
i18n (1.12.0)
|
24
24
|
concurrent-ruby (~> 1.0)
|
25
|
-
karafka-core (2.0.
|
25
|
+
karafka-core (2.0.8)
|
26
26
|
concurrent-ruby (>= 1.1)
|
27
27
|
rdkafka (>= 0.12)
|
28
|
-
mini_portile2 (2.8.
|
29
|
-
minitest (5.
|
28
|
+
mini_portile2 (2.8.1)
|
29
|
+
minitest (5.17.0)
|
30
30
|
rake (13.0.6)
|
31
31
|
rdkafka (0.12.0)
|
32
32
|
ffi (~> 1.15)
|
@@ -38,14 +38,14 @@ GEM
|
|
38
38
|
rspec-mocks (~> 3.12.0)
|
39
39
|
rspec-core (3.12.0)
|
40
40
|
rspec-support (~> 3.12.0)
|
41
|
-
rspec-expectations (3.12.
|
41
|
+
rspec-expectations (3.12.1)
|
42
42
|
diff-lcs (>= 1.2.0, < 2.0)
|
43
43
|
rspec-support (~> 3.12.0)
|
44
|
-
rspec-mocks (3.12.
|
44
|
+
rspec-mocks (3.12.1)
|
45
45
|
diff-lcs (>= 1.2.0, < 2.0)
|
46
46
|
rspec-support (~> 3.12.0)
|
47
47
|
rspec-support (3.12.0)
|
48
|
-
simplecov (0.
|
48
|
+
simplecov (0.22.0)
|
49
49
|
docile (~> 1.1)
|
50
50
|
simplecov-html (~> 0.11)
|
51
51
|
simplecov_json_formatter (~> 0.1)
|
@@ -67,4 +67,4 @@ DEPENDENCIES
|
|
67
67
|
waterdrop!
|
68
68
|
|
69
69
|
BUNDLED WITH
|
70
|
-
2.
|
70
|
+
2.4.2
|
data/README.md
CHANGED
@@ -35,6 +35,7 @@ It:
|
|
35
35
|
* [Error notifications](#error-notifications)
|
36
36
|
* [Datadog and StatsD integration](#datadog-and-statsd-integration)
|
37
37
|
* [Forking and potential memory problems](#forking-and-potential-memory-problems)
|
38
|
+
- [Middleware](#middleware)
|
38
39
|
- [Note on contributions](#note-on-contributions)
|
39
40
|
|
40
41
|
## Installation
|
@@ -420,6 +421,38 @@ If you work with forked processes, make sure you **don't** use the producer befo
|
|
420
421
|
|
421
422
|
To tackle this [obstacle](https://github.com/appsignal/rdkafka-ruby/issues/15) related to rdkafka, WaterDrop adds finalizer to each of the producers to close the rdkafka client before the Ruby process is shutdown. Due to the [nature of the finalizers](https://www.mikeperham.com/2010/02/24/the-trouble-with-ruby-finalizers/), this implementation prevents producers from being GCed (except upon VM shutdown) and can cause memory leaks if you don't use persistent/long-lived producers in a long-running process or if you don't use the `#close` method of a producer when it is no longer needed. Creating a producer instance for each message is anyhow a rather bad idea, so we recommend not to.
|
422
423
|
|
424
|
+
## Middleware
|
425
|
+
|
426
|
+
WaterDrop supports injecting middleware similar to Rack.
|
427
|
+
|
428
|
+
Middleware can be used to provide extra functionalities like auto-serialization of data or any other modifications of messages before their validation and dispatch.
|
429
|
+
|
430
|
+
Each middleware accepts the message hash as input and expects a message hash as a result.
|
431
|
+
|
432
|
+
There are two methods to register middlewares:
|
433
|
+
|
434
|
+
- `#prepend` - registers middleware as the first in the order of execution
|
435
|
+
- `#append` - registers middleware as the last in the order of execution
|
436
|
+
|
437
|
+
Below you can find an example middleware that converts the incoming payload into a JSON string by running `#to_json` automatically:
|
438
|
+
|
439
|
+
```ruby
|
440
|
+
class AutoMapper
|
441
|
+
def call(message)
|
442
|
+
message[:payload] = message[:payload].to_json
|
443
|
+
message
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
# Register middleware
|
448
|
+
producer.middleware.append(AutoMapper.new)
|
449
|
+
|
450
|
+
# Dispatch without manual casting
|
451
|
+
producer.produce_async(topic: 'users', payload: user)
|
452
|
+
```
|
453
|
+
|
454
|
+
**Note**: It is up to the end user to decide whether to modify the provided message or deep copy it and update the newly created one.
|
455
|
+
|
423
456
|
## Note on contributions
|
424
457
|
|
425
458
|
First, thank you for considering contributing to the Karafka ecosystem! It's people like you that make the open source community such a great community!
|
File without changes
|
data/lib/waterdrop/config.rb
CHANGED
@@ -57,6 +57,12 @@ module WaterDrop
|
|
57
57
|
# rdkafka options
|
58
58
|
# @see https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
|
59
59
|
setting :kafka, default: {}
|
60
|
+
# Middleware chain that can be expanded with useful middleware steps
|
61
|
+
setting(
|
62
|
+
:middleware,
|
63
|
+
default: false,
|
64
|
+
constructor: ->(middleware) { middleware || WaterDrop::Middleware.new }
|
65
|
+
)
|
60
66
|
|
61
67
|
# Configuration method
|
62
68
|
# @yield Runs a block of code providing a config singleton instance to it
|
@@ -7,7 +7,7 @@ module WaterDrop
|
|
7
7
|
configure do |config|
|
8
8
|
config.error_messages = YAML.safe_load(
|
9
9
|
File.read(
|
10
|
-
File.join(WaterDrop.gem_root, 'config', 'errors.yml')
|
10
|
+
File.join(WaterDrop.gem_root, 'config', 'locales', 'errors.yml')
|
11
11
|
)
|
12
12
|
).fetch('en').fetch('validations').fetch('config')
|
13
13
|
end
|
@@ -8,7 +8,7 @@ module WaterDrop
|
|
8
8
|
configure do |config|
|
9
9
|
config.error_messages = YAML.safe_load(
|
10
10
|
File.read(
|
11
|
-
File.join(WaterDrop.gem_root, 'config', 'errors.yml')
|
11
|
+
File.join(WaterDrop.gem_root, 'config', 'locales', 'errors.yml')
|
12
12
|
)
|
13
13
|
).fetch('en').fetch('validations').fetch('message')
|
14
14
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module WaterDrop
|
4
|
+
# WaterDrop instrumentation related module
|
4
5
|
module Instrumentation
|
5
6
|
# Default listener that hooks up to our instrumentation and uses its events for logging
|
6
7
|
# It can be removed/replaced or anything without any harm to the Waterdrop flow
|
@@ -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
|
@@ -7,6 +7,14 @@ module WaterDrop
|
|
7
7
|
module Rdkafka
|
8
8
|
# Rdkafka::Metadata patches
|
9
9
|
module Metadata
|
10
|
+
# Errors upon which we retry the metadata fetch
|
11
|
+
RETRIED_ERRORS = %i[
|
12
|
+
timed_out
|
13
|
+
leader_not_available
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
private_constant :RETRIED_ERRORS
|
17
|
+
|
10
18
|
# We overwrite this method because there were reports of metadata operation timing out
|
11
19
|
# when Kafka was under stress. While the messages dispatch will be retried, metadata
|
12
20
|
# fetch happens prior to that, effectively crashing the process. Metadata fetch was not
|
@@ -19,7 +27,7 @@ module WaterDrop
|
|
19
27
|
|
20
28
|
super(*args)
|
21
29
|
rescue ::Rdkafka::RdkafkaError => e
|
22
|
-
raise unless e.code
|
30
|
+
raise unless RETRIED_ERRORS.include?(e.code)
|
23
31
|
raise if attempt > 10
|
24
32
|
|
25
33
|
backoff_factor = 2**attempt
|
@@ -7,6 +7,8 @@ module WaterDrop
|
|
7
7
|
module Rdkafka
|
8
8
|
# Rdkafka::Producer patches
|
9
9
|
module Producer
|
10
|
+
include ::Karafka::Core::Helpers::Time
|
11
|
+
|
10
12
|
# Cache partitions count for 30 seconds
|
11
13
|
PARTITIONS_COUNT_TTL = 30_000
|
12
14
|
|
@@ -20,7 +22,7 @@ module WaterDrop
|
|
20
22
|
topic_metadata = ::Rdkafka::Metadata.new(inner_kafka, topic).topics&.first
|
21
23
|
|
22
24
|
cache[topic] = [
|
23
|
-
|
25
|
+
monotonic_now,
|
24
26
|
topic_metadata ? topic_metadata[:partition_count] : nil
|
25
27
|
]
|
26
28
|
end
|
@@ -46,7 +48,7 @@ module WaterDrop
|
|
46
48
|
closed_producer_check(__method__)
|
47
49
|
|
48
50
|
@_partitions_count_cache.delete_if do |_, cached|
|
49
|
-
|
51
|
+
monotonic_now - cached.first > PARTITIONS_COUNT_TTL
|
50
52
|
end
|
51
53
|
|
52
54
|
@_partitions_count_cache[topic].last
|
@@ -68,11 +70,6 @@ module WaterDrop
|
|
68
70
|
|
69
71
|
@_inner_kafka
|
70
72
|
end
|
71
|
-
|
72
|
-
# @return [Float] current clock time
|
73
|
-
def now
|
74
|
-
::Process.clock_gettime(::Process::CLOCK_MONOTONIC) * 1_000
|
75
|
-
end
|
76
73
|
end
|
77
74
|
end
|
78
75
|
end
|
@@ -15,6 +15,8 @@ module WaterDrop
|
|
15
15
|
# message could not be sent to Kafka
|
16
16
|
def produce_async(message)
|
17
17
|
ensure_active!
|
18
|
+
|
19
|
+
message = middleware.run(message)
|
18
20
|
validate_message!(message)
|
19
21
|
|
20
22
|
@monitor.instrument(
|
@@ -36,6 +38,8 @@ module WaterDrop
|
|
36
38
|
# and the message could not be sent to Kafka
|
37
39
|
def produce_many_async(messages)
|
38
40
|
ensure_active!
|
41
|
+
|
42
|
+
messages = middleware.run_many(messages)
|
39
43
|
messages.each { |message| validate_message!(message) }
|
40
44
|
|
41
45
|
@monitor.instrument(
|
@@ -19,6 +19,7 @@ module WaterDrop
|
|
19
19
|
# message could not be sent to Kafka
|
20
20
|
def buffer(message)
|
21
21
|
ensure_active!
|
22
|
+
message = middleware.run(message)
|
22
23
|
validate_message!(message)
|
23
24
|
|
24
25
|
@monitor.instrument(
|
@@ -37,6 +38,8 @@ module WaterDrop
|
|
37
38
|
# and the message could not be sent to Kafka
|
38
39
|
def buffer_many(messages)
|
39
40
|
ensure_active!
|
41
|
+
|
42
|
+
messages = middleware.run_many(messages)
|
40
43
|
messages.each { |message| validate_message!(message) }
|
41
44
|
|
42
45
|
@monitor.instrument(
|
@@ -17,6 +17,8 @@ module WaterDrop
|
|
17
17
|
# message could not be sent to Kafka
|
18
18
|
def produce_sync(message)
|
19
19
|
ensure_active!
|
20
|
+
|
21
|
+
message = middleware.run(message)
|
20
22
|
validate_message!(message)
|
21
23
|
|
22
24
|
@monitor.instrument(
|
@@ -47,6 +49,8 @@ module WaterDrop
|
|
47
49
|
# and the message could not be sent to Kafka
|
48
50
|
def produce_many_sync(messages)
|
49
51
|
ensure_active!
|
52
|
+
|
53
|
+
messages = middleware.run_many(messages)
|
50
54
|
messages.each { |message| validate_message!(message) }
|
51
55
|
|
52
56
|
@monitor.instrument('messages.produced_sync', producer_id: id, messages: messages) do
|
data/lib/waterdrop/producer.rb
CHANGED
@@ -3,10 +3,13 @@
|
|
3
3
|
module WaterDrop
|
4
4
|
# Main WaterDrop messages producer
|
5
5
|
class Producer
|
6
|
+
extend Forwardable
|
6
7
|
include Sync
|
7
8
|
include Async
|
8
9
|
include Buffer
|
9
10
|
|
11
|
+
def_delegators :config, :middleware
|
12
|
+
|
10
13
|
# @return [String] uuid of the current producer
|
11
14
|
attr_reader :id
|
12
15
|
# @return [Status] producer status object
|
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.0.
|
19
|
+
spec.add_dependency 'karafka-core', '>= 2.0.8', '< 3.0.0'
|
20
20
|
spec.add_dependency 'zeitwerk', '~> 2.3'
|
21
21
|
|
22
22
|
spec.required_ruby_version = '>= 2.7'
|
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.4.
|
4
|
+
version: 2.4.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maciej Mensfeld
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
Qf04B9ceLUaC4fPVEz10FyobjaFoY4i32xRto3XnrzeAgfEe4swLq8bQsR3w/EF3
|
36
36
|
MGU0FeSV2Yj7Xc2x/7BzLK8xQn5l7Yy75iPF+KP3vVmDHnNl
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date:
|
38
|
+
date: 2023-01-07 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.0.
|
46
|
+
version: 2.0.8
|
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.0.
|
56
|
+
version: 2.0.8
|
57
57
|
- - "<"
|
58
58
|
- !ruby/object:Gem::Version
|
59
59
|
version: 3.0.0
|
@@ -92,7 +92,7 @@ files:
|
|
92
92
|
- MIT-LICENSE
|
93
93
|
- README.md
|
94
94
|
- certs/cert_chain.pem
|
95
|
-
- config/errors.yml
|
95
|
+
- config/locales/errors.yml
|
96
96
|
- docker-compose.yml
|
97
97
|
- lib/waterdrop.rb
|
98
98
|
- lib/waterdrop/config.rb
|
@@ -108,6 +108,7 @@ files:
|
|
108
108
|
- lib/waterdrop/instrumentation/notifications.rb
|
109
109
|
- lib/waterdrop/instrumentation/vendors/datadog/dashboard.json
|
110
110
|
- lib/waterdrop/instrumentation/vendors/datadog/listener.rb
|
111
|
+
- lib/waterdrop/middleware.rb
|
111
112
|
- lib/waterdrop/patches/rdkafka/metadata.rb
|
112
113
|
- lib/waterdrop/patches/rdkafka/producer.rb
|
113
114
|
- lib/waterdrop/producer.rb
|
@@ -141,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
142
|
- !ruby/object:Gem::Version
|
142
143
|
version: '0'
|
143
144
|
requirements: []
|
144
|
-
rubygems_version: 3.
|
145
|
+
rubygems_version: 3.4.1
|
145
146
|
signing_key:
|
146
147
|
specification_version: 4
|
147
148
|
summary: Kafka messaging made easy!
|
metadata.gz.sig
CHANGED
Binary file
|