waterdrop 2.4.6 → 2.4.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a072e6e8ee65e0ce6f5c337c3a56b043140d0a5c60a1fd93d70b329c50932c5
4
- data.tar.gz: 3b0d853ed72939da7302a89cca314d105bf3eef7c7abebe0c37c45960c2e8a80
3
+ metadata.gz: 8fab3f2b565a128bcc34f8d8bd10b36b0bd9546d1c52109a97ad87449fcd1731
4
+ data.tar.gz: 891f381590986451b330f74ac0ab56e6d2873d84a778d33bf470db1fe5e8447d
5
5
  SHA512:
6
- metadata.gz: 50b61ccd6d84ee67a7de95dde8dfc926dd77cc0ebcba6299704a897da88c55a33352a87f0bed725160dd2859e6258d683dc73fec0c92bf3370eeddf1b4490e3c
7
- data.tar.gz: a44bdb91da44a8767a385d829eaefd86f214c1baf03b5776c9d3f3d89ac6e2106b07b0da3185208b160110f0cfc5002bb779af66a4682db7d4d96211de7964fd
6
+ metadata.gz: de29a31f2d498bfd04577ddd7edec90603d1226bc9b355d3e65e62fae0e2cb0f89bc5708a0734425df0fc5458adcb01e5b59775fda3daaceab1de586b41ed07d
7
+ data.tar.gz: 052ea95a7c819c42ba62c1275b4895cf9b034e429ef34af0d4556965dd28cd0a9307a0a5613f3a126b4e41da6b0cfe8042e3f614045929203faca67c496d4352
checksums.yaml.gz.sig CHANGED
Binary file
@@ -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.1'
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.1
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
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.6)
5
- karafka-core (>= 2.0.6, < 3.0.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.6)
25
+ karafka-core (2.0.8)
26
26
  concurrent-ruby (>= 1.1)
27
27
  rdkafka (>= 0.12)
28
- mini_portile2 (2.8.0)
29
- minitest (5.16.3)
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.0)
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.0)
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.21.2)
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.3.26
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
@@ -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 == :timed_out
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
- now,
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
- now - cached.first > PARTITIONS_COUNT_TTL
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
@@ -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
@@ -3,5 +3,5 @@
3
3
  # WaterDrop library
4
4
  module WaterDrop
5
5
  # Current WaterDrop version
6
- VERSION = '2.4.6'
6
+ VERSION = '2.4.8'
7
7
  end
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.6', '< 3.0.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.6
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: 2022-12-10 00:00:00.000000000 Z
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.6
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.6
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.3.26
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