ruby-kafka 0.6.0.beta3 → 0.6.0.beta4

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: b0de3ad482f6130c080cc00b0dd7f4d862861838cfd6c762f6235defada9652f
4
- data.tar.gz: c97c1432402dc5d78816ffb0c251e1c808c4ebaaad7609e3f8b7265d0661db8a
3
+ metadata.gz: 58773abdf15d690fb38890feb0788e3a47cb26312aa8022f869691dc6b56c3c6
4
+ data.tar.gz: e63116cf01fd6a140976179d63b9368b0a78855a3cbbb580ad84da74750b31a0
5
5
  SHA512:
6
- metadata.gz: 647f4e5908073b08c597e90bc229c9962cf53121590297857caa641ebed7007331aa434238a719584b5c4ef66668a3a9fbe9a732105577b51f3fbc2539b6b3c9
7
- data.tar.gz: dce76f84ed3ad8a010935321adc225f5ac757c33cfb39fee8d84c67c4f36bc4fdb053123f84638324695a4ec834d5765a0f44abd66eeb0c153762907f1737e55
6
+ metadata.gz: 421cb2a9813a72cb8b6821323427e2c2dd86467b9c5658b0a095650c87578dccc69bd48aa3c094a8c9827ea5bb18e752c24fe846c92540522c3bf2710ac41f07
7
+ data.tar.gz: cbef389f1c4b6336fcc957d27f18d0e37e76149a814fbfee39aeb7b8ec21c3aa39d5f4f3945ad953a70c67aea06273e096937da7be9e9daa3978ec7c84994fde
data/CHANGELOG.md CHANGED
@@ -5,6 +5,8 @@ Changes and additions to the library will be listed here.
5
5
  ## Unreleased
6
6
 
