fluent-plugin-kafka 0.19.8 → 0.19.9
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
- data/.github/workflows/linux.yml +2 -15
- data/ChangeLog +22 -0
- data/README.md +40 -3
- data/fluent-plugin-kafka.gemspec +1 -1
- data/lib/fluent/plugin/in_kafka.rb +11 -1
- data/lib/fluent/plugin/in_kafka_group.rb +8 -0
- data/lib/fluent/plugin/out_kafka2.rb +33 -6
- data/test/plugin/test_in_kafka.rb +26 -0
- data/test/plugin/test_in_kafka_group.rb +13 -0
- data/test/plugin/test_out_kafka2.rb +73 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2eeb3b37a48db8cde6fda0eb9e413f507076c231abd06456a35540a10eed5667
|
|
4
|
+
data.tar.gz: 90b95493d3445829123ee569af66f581b109dcea7f91cf3d9aee6b2b6b9caeab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3abf77244f8a7b239e92f63c798f0dc70b0d078a3b75de8f17ddc9e7cb17a8108088cc4f4bc3e44005496c68c89e960ca727def67cb7418f8e001bb713c938b4
|
|
7
|
+
data.tar.gz: 892d0c09e810b9fe78f87917a8468ac392b206991e99c4080f755e80953db79d8637e41717ef3ae0acbf13baa26e2fd1fe032bef7650ca70bc62df44b1dead7a
|
data/.github/workflows/linux.yml
CHANGED
|
@@ -11,7 +11,7 @@ jobs:
|
|
|
11
11
|
uses: ruby/actions/.github/workflows/ruby_versions.yml@master
|
|
12
12
|
with:
|
|
13
13
|
engine: cruby
|
|
14
|
-
min_version: 3.
|
|
14
|
+
min_version: 3.2
|
|
15
15
|
build:
|
|
16
16
|
needs: ruby-versions
|
|
17
17
|
runs-on: ${{ matrix.os }}
|
|
@@ -29,25 +29,12 @@ jobs:
|
|
|
29
29
|
- { min: '>= 0.14.0', max: '< 0.16.0' }
|
|
30
30
|
- { min: '>= 0.16.0', max: '< 0.25.0' }
|
|
31
31
|
- { min: '>= 0.25.0', max: '>= 0.25.0' }
|
|
32
|
-
# rdkafka 0.25.0 supports Ruby 3.2+, so we exclude it for Ruby 3.0 and 3.1
|
|
33
32
|
exclude:
|
|
34
33
|
- ruby: head
|
|
35
|
-
- ruby: '3.1'
|
|
36
|
-
rdkafka_versions: { min: '>= 0.25.0', max: '>= 0.25.0' }
|
|
37
|
-
- ruby: '3.0'
|
|
38
|
-
rdkafka_versions: { min: '>= 0.25.0', max: '>= 0.25.0' }
|
|
39
|
-
# rdkafka 0.15.2 is the last version which supports Ruby 2.7
|
|
40
|
-
include:
|
|
41
|
-
- ruby: '2.7'
|
|
42
|
-
os: ubuntu-latest
|
|
43
|
-
rdkafka_versions: { min: '>= 0.6.0', max: '< 0.12.0' }
|
|
44
|
-
- ruby: '2.7'
|
|
45
|
-
os: ubuntu-latest
|
|
46
|
-
rdkafka_versions: { min: '>= 0.12.0', max: '= 0.15.2' }
|
|
47
34
|
name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }} with rdkafka gem version (min ${{ matrix.rdkafka_versions.min }} max ${{ matrix.rdkafka_versions.max }})
|
|
48
35
|
steps:
|
|
49
36
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
50
|
-
- uses: ruby/setup-ruby@
|
|
37
|
+
- uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
|
|
51
38
|
with:
|
|
52
39
|
ruby-version: ${{ matrix.ruby }}
|
|
53
40
|
- name: Install confluent-kafka
|
data/ChangeLog
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
Release 0.19.9 - 2026/10/30
|
|
2
|
+
* out_kafka2: Fix duplicated records and failing writes with
|
|
3
|
+
`share_producer true`. All flush threads used one ruby-kafka producer
|
|
4
|
+
at the same time, although it is not thread safe. NOTE: only one flush
|
|
5
|
+
thread at a time now uses the shared producer, so chunks are no longer
|
|
6
|
+
sent in parallel even if `flush_thread_count` is 2 or more. Keep
|
|
7
|
+
`share_producer false` to send them in parallel. (#589)
|
|
8
|
+
* out_kafka2: Fix later writes failing until restart after a delivery
|
|
9
|
+
error. Since 0.18.0, the messages of a failed delivery stayed in the
|
|
10
|
+
producer, so later writes could keep failing with
|
|
11
|
+
`Kafka::MessageSizeTooLarge`. The producer is now discarded after a
|
|
12
|
+
failure and created again on the next write. (#588, #589)
|
|
13
|
+
* in_kafka: Fix endless re-fetching of a batch with a record larger than
|
|
14
|
+
`chunk_limit_size`. The offset did not advance, and the records that
|
|
15
|
+
did fit were emitted again on every `interval`. NOTE: such a record is
|
|
16
|
+
now dropped with a warning. Use `<label @ERROR>` to keep it. (#584)
|
|
17
|
+
* in_kafka_group: Fix endless consumer restarts on a record larger than
|
|
18
|
+
`chunk_limit_size`. The consumer restarted without committing offsets,
|
|
19
|
+
so the same batch was fetched forever, and the records that did fit
|
|
20
|
+
were emitted again each time. NOTE: such a record is now dropped with a
|
|
21
|
+
warning. Use `<label @ERROR>` to keep it. (#583)
|
|
22
|
+
|
|
1
23
|
Release 0.19.8 - 2026/09/04
|
|
2
24
|
* in_kafka: Emit each record with its own tag when `tag_source record` is
|
|
3
25
|
set. The whole fetch batch used to be emitted with the tag of the
|
data/README.md
CHANGED
|
@@ -263,6 +263,8 @@ The `<formatter name>` in `<format>` uses fluentd's formatter plugins. See [form
|
|
|
263
263
|
|
|
264
264
|
**Note:** Java based Kafka client uses `murmur2` as partitioner function by default. If you want to use same partitioning behavior with fluent-plugin-kafka, change it to `murmur2` instead of `crc32`. Note that for using `murmur2` hash partitioner function, you must install `digest-murmurhash` gem.
|
|
265
265
|
|
|
266
|
+
**Note:** `share_producer true` makes all flush threads use one ruby-kafka producer. This reduces the connections to the brokers. However, the ruby-kafka producer is not thread safe, so the flush threads send chunks one at a time. To send chunks in parallel with `flush_thread_count`, keep `share_producer` false. `rdkafka2` can share its producer without this limit, because librdkafka is thread safe.
|
|
267
|
+
|
|
266
268
|
ruby-kafka sometimes returns `Kafka::DeliveryFailed` error without good information.
|
|
267
269
|
In this case, `get_kafka_client_log` is useful for identifying the error cause.
|
|
268
270
|
ruby-kafka's log is routed to fluentd log so you can see ruby-kafka's log in fluentd logs.
|
|
@@ -273,7 +275,7 @@ Supports following ruby-kafka's producer options.
|
|
|
273
275
|
- required_acks - default: -1 - The number of acks required per request. If you need flush performance, set lower value, e.g. 1, 2.
|
|
274
276
|
- ack_timeout - default: nil - How long the producer waits for acks. The unit is seconds.
|
|
275
277
|
- compression_codec - default: nil - The codec the producer uses to compress messages.
|
|
276
|
-
- max_send_limit_bytes - default: nil - Max byte size
|
|
278
|
+
- max_send_limit_bytes - default: nil - Max byte size of one record after formatting. A larger record is dropped with a warning, and the other records in the chunk are sent. For example, if you set 1000000, a record larger than 1000000 bytes is dropped. This does not limit the size of a record batch, so it does not prevent MessageSizeTooLarge by itself. See [FAQ](#why-does-fluentd-fail-with-kafkamessagesizetoolarge-or-msg_size_too_large).
|
|
277
279
|
- discard_kafka_delivery_failed - default: false - discard the record where [Kafka::DeliveryFailed](http://www.rubydoc.info/gems/ruby-kafka/Kafka/DeliveryFailed) occurred
|
|
278
280
|
|
|
279
281
|
If you want to know about detail of monitoring, see also https://github.com/zendesk/ruby-kafka#monitoring
|
|
@@ -451,7 +453,7 @@ Support of fluentd v0.12 has ended. `kafka_buffered` will be an alias of `kafka2
|
|
|
451
453
|
- required_acks - default: -1 - The number of acks required per request. If you need flush performance, set lower value, e.g. 1, 2.
|
|
452
454
|
- ack_timeout - default: nil - How long the producer waits for acks. The unit is seconds.
|
|
453
455
|
- compression_codec - default: nil - The codec the producer uses to compress messages.
|
|
454
|
-
- max_send_limit_bytes - default: nil - Max byte size
|
|
456
|
+
- max_send_limit_bytes - default: nil - Max byte size of one record after formatting. A larger record is dropped with a warning, and the other records in the chunk are sent. For example, if you set 1000000, a record larger than 1000000 bytes is dropped. This does not limit the size of a record batch, so it does not prevent MessageSizeTooLarge by itself. See [FAQ](#why-does-fluentd-fail-with-kafkamessagesizetoolarge-or-msg_size_too_large).
|
|
455
457
|
- discard_kafka_delivery_failed - default: false - discard the record where [Kafka::DeliveryFailed](http://www.rubydoc.info/gems/ruby-kafka/Kafka/DeliveryFailed) occurred
|
|
456
458
|
- monitoring_list - default: [] - library to be used to monitor. statsd and datadog are supported
|
|
457
459
|
|
|
@@ -564,7 +566,8 @@ You need to install rdkafka gem.
|
|
|
564
566
|
share_producer (bool) :default => false
|
|
565
567
|
# Timeout for polling message wait. If 0, no wait.
|
|
566
568
|
rdkafka_delivery_handle_poll_timeout (integer) :default => 30
|
|
567
|
-
#
|
|
569
|
+
# Max byte size of one record after formatting. Larger records are dropped.
|
|
570
|
+
# This does not limit the size of a record batch. Default is no limit
|
|
568
571
|
max_send_limit_bytes (integer) :default => nil
|
|
569
572
|
# The maximum number of enqueueing bytes per second. It can reduce the
|
|
570
573
|
# load of both Fluentd and Kafka when excessive messages are attempted
|
|
@@ -608,6 +611,40 @@ To avoid the problem, there are 2 approaches:
|
|
|
608
611
|
- Upgrade your kafka cluster to latest version. This is better because recent version is faster and robust.
|
|
609
612
|
- Downgrade ruby-kafka/fluent-plugin-kafka to work with your older kafka.
|
|
610
613
|
|
|
614
|
+
### Why does Fluentd fail with Kafka::MessageSizeTooLarge (or msg_size_too_large)?
|
|
615
|
+
|
|
616
|
+
Kafka rejects a record batch that is larger than the size limit of the topic. The limit is `max.message.bytes` of the topic if it is set, and `message.max.bytes` of the broker otherwise. The topic value can be larger or smaller than the broker value. Below, "the topic limit" means this value. If one output writes to several topics, use the smallest one.
|
|
617
|
+
|
|
618
|
+
`kafka2` sends all records of a buffer chunk at once, and the records for the same partition go into one record batch. In the worst case, the whole chunk becomes one record batch. To make the batches smaller, lower `chunk_limit_size` in `<buffer>`.
|
|
619
|
+
|
|
620
|
+
`chunk_limit_size` is the size of a chunk in the buffer, where records are kept as msgpack. The records sent to Kafka can be larger, because of `<format>`, `<inject>` and the overhead of each Kafka record. Start with about half of the topic limit, and lower it if the error continues. If `<buffer>` has `compress gzip` or `compress zstd`, `chunk_limit_size` limits the compressed size, and a chunk can hold several times more data. In that case, divide it further by the compression ratio of your data. `compression_codec` works the other way. Kafka checks the size of a record batch after compression, so compressed batches leave more room. How much depends on how well your data compresses.
|
|
621
|
+
|
|
622
|
+
`max_send_limit_bytes` does not make the batch smaller. It checks only the formatted value of each record, and drops the records larger than this setting. The message key, headers and the overhead of the batch are not counted. To drop a single record that Kafka would reject, set it below the topic limit with some room for them.
|
|
623
|
+
|
|
624
|
+
The following example assumes a topic limit of 1MB.
|
|
625
|
+
|
|
626
|
+
<match app.**>
|
|
627
|
+
@type kafka2
|
|
628
|
+
brokers <broker1_host>:<broker1_port>
|
|
629
|
+
default_topic app
|
|
630
|
+
|
|
631
|
+
max_send_limit_bytes 1000000
|
|
632
|
+
|
|
633
|
+
<format>
|
|
634
|
+
@type json
|
|
635
|
+
</format>
|
|
636
|
+
|
|
637
|
+
<buffer topic>
|
|
638
|
+
chunk_limit_size 512k
|
|
639
|
+
</buffer>
|
|
640
|
+
</match>
|
|
641
|
+
|
|
642
|
+
Fluentd may log `chunk bytes limit exceeds for an emitted event stream`. This warning comes from the Fluentd buffer. It means that one event stream emitted to the output was larger than `chunk_limit_size`, and Fluentd splits it into several chunks. The number in the warning is the size of that event stream in the buffer, not the growth by formatting. If a single record is larger than `chunk_limit_size`, that record cannot be stored, and Fluentd raises `BufferChunkOverflowError`.
|
|
643
|
+
|
|
644
|
+
`kafka_buffered` splits a chunk into smaller deliveries by `kafka_agg_max_bytes` (default: 4096) and `kafka_agg_max_messages`, so `chunk_limit_size` does not decide its batch size.
|
|
645
|
+
|
|
646
|
+
`rdkafka2` passes records to librdkafka one by one, and librdkafka builds the record batches within its own `batch.size` and `message.max.bytes`. You can set them in `rdkafka_options`. Keep them below the topic limit. `msg_size_too_large` is in the default of `unrecoverable_error_codes`, so Fluentd does not retry a chunk that hits it.
|
|
647
|
+
|
|
611
648
|
## Contributing
|
|
612
649
|
|
|
613
650
|
1. Fork it
|
data/fluent-plugin-kafka.gemspec
CHANGED
|
@@ -13,7 +13,7 @@ Gem::Specification.new do |gem|
|
|
|
13
13
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
14
14
|
gem.name = "fluent-plugin-kafka"
|
|
15
15
|
gem.require_paths = ["lib"]
|
|
16
|
-
gem.version = '0.19.
|
|
16
|
+
gem.version = '0.19.9'
|
|
17
17
|
gem.required_ruby_version = ">= 2.1.0"
|
|
18
18
|
|
|
19
19
|
gem.add_dependency "fluentd", [">= 0.10.58", "< 2"]
|
|
@@ -62,6 +62,12 @@ class Fluent::KafkaInput < Fluent::Input
|
|
|
62
62
|
include Fluent::KafkaPluginUtil::SSLSettings
|
|
63
63
|
include Fluent::KafkaPluginUtil::SaslSettings
|
|
64
64
|
|
|
65
|
+
BufferChunkOverflowError = if defined?(Fluent::Plugin::Buffer::BufferChunkOverflowError)
|
|
66
|
+
Fluent::Plugin::Buffer::BufferChunkOverflowError
|
|
67
|
+
else
|
|
68
|
+
Fluent::BufferChunkLimitError
|
|
69
|
+
end
|
|
70
|
+
|
|
65
71
|
unless method_defined?(:router)
|
|
66
72
|
define_method("router") { Fluent::Engine }
|
|
67
73
|
end
|
|
@@ -348,7 +354,11 @@ class Fluent::KafkaInput < Fluent::Input
|
|
|
348
354
|
offset = messages.last.offset + 1
|
|
349
355
|
|
|
350
356
|
event_streams.each { |tag, es|
|
|
351
|
-
|
|
357
|
+
begin
|
|
358
|
+
@router.emit_stream(tag, es)
|
|
359
|
+
rescue BufferChunkOverflowError => e
|
|
360
|
+
$log.warn "Skipped records larger than the buffer chunk limit size in #{@topic_entry.topic}/#{@topic_entry.partition}", :tag => tag, :error => e.to_s
|
|
361
|
+
end
|
|
352
362
|
}
|
|
353
363
|
|
|
354
364
|
if @offset_manager
|
|
@@ -85,6 +85,12 @@ class Fluent::KafkaGroupInput < Fluent::Input
|
|
|
85
85
|
Fluent::BufferQueueLimitError
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
+
BufferChunkOverflowError = if defined?(Fluent::Plugin::Buffer::BufferChunkOverflowError)
|
|
89
|
+
Fluent::Plugin::Buffer::BufferChunkOverflowError
|
|
90
|
+
else
|
|
91
|
+
Fluent::BufferChunkLimitError
|
|
92
|
+
end
|
|
93
|
+
|
|
88
94
|
unless method_defined?(:router)
|
|
89
95
|
define_method("router") { Fluent::Engine }
|
|
90
96
|
end
|
|
@@ -380,6 +386,8 @@ class Fluent::KafkaGroupInput < Fluent::Input
|
|
|
380
386
|
retries = 0
|
|
381
387
|
begin
|
|
382
388
|
router.emit_stream(tag, es)
|
|
389
|
+
rescue BufferChunkOverflowError => e
|
|
390
|
+
log.warn "Skipped records larger than the buffer chunk limit size", :tag => tag, :error => e.to_s
|
|
383
391
|
rescue BufferError
|
|
384
392
|
raise ForShutdown if @consumer.nil?
|
|
385
393
|
|
|
@@ -198,6 +198,10 @@ DESC
|
|
|
198
198
|
end
|
|
199
199
|
end
|
|
200
200
|
|
|
201
|
+
if @share_producer && @buffer_config.flush_thread_count > 1
|
|
202
|
+
log.warn "share_producer sends one chunk at a time even with flush_thread_count #{@buffer_config.flush_thread_count}. Set share_producer false to send chunks in parallel"
|
|
203
|
+
end
|
|
204
|
+
|
|
201
205
|
@producer_opts = {max_retries: @max_send_retries, required_acks: @required_acks, idempotent: @idempotent}
|
|
202
206
|
@producer_opts[:ack_timeout] = @ack_timeout if @ack_timeout
|
|
203
207
|
@producer_opts[:compression_codec] = @compression_codec.to_sym if @compression_codec
|
|
@@ -240,6 +244,7 @@ DESC
|
|
|
240
244
|
|
|
241
245
|
if @share_producer
|
|
242
246
|
@shared_producer = create_producer
|
|
247
|
+
@shared_producer_mutex = Mutex.new
|
|
243
248
|
else
|
|
244
249
|
@producers = {}
|
|
245
250
|
@producers_mutex = Mutex.new
|
|
@@ -274,8 +279,10 @@ DESC
|
|
|
274
279
|
|
|
275
280
|
def shutdown_producers
|
|
276
281
|
if @share_producer
|
|
277
|
-
@
|
|
278
|
-
|
|
282
|
+
@shared_producer_mutex.synchronize {
|
|
283
|
+
@shared_producer.shutdown if @shared_producer
|
|
284
|
+
@shared_producer = nil
|
|
285
|
+
}
|
|
279
286
|
else
|
|
280
287
|
@producers_mutex.synchronize {
|
|
281
288
|
shutdown_threads = @producers.map { |key, producer|
|
|
@@ -293,7 +300,7 @@ DESC
|
|
|
293
300
|
|
|
294
301
|
def get_producer
|
|
295
302
|
if @share_producer
|
|
296
|
-
@shared_producer
|
|
303
|
+
@shared_producer ||= create_producer
|
|
297
304
|
else
|
|
298
305
|
@producers_mutex.synchronize {
|
|
299
306
|
producer = @producers[Thread.current.object_id]
|
|
@@ -306,6 +313,17 @@ DESC
|
|
|
306
313
|
end
|
|
307
314
|
end
|
|
308
315
|
|
|
316
|
+
def discard_producer
|
|
317
|
+
if @share_producer
|
|
318
|
+
producer, @shared_producer = @shared_producer, nil
|
|
319
|
+
else
|
|
320
|
+
producer = @producers_mutex.synchronize { @producers.delete(Thread.current.object_id) }
|
|
321
|
+
end
|
|
322
|
+
producer.shutdown if producer
|
|
323
|
+
rescue => e
|
|
324
|
+
log.warn "failed to shut down the discarded producer:", :error => e.to_s, :error_class => e.class.to_s
|
|
325
|
+
end
|
|
326
|
+
|
|
309
327
|
def setup_formatter(conf)
|
|
310
328
|
type = conf['@type']
|
|
311
329
|
case type
|
|
@@ -327,10 +345,20 @@ DESC
|
|
|
327
345
|
end
|
|
328
346
|
end
|
|
329
347
|
|
|
330
|
-
# TODO: optimize write performance
|
|
331
348
|
def write(chunk)
|
|
332
349
|
@writing_threads_mutex.synchronize { @writing_threads.add(Thread.current) }
|
|
333
350
|
|
|
351
|
+
if @share_producer
|
|
352
|
+
@shared_producer_mutex.synchronize { write_chunk(chunk) }
|
|
353
|
+
else
|
|
354
|
+
write_chunk(chunk)
|
|
355
|
+
end
|
|
356
|
+
ensure
|
|
357
|
+
@writing_threads_mutex.synchronize { @writing_threads.delete(Thread.current) }
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# TODO: optimize write performance
|
|
361
|
+
def write_chunk(chunk)
|
|
334
362
|
tag = chunk.metadata.tag
|
|
335
363
|
topic = if @topic
|
|
336
364
|
extract_placeholders(@topic, chunk)
|
|
@@ -418,12 +446,11 @@ DESC
|
|
|
418
446
|
log.warn "Exception ignored in tag : #{tag}" if ignore
|
|
419
447
|
# For safety, refresh client and its producers
|
|
420
448
|
refresh_client(false)
|
|
449
|
+
discard_producer
|
|
421
450
|
# raise UnrecoverableError for backup ignored exception chunk
|
|
422
451
|
raise Fluent::UnrecoverableError if ignore && exception_backup
|
|
423
452
|
# Raise exception to retry sendind messages
|
|
424
453
|
raise e unless ignore
|
|
425
|
-
ensure
|
|
426
|
-
@writing_threads_mutex.synchronize { @writing_threads.delete(Thread.current) }
|
|
427
454
|
end
|
|
428
455
|
end
|
|
429
456
|
end
|
|
@@ -52,6 +52,18 @@ class KafkaInputTest < Test::Unit::TestCase
|
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
class OverflowRouter < FakeRouter
|
|
56
|
+
def initialize(overflow_tag)
|
|
57
|
+
super()
|
|
58
|
+
@overflow_tag = overflow_tag
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def emit_stream(tag, es)
|
|
62
|
+
raise Fluent::KafkaInput::BufferChunkOverflowError, "too large" if tag == @overflow_tag
|
|
63
|
+
super
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
55
67
|
def create_topic_watcher(messages, router, tag_source: :record, add_prefix: nil, add_suffix: nil)
|
|
56
68
|
kafka = Object.new
|
|
57
69
|
kafka.define_singleton_method(:fetch_messages) { |**args| messages }
|
|
@@ -159,6 +171,20 @@ class KafkaInputTest < Test::Unit::TestCase
|
|
|
159
171
|
assert_equal([[TOPIC_NAME, ['record 1', 'record 2']]],
|
|
160
172
|
router.emitted.map { |tag, records| [tag, records.map { |r| r['message'] }] })
|
|
161
173
|
end
|
|
174
|
+
|
|
175
|
+
def test_consume_skips_buffer_chunk_overflow
|
|
176
|
+
messages = [
|
|
177
|
+
tagged_message('app.large', 'record 1', 0),
|
|
178
|
+
tagged_message('app.small', 'record 2', 1),
|
|
179
|
+
]
|
|
180
|
+
router = OverflowRouter.new('app.large')
|
|
181
|
+
watcher = create_topic_watcher(messages, router)
|
|
182
|
+
watcher.consume
|
|
183
|
+
|
|
184
|
+
assert_equal([['app.small', ['record 2']]],
|
|
185
|
+
router.emitted.map { |tag, records| [tag, records.map { |r| r['message'] }] })
|
|
186
|
+
assert_equal(2, watcher.instance_variable_get(:@next_offset))
|
|
187
|
+
end
|
|
162
188
|
end
|
|
163
189
|
|
|
164
190
|
class ConsumeTest < self
|
|
@@ -36,6 +36,19 @@ class KafkaGroupInputTest < Test::Unit::TestCase
|
|
|
36
36
|
assert_true d.instance.multi_workers_ready?
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
def test_emit_events_with_buffer_chunk_overflow
|
|
40
|
+
d = create_driver
|
|
41
|
+
es = Fluent::MultiEventStream.new
|
|
42
|
+
router = Object.new
|
|
43
|
+
mock(router).emit_stream("test", es).once { raise Fluent::Plugin::Buffer::BufferChunkOverflowError, "too large" }
|
|
44
|
+
stub(d.instance).router { router }
|
|
45
|
+
|
|
46
|
+
assert_nothing_raised do
|
|
47
|
+
d.instance.emit_events("test", es)
|
|
48
|
+
end
|
|
49
|
+
assert_true d.logs.any? { |log| log.include?("Skipped records larger than the buffer chunk limit size") && log.include?("too large") }
|
|
50
|
+
end
|
|
51
|
+
|
|
39
52
|
class ConsumeTest < self
|
|
40
53
|
def setup
|
|
41
54
|
@kafka = Kafka.new(["localhost:9092"], client_id: 'kafka')
|
|
@@ -144,6 +144,79 @@ class Kafka2OutputTest < Test::Unit::TestCase
|
|
|
144
144
|
assert_equal [10], producer.produced.collect { |message| message[:partition] }
|
|
145
145
|
end
|
|
146
146
|
|
|
147
|
+
data("per thread producer" => false,
|
|
148
|
+
"shared producer" => true)
|
|
149
|
+
def test_write_failure_replaces_producer(share_producer)
|
|
150
|
+
d = create_driver(base_config + config_element('ROOT', '', {"default_topic" => "kitagawakeiko",
|
|
151
|
+
"brokers" => "localhost:1",
|
|
152
|
+
"share_producer" => share_producer}))
|
|
153
|
+
d.instance_start
|
|
154
|
+
producer = d.instance.get_producer
|
|
155
|
+
mock.proxy(producer).shutdown
|
|
156
|
+
|
|
157
|
+
assert_raise(Kafka::ConnectionError) {
|
|
158
|
+
d.instance.write(create_chunk([{"a" => "b"}]))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
assert_not_same producer, d.instance.get_producer
|
|
162
|
+
ensure
|
|
163
|
+
d.instance_shutdown if d
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
data("per thread producer" => false,
|
|
167
|
+
"shared producer" => true)
|
|
168
|
+
def test_write_failure_keeps_error_when_producer_shutdown_fails(share_producer)
|
|
169
|
+
d = create_driver(base_config + config_element('ROOT', '', {"default_topic" => "kitagawakeiko",
|
|
170
|
+
"brokers" => "localhost:1",
|
|
171
|
+
"share_producer" => share_producer}))
|
|
172
|
+
d.instance_start
|
|
173
|
+
producer = d.instance.get_producer
|
|
174
|
+
stub(producer).shutdown { raise IOError }
|
|
175
|
+
|
|
176
|
+
assert_raise(Kafka::ConnectionError) {
|
|
177
|
+
d.instance.write(create_chunk([{"a" => "b"}]))
|
|
178
|
+
}
|
|
179
|
+
ensure
|
|
180
|
+
d.instance_shutdown if d
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
class SlowProducer < DummyProducer
|
|
184
|
+
attr_reader :max_concurrent_deliveries
|
|
185
|
+
|
|
186
|
+
def initialize
|
|
187
|
+
@mutex = Mutex.new
|
|
188
|
+
@deliveries = 0
|
|
189
|
+
@max_concurrent_deliveries = 0
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def deliver_messages
|
|
193
|
+
@mutex.synchronize {
|
|
194
|
+
@deliveries += 1
|
|
195
|
+
@max_concurrent_deliveries = [@max_concurrent_deliveries, @deliveries].max
|
|
196
|
+
}
|
|
197
|
+
sleep 0.1
|
|
198
|
+
@mutex.synchronize { @deliveries -= 1 }
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def shutdown
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def test_shared_producer_delivers_one_chunk_at_a_time
|
|
206
|
+
d = create_driver(config + config_element('ROOT', '', {"share_producer" => true}))
|
|
207
|
+
producer = SlowProducer.new
|
|
208
|
+
stub(d.instance).create_producer { producer }
|
|
209
|
+
d.instance_start
|
|
210
|
+
|
|
211
|
+
2.times.map {
|
|
212
|
+
Thread.new { d.instance.write(create_chunk([{"a" => "b"}])) }
|
|
213
|
+
}.each(&:join)
|
|
214
|
+
|
|
215
|
+
assert_equal 1, producer.max_concurrent_deliveries
|
|
216
|
+
ensure
|
|
217
|
+
d.instance_shutdown if d
|
|
218
|
+
end
|
|
219
|
+
|
|
147
220
|
data("crc32" => "crc32",
|
|
148
221
|
"murmur2" => "murmur2")
|
|
149
222
|
def test_partitioner_hash_function(data)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fluent-plugin-kafka
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.19.
|
|
4
|
+
version: 0.19.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hidemasa Togashi
|
|
@@ -218,7 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
218
218
|
- !ruby/object:Gem::Version
|
|
219
219
|
version: '0'
|
|
220
220
|
requirements: []
|
|
221
|
-
rubygems_version: 4.0.
|
|
221
|
+
rubygems_version: 4.0.20
|
|
222
222
|
specification_version: 4
|
|
223
223
|
summary: Fluentd plugin for Apache Kafka > 0.8
|
|
224
224
|
test_files:
|