racecar 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +73 -11
- data/lib/racecar/config.rb +6 -2
- data/lib/racecar/consumer.rb +2 -0
- data/lib/racecar/runner.rb +11 -5
- data/lib/racecar/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 712a502336151d29108a93f1f00d2eb3b8b0b072
|
4
|
+
data.tar.gz: 9cd9f1fe7b345f1dc9aaf8ceba6751f7e848df98
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c468794299f01ab66e9727bca985cc334cbcf0c200ec89260f6e3eca55a6325823ab3741c92d0d43f119abea99f3111a534b081da6dd7ae0aaea483b7c56723f
|
7
|
+
data.tar.gz: aaff7d20a5da77ea2b447fa4ed554c3a1230eafca78db0fecfee03c9cd11e37d7a0f0cb6bbf566f3343bfe31c07c643c9b248f0943b3eb2829d34ba36a2bb3ec
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Introducing Racecar, your friendly and easy-to-approach Kafka consumer framework!
|
4
4
|
|
5
|
-
Using [ruby-kafka](https://github.com/zendesk/ruby-kafka) directly can be a challenge: it's a flexible library with lots of knobs and options. Most users don't need that level of flexibility, though. Racecar provides a simple and intuitive way to build and configure Kafka consumers that optionally integrates
|
5
|
+
Using [ruby-kafka](https://github.com/zendesk/ruby-kafka) directly can be a challenge: it's a flexible library with lots of knobs and options. Most users don't need that level of flexibility, though. Racecar provides a simple and intuitive way to build and configure Kafka consumers that optionally integrates seamlessly with Rails.
|
6
6
|
|
7
7
|
## Table of content
|
8
8
|
|
@@ -13,8 +13,9 @@ Using [ruby-kafka](https://github.com/zendesk/ruby-kafka) directly can be a chal
|
|
13
13
|
3. [Configuration](#configuration)
|
14
14
|
4. [Testing consumers](#testing-consumers)
|
15
15
|
5. [Deploying consumers](#deploying-consumers)
|
16
|
-
6. [
|
17
|
-
7. [
|
16
|
+
6. [Handling errors](#handling-errors)
|
17
|
+
7. [Logging](#logging)
|
18
|
+
8. [Operations](#operations)
|
18
19
|
3. [Development](#development)
|
19
20
|
4. [Contributing](#contributing)
|
20
21
|
5. [Copyright and license](#copyright-and-license)
|
@@ -86,14 +87,14 @@ You can optionally add an `initialize` method if you need to do any set-up work
|
|
86
87
|
```ruby
|
87
88
|
class PushNotificationConsumer < Racecar::Consumer
|
88
89
|
subscribes_to "notifications"
|
89
|
-
|
90
|
+
|
90
91
|
def initialize
|
91
92
|
@push_service = PushService.new # pretend this exists.
|
92
93
|
end
|
93
|
-
|
94
|
+
|
94
95
|
def process(message)
|
95
96
|
data = JSON.parse(message.value)
|
96
|
-
|
97
|
+
|
97
98
|
@push_service.notify!(
|
98
99
|
recipient: data.fetch("recipient"),
|
99
100
|
notification: data.fetch("notification"),
|
@@ -142,6 +143,28 @@ end
|
|
142
143
|
|
143
144
|
An important detail is that, if an exception is raised while processing a batch, the _whole batch_ is re-processed.
|
144
145
|
|
146
|
+
#### Tearing down resources when stopping
|
147
|
+
|
148
|
+
When a Racecar consumer shuts down, it gets the opportunity to tear down any resources held by the consumer instance. For example, it may make sense to close any open files or network connections. Doing so is simple: just implement a `#teardown` method in your consumer class and it will be called during the shutdown procedure.
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
class ArchiveConsumer < Racecar::Consumer
|
152
|
+
subscribes_to "events"
|
153
|
+
|
154
|
+
def initialize
|
155
|
+
@file = File.open("archive", "a")
|
156
|
+
end
|
157
|
+
|
158
|
+
def process(message)
|
159
|
+
@file << message.value
|
160
|
+
end
|
161
|
+
|
162
|
+
def teardown
|
163
|
+
@file.close
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
145
168
|
### Running consumers
|
146
169
|
|
147
170
|
Racecar is first and foremost an executable _consumer runner_. The `racecar` executable takes as argument the name of the consumer class that should be run. Racecar automatically loads your Rails application before starting, and you can load any other library you need by passing the `--require` flag, e.g.
|
@@ -176,8 +199,9 @@ The consumers will checkpoint their positions from time to time in order to be a
|
|
176
199
|
|
177
200
|
All timeouts are defined in number of seconds.
|
178
201
|
|
202
|
+
* `session_timeout` – The idle timeout after which a consumer is kicked out of the group. Consumers must send heartbeats with at least this frequency.
|
179
203
|
* `heartbeat_interval` – How often to send a heartbeat message to Kafka.
|
180
|
-
* `pause_timeout` – How long to pause a partition for if the consumer raises an exception while processing a message.
|
204
|
+
* `pause_timeout` – How long to pause a partition for if the consumer raises an exception while processing a message. Default is to pause for 10 seconds. Set this to zero in order to disable automatic pausing of partitions.
|
181
205
|
* `connect_timeout` – How long to wait when trying to connect to a Kafka broker. Default is 10 seconds.
|
182
206
|
* `socket_timeout` – How long to wait when trying to communicate with a Kafka broker. Default is 30 seconds.
|
183
207
|
* `max_wait_time` – How long to allow the Kafka brokers to wait before returning messages. A higher number means larger batches, at the cost of higher latency. Default is 5 seconds.
|
@@ -218,10 +242,10 @@ Here's an example of testing a consumer class using [RSpec](http://rspec.info/)
|
|
218
242
|
# `email-addresses` topic.
|
219
243
|
class CreateContactsConsumer < Racecar::Consumer
|
220
244
|
subscribes_to "email-addresses"
|
221
|
-
|
245
|
+
|
222
246
|
def process(message)
|
223
247
|
email = message.value
|
224
|
-
|
248
|
+
|
225
249
|
Contact.create!(email: email)
|
226
250
|
end
|
227
251
|
end
|
@@ -231,9 +255,9 @@ describe CreateContactsConsumer do
|
|
231
255
|
it "creates a Contact for each email address in the topic" do
|
232
256
|
message = double("message", value: "john@example.com")
|
233
257
|
consumer = CreateContactsConsumer.new
|
234
|
-
|
258
|
+
|
235
259
|
consumer.process(message)
|
236
|
-
|
260
|
+
|
237
261
|
expect(Contact.where(email: "john@example.com")).to exist
|
238
262
|
end
|
239
263
|
end
|
@@ -256,6 +280,44 @@ If you've ever used Heroku you'll recognize the format – indeed, deploying to
|
|
256
280
|
With Foreman, you can easily run these processes locally by executing `foreman run`; in production you'll want to _export_ to another process management format such as Upstart or Runit. [capistrano-foreman](https://github.com/hyperoslo/capistrano-foreman) allows you to do this with Capistrano.
|
257
281
|
|
258
282
|
|
283
|
+
### Handling errors
|
284
|
+
|
285
|
+
When processing messages from a Kafka topic, your code may encounter an error and raise an exception. The cause is typically on of two things:
|
286
|
+
|
287
|
+
1. The message being processed is somehow malformed or doesn't conform with the assumptions made by the processing code.
|
288
|
+
2. You're using some external resource such as a database or a network API that is temporarily unavailable.
|
289
|
+
|
290
|
+
In the first case, you'll need to either skip the message or deploy a new version of your consumer that can correctly handle the message that caused the error. In order to skip a message, handle the relevant exception in your `#process` method:
|
291
|
+
|
292
|
+
```ruby
|
293
|
+
def process(message)
|
294
|
+
data = JSON.parse(message.value)
|
295
|
+
# ...
|
296
|
+
rescue JSON::ParserError => e
|
297
|
+
puts "Failed to process message in #{message.topic}/#{message.partition} at offset #{message.offset}: #{e}"
|
298
|
+
# It's probably a good idea to report the exception to an exception tracker service.
|
299
|
+
end
|
300
|
+
```
|
301
|
+
|
302
|
+
Since the exception is handled by your `#process` method and is no longer raised, Racecar will consider the message successfully processed. Tracking these errors in an exception tracker or some other monitoring system is highly recommended, as you otherwise will have little insight into how many messages are being skipped this way.
|
303
|
+
|
304
|
+
If, on the other hand, the exception was cause by a temporary network or database problem, you will probably want to retry processing of the message after some time has passed. By default, if an exception is raised by the `#process` method, the consumer will pause all processing of the message's partition for some number of seconds, configured by setting the `pause_timeout` configuration variable. This allows the consumer to continue processing messages from other partitions that may not be impacted by the problem while still making sure to not drop the original message. Since messages in a single Kafka topic partition _must_ be processed in order, it's not possible to keep processing _other_ messages in that partition.
|
305
|
+
|
306
|
+
In addition to retrying the processing of messages, Racecar also allows defining an _error handler_ callback that is invoked whenever an exception is raised by your `#process` method. This allows you to track and report errors to a monitoring system:
|
307
|
+
|
308
|
+
```ruby
|
309
|
+
Racecar.config.on_error do |exception, info|
|
310
|
+
MyErrorTracker.report(exception, {
|
311
|
+
topic: info[:topic],
|
312
|
+
partition: info[:partition],
|
313
|
+
offset: info[:offset],
|
314
|
+
})
|
315
|
+
end
|
316
|
+
```
|
317
|
+
|
318
|
+
It is highly recommended that you set up an error handler.
|
319
|
+
|
320
|
+
|
259
321
|
### Logging
|
260
322
|
|
261
323
|
By default, Racecar will log to `STDOUT`. If you're using Rails, your application code will use whatever logger you've configured there.
|
data/lib/racecar/config.rb
CHANGED
@@ -11,6 +11,7 @@ module Racecar
|
|
11
11
|
offset_commit_interval
|
12
12
|
offset_commit_threshold
|
13
13
|
|
14
|
+
session_timeout
|
14
15
|
heartbeat_interval
|
15
16
|
pause_timeout
|
16
17
|
connect_timeout
|
@@ -55,8 +56,11 @@ module Racecar
|
|
55
56
|
# Default is to send a heartbeat every 10 seconds.
|
56
57
|
heartbeat_interval: 10,
|
57
58
|
|
58
|
-
# Default is to
|
59
|
-
pause_timeout:
|
59
|
+
# Default is to pause partitions for 10 seconds on processing errors.
|
60
|
+
pause_timeout: 10,
|
61
|
+
|
62
|
+
# Default is to kick consumers out of a group after 30 seconds without activity.
|
63
|
+
session_timeout: 30,
|
60
64
|
|
61
65
|
# Default is to allow at most 10 seconds when connecting to a broker.
|
62
66
|
connect_timeout: 10,
|
data/lib/racecar/consumer.rb
CHANGED
data/lib/racecar/runner.rb
CHANGED
@@ -2,12 +2,17 @@ require "kafka"
|
|
2
2
|
|
3
3
|
module Racecar
|
4
4
|
class Runner
|
5
|
-
attr_reader :processor, :config, :logger
|
5
|
+
attr_reader :processor, :config, :logger, :consumer
|
6
6
|
|
7
7
|
def initialize(processor, config:, logger:)
|
8
8
|
@processor, @config, @logger = processor, config, logger
|
9
9
|
end
|
10
10
|
|
11
|
+
def stop
|
12
|
+
processor.teardown
|
13
|
+
consumer.stop unless consumer.nil?
|
14
|
+
end
|
15
|
+
|
11
16
|
def run
|
12
17
|
kafka = Kafka.new(
|
13
18
|
client_id: config.client_id,
|
@@ -20,17 +25,18 @@ module Racecar
|
|
20
25
|
ssl_client_cert_key: config.ssl_client_cert_key,
|
21
26
|
)
|
22
27
|
|
23
|
-
consumer = kafka.consumer(
|
28
|
+
@consumer = kafka.consumer(
|
24
29
|
group_id: config.group_id,
|
25
30
|
offset_commit_interval: config.offset_commit_interval,
|
26
31
|
offset_commit_threshold: config.offset_commit_threshold,
|
32
|
+
session_timeout: config.session_timeout,
|
27
33
|
heartbeat_interval: config.heartbeat_interval,
|
28
34
|
)
|
29
35
|
|
30
36
|
# Stop the consumer on SIGINT, SIGQUIT or SIGTERM.
|
31
|
-
trap("QUIT") {
|
32
|
-
trap("INT") {
|
33
|
-
trap("TERM") {
|
37
|
+
trap("QUIT") { stop }
|
38
|
+
trap("INT") { stop }
|
39
|
+
trap("TERM") { stop }
|
34
40
|
|
35
41
|
# Print the consumer config to STDERR on USR1.
|
36
42
|
trap("USR1") { $stderr.puts config.inspect }
|
data/lib/racecar/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: racecar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Schierbeck
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2017-08-
|
12
|
+
date: 2017-08-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ruby-kafka
|