7
7
  - Fetch messages asynchronously (#526).
8
+ - Add support for exponential backoff in pauses (#566).
9
+ - Instrument pause durations (#574).
8
10
 
9
11
  ## v0.5.5
10
12
 
@@ -1,6 +1,7 @@
1
1
  require "kafka/consumer_group"
2
2
  require "kafka/offset_manager"
3
3
  require "kafka/fetcher"
4
+ require "kafka/pause"
4
5
 
5
6
  module Kafka
6
7
 
@@ -50,8 +51,11 @@ module Kafka
50
51
  @fetcher = fetcher
51
52
  @heartbeat = heartbeat
52
53
 
53
- # A list of partitions that have been paused, per topic.
54
- @paused_partitions = {}
54
+ @pauses = Hash.new {|h, k|
55
+ h[k] = Hash.new {|h2, k2|
56
+ h2[k2] = Pause.new
57
+ }
58
+ }
55
59
 
56
60
  # Whether or not the consumer is currently consuming messages.
57
61
  @running = false
@@ -113,16 +117,28 @@ module Kafka
113
117
  # the rest of the partitions to continue being processed.
114
118
  #
115
119
  # If the `timeout` argument is passed, the partition will automatically be
116
- # resumed when the timeout expires.
120
+ # resumed when the timeout expires. If `exponential_backoff` is enabled, each
121
+ # subsequent pause will cause the timeout to double until a message from the
122
+ # partition has been successfully processed.
117
123
  #
118
124
  # @param topic [String]
119
125
  # @param partition [Integer]
120
- # @param timeout [Integer] the number of seconds to pause the partition for,
126
+ # @param timeout [nil, Integer] the number of seconds to pause the partition for,
121
127
  # or `nil` if the partition should not be automatically resumed.
128
+ # @param max_timeout [nil, Integer] the maximum number of seconds to pause for,
129
+ # or `nil` if no maximum should be enforced.
130
+ # @param exponential_backoff [Boolean] whether to enable exponential backoff.
122
131
  # @return [nil]
123
- def pause(topic, partition, timeout: nil)
124
- @paused_partitions[topic] ||= {}
125
- @paused_partitions[topic][partition] = timeout && Time.now + timeout
132
+ def pause(topic, partition, timeout: nil, max_timeout: nil, exponential_backoff: false)
133
+ if max_timeout && !exponential_backoff
134
+ raise ArgumentError, "`max_timeout` only makes sense when `exponential_backoff` is enabled"
135
+ end
136
+
137
+ pause_for(topic, partition).pause!(
138
+ timeout: timeout,
139
+ max_timeout: max_timeout,
140
+ exponential_backoff: exponential_backoff,
141
+ )
126
142
  end
127
143
 
128
144
  # Resume processing of a topic partition.
@@ -132,8 +148,7 @@ module Kafka
132
148
  # @param partition [Integer]
133
149
  # @return [nil]
134
150
  def resume(topic, partition)
135
- paused_partitions = @paused_partitions.fetch(topic, {})
136
- paused_partitions.delete(partition)
151
+ pause_for(topic, partition).resume!
137
152
 
138
153
  seek_to_next(topic, partition)
139
154
  end
@@ -145,16 +160,7 @@ module Kafka
145
160
  # @param partition [Integer]
146
161
  # @return [Boolean] true if the partition is paused, false otherwise.
147
162
  def paused?(topic, partition)
148
- partitions = @paused_partitions.fetch(topic, {})
149
-
150
- if partitions.key?(partition)
151
- # Users can set an optional timeout, after which the partition is
152
- # automatically resumed. When pausing, the timeout is translated to an
153
- # absolute point in time.
154
- timeout = partitions.fetch(partition)
155
-
156
- timeout.nil? || Time.now < timeout
157
- end
163
+ pause_for(topic, partition).paused?
158
164
  end
159
165
 
160
166
  # Fetches and enumerates the messages in the topics that the consumer group
@@ -230,6 +236,10 @@ module Kafka
230
236
 
231
237
  return if !@running
232
238
  end
239
+
240
+ # We've successfully processed a batch from the partition, so we can clear
241
+ # the pause.
242
+ pause_for(batch.topic, batch.partition).reset!
233
243
  end
234
244
 
235
245
  # We may not have received any messages, but it's still a good idea to
@@ -305,6 +315,10 @@ module Kafka
305
315
  end
306
316
 
307
317
  mark_message_as_processed(batch.messages.last) if automatically_mark_as_processed
318
+
319
+ # We've successfully processed a batch from the partition, so we can clear
320
+ # the pause.
321
+ pause_for(batch.topic, batch.partition).reset!
308
322
  end
309
323
 
310
324
  @offset_manager.commit_offsets_if_necessary
@@ -449,9 +463,15 @@ module Kafka
449
463
  end
450
464
 
451
465
  def resume_paused_partitions!
452
- @paused_partitions.each do |topic, partitions|
453
- partitions.keys.each do |partition|
454
- unless paused?(topic, partition)
466
+ @pauses.each do |topic, partitions|
467
+ partitions.each do |partition, pause|
468
+ @instrumenter.instrument("pause_status.consumer", {
469
+ topic: topic,
470
+ partition: partition,
471
+ duration: pause.pause_duration,
472
+ })
473
+
474
+ if pause.paused? && pause.expired?
455
475
  @logger.info "Automatically resuming partition #{topic}/#{partition}, pause timeout expired"
456
476
  resume(topic, partition)
457
477
  end
@@ -494,5 +514,9 @@ module Kafka
494
514
 
495
515
  raise FetchError, e
496
516
  end
517
+
518
+ def pause_for(topic, partition)
519
+ @pauses[topic][partition]
520
+ end
497
521
  end
498
522
  end
data/lib/kafka/datadog.rb CHANGED
@@ -217,6 +217,19 @@ module Kafka
217
217
  end
218
218
  end
219
219
 
220
+ def pause_status(event)
221
+ tags = {
222
+ client: event.payload.fetch(:client_id),
223
+ group_id: event.payload.fetch(:group_id),
224
+ topic: event.payload.fetch(:topic),
225
+ partition: event.payload.fetch(:partition),
226
+ }
227
+
228
+ duration = event.payload.fetch(:duration)
229
+
230
+ gauge("consumer.pause.duration", duration, tags: tags)
231
+ end
232
+
220
233
  attach_to "consumer.kafka"
221
234
  end
222
235
 
@@ -0,0 +1,90 @@
1
+ module Kafka
2
+ # Manages the pause state of a partition.
3
+ #
4
+ # The processing of messages in a partition can be paused, e.g. if there was
5
+ # an exception during processing. This could be caused by a downstream service
6
+ # not being available. A typical way of solving such an issue is to back off
7
+ # for a little while and then try again. In order to do that, _pause_ the
8
+ # partition.
9
+ class Pause
10
+ def initialize(clock: Time)
11
+ @clock = clock
12
+ @started_at = nil
13
+ @pauses = 0
14
+ @timeout = nil
15
+ @max_timeout = nil
16
+ @exponential_backoff = false
17
+ end
18
+
19
+ # Mark the partition as paused.
20
+ #
21
+ # If exponential backoff is enabled, each subsequent pause of a partition will
22
+ # cause a doubling of the actual timeout, i.e. for pause number _n_, the actual
23
+ # timeout will be _2^n * timeout_.
24
+ #
25
+ # Only when {#reset!} is called is this state cleared.
26
+ #
27
+ # @param timeout [nil, Integer] if specified, the partition will automatically
28
+ # resume after this many seconds.
29
+ # @param exponential_backoff [Boolean] whether to enable exponential timeouts.
30
+ def pause!(timeout: nil, max_timeout: nil, exponential_backoff: false)
31
+ @started_at = @clock.now
32
+ @timeout = timeout
33
+ @max_timeout = max_timeout
34
+ @exponential_backoff = exponential_backoff
35
+ @pauses += 1
36
+ end
37
+
38
+ # Resumes the partition.
39
+ #
40
+ # The number of pauses is still retained, and if the partition is paused again
41
+ # it may be with an exponential backoff.
42
+ def resume!
43
+ @started_at = nil
44
+ @timeout = nil
45
+ @max_timeout = nil
46
+ end
47
+
48
+ # Whether the partition is currently paused.
49
+ def paused?
50
+ # This is nil if we're not currently paused.
51
+ return false if @started_at.nil?
52
+
53
+ # If no timeout is set we pause forever.
54
+ return true if @timeout.nil?
55
+
56
+ !expired?
57
+ end
58
+
59
+ def pause_duration
60
+ if paused?
61
+ Time.now - @started_at
62
+ else
63
+ 0
64
+ end
65
+ end
66
+
67
+ # Whether the pause has expired.
68
+ def expired?
69
+ !@timeout.nil? && @clock.now >= ends_at
70
+ end
71
+
72
+ # Resets the pause state, ensuring that the next pause is not exponential.
73
+ def reset!
74
+ @pauses = 0
75
+ end
76
+
77
+ private
78
+
79
+ def ends_at
80
+ # Apply an exponential backoff to the timeout.
81
+ backoff_factor = @exponential_backoff ? 2**(@pauses - 1) : 1
82
+ timeout = backoff_factor * @timeout
83
+
84
+ # If set, don't allow a timeout longer than max_timeout.
85
+ timeout = @max_timeout if @max_timeout && timeout > @max_timeout
86
+
87
+ @started_at + timeout
88
+ end
89
+ end
90
+ end
data/lib/kafka/statsd.rb CHANGED
@@ -155,6 +155,17 @@ module Kafka
155
155
  end
156
156
  end
157
157
 
158
+ def pause_status(event)
159
+ client = event.payload.fetch(:client_id)
160
+ group_id = event.payload.fetch(:group_id)
161
+ topic = event.payload.fetch(:topic)
162
+ partition = event.payload.fetch(:partition)
163
+
164
+ duration = event.payload.fetch(:duration)
165
+
166
+ gauge("consumer.#{client}.#{group_id}.#{topic}.#{partition}.pause.duration", duration)
167
+ end
168
+
158
169
  attach_to "consumer.kafka"
159
170
  end
160
171
 
data/lib/kafka/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kafka
2
- VERSION = "0.6.0.beta3"
2
+ VERSION = "0.6.0.beta4"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-kafka
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0.beta3
4
+ version: 0.6.0.beta4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Schierbeck
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-04-24 00:00:00.000000000 Z
11
+ date: 2018-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -331,6 +331,7 @@ files:
331
331
  - lib/kafka/message_buffer.rb
332
332
  - lib/kafka/offset_manager.rb
333
333
  - lib/kafka/partitioner.rb
334
+ - lib/kafka/pause.rb
334
335
  - lib/kafka/pending_message.rb
335
336
  - lib/kafka/pending_message_queue.rb
336
337
  - lib/kafka/produce_operation.rb