-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bump rdkafka gem version and support cooperative-sticky
Features: - Support for cooperative-sticky assignment strategy - Consumer class partition assign and revoke callbacks Tests: - Refactor to share a common consumer - Consistent and performant communication mechanism with test consumers (using pipes)
- Loading branch information
Showing
11 changed files
with
257 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,36 @@ | ||
module Racecar | ||
class RebalanceListener | ||
def initialize(config) | ||
@config = config | ||
@consumer_class = config.consumer_class | ||
def initialize(consumer_class, instrumenter) | ||
@consumer_class = consumer_class | ||
@instrumenter = instrumenter | ||
@rdkafka_consumer = nil | ||
end | ||
|
||
attr_reader :config, :consumer_class | ||
attr_writer :rdkafka_consumer | ||
|
||
def on_partitions_assigned(_consumer, topic_partition_list) | ||
consumer_class.respond_to?(:on_partitions_assigned) && | ||
consumer_class.on_partitions_assigned(topic_partition_list.to_h) | ||
rescue | ||
attr_reader :consumer_class, :instrumenter, :rdkafka_consumer | ||
private :consumer_class, :instrumenter, :rdkafka_consumer | ||
|
||
def on_partitions_assigned(rdkafka_topic_partition_list) | ||
partitions_by_topic = rdkafka_topic_partition_list.to_h | ||
|
||
instrument("partitions_assigned", partitions: partitions_by_topic ) do | ||
consumer_class.on_partitions_assigned(partitions_by_topic, rdkafka_consumer) | ||
end | ||
end | ||
|
||
def on_partitions_revoked(rdkafka_topic_partition_list) | ||
partitions_by_topic = rdkafka_topic_partition_list.to_h | ||
|
||
instrument("partitions_revoked", partitions: partitions_by_topic ) do | ||
consumer_class.on_partitions_revoked(partitions_by_topic, rdkafka_consumer) | ||
end | ||
end | ||
|
||
def on_partitions_revoked(_consumer, topic_partition_list) | ||
consumer_class.respond_to?(:on_partitions_revoked) && | ||
consumer_class.on_partitions_revoked(topic_partition_list.to_h) | ||
rescue | ||
private | ||
|
||
def instrument(event, payload, &block) | ||
instrumenter.instrument(event, payload, &block) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
# frozen_string_literal: true | ||
|
||
require "racecar/cli" | ||
|
||
RSpec.describe "cooperative-sticky assignment", type: :integration do | ||
before do | ||
create_topic(topic: input_topic, partitions: topic_partitions) | ||
create_topic(topic: output_topic, partitions: topic_partitions) | ||
|
||
set_config | ||
|
||
consumer_class.group_id = group_id | ||
consumer_class.output_topic = output_topic | ||
consumer_class.pipe_to_test = consumer_message_pipe | ||
consumer_class.subscribes_to(input_topic) | ||
end | ||
|
||
let(:input_topic) { generate_input_topic_name } | ||
let(:output_topic) { generate_output_topic_name } | ||
let(:group_id) { generate_group_id } | ||
let(:topic_partitions) { 2 } | ||
let(:consumer_class) { CoopStickyConsumer ||= echo_consumer_class } | ||
let(:input_messages) do | ||
message_count.times.map { |n| | ||
{ payload: "message-#{n}", partition: n % topic_partitions } | ||
} | ||
end | ||
let(:message_count) { 20 } | ||
|
||
context "during a rebalance" do | ||
let!(:consumers) { [] } | ||
let(:consumer_index_by_id) { {} } | ||
|
||
after { terminate_all_consumers } | ||
|
||
it "allows healthy consumers to keep processing their paritions" do | ||
start_consumer | ||
start_consumer | ||
|
||
wait_for_assignments(2) | ||
publish_messages | ||
wait_for_a_few_messages | ||
|
||
terminate_consumer1 | ||
|
||
wait_for_all_messages | ||
|
||
aggregate_failures do | ||
expect_consumer0_did_not_have_partitions_revoked_but_consumer1_did | ||
expect_consumer0_took_over_processing_from_consumer1 | ||
end | ||
end | ||
|
||
def expect_consumer0_took_over_processing_from_consumer1 | ||
consumer0_partitions = messages_by_consumer[0].map(&:partition).uniq | ||
consumer1_partitions = messages_by_consumer[1].map(&:partition).uniq | ||
|
||
raise "consumer1 got assigned more than 1 parition" if consumer1_partitions.count > 1 | ||
consumer1_partition = consumer1_partitions.fetch(0) | ||
|
||
expect(consumer0_partitions).to include(consumer1_partition) | ||
end | ||
|
||
def expect_consumer0_did_not_have_partitions_revoked_but_consumer1_did | ||
revocations_by_consumer_thread_id = revocation_events.group_by { |e| e.fetch("consumer_id") } | ||
|
||
revocations_by_consumer_index = revocations_by_consumer_thread_id | ||
.transform_keys { |consumer_id| consumer_index_by_id.fetch(consumer_id) } | ||
|
||
expect(revocations_by_consumer_index.keys).to eq([1]) | ||
end | ||
|
||
def messages_by_consumer | ||
incoming_messages.group_by { |m| m.headers["processed_by"] } | ||
.transform_keys { |consumer_id| consumer_index_by_id.fetch(consumer_id) } | ||
end | ||
|
||
def start_consumer | ||
runner = Racecar.runner(consumer_class.new) | ||
|
||
thread = Thread.new do | ||
Thread.current.name = "Racecar runner #{consumers.size}" | ||
runner.run | ||
end | ||
|
||
consumers << runner | ||
consumer_index_by_id["#{Process.pid}-#{thread.object_id}"] = consumers.index(runner) | ||
end | ||
|
||
def terminate_consumer1 | ||
consumers[1].stop | ||
end | ||
|
||
def terminate_all_consumers | ||
consumers.each(&:stop) | ||
end | ||
|
||
def wait_for_a_few_messages | ||
wait_for_messages(expected_message_count: 5) | ||
end | ||
|
||
def wait_for_all_messages | ||
wait_for_messages(expected_message_count: message_count) | ||
end | ||
end | ||
|
||
def set_config | ||
Racecar.config.fetch_messages = 1 | ||
Racecar.config.max_wait_time = 0.1 | ||
Racecar.config.session_timeout = 6 # minimum allowed by default broker config | ||
Racecar.config.heartbeat_interval = 1.5 | ||
Racecar.config.partition_assignment_strategy = "cooperative-sticky" | ||
Racecar.config.load_consumer_class(consumer_class) | ||
end | ||
|
||
after do |test| | ||
Object.send(:remove_const, :CoopStickyConsumer) if defined?(CoopStickyConsumer) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.