-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-9184: Redundant task creation and periodic rebalances after zombie Connect worker rejoins the group #7771
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
de1c093
ce4f95e
9b37c17
1005a61
9560d0b
01caf29
6fdae3a
6e6eb0a
4e9a616
f7ded4f
6135111
a031894
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |
| package org.apache.kafka.connect.integration; | ||
|
|
||
| import org.apache.kafka.connect.runtime.AbstractStatus; | ||
| import org.apache.kafka.connect.runtime.distributed.DistributedConfig; | ||
| import org.apache.kafka.connect.runtime.rest.entities.ConnectorStateInfo; | ||
| import org.apache.kafka.connect.storage.StringConverter; | ||
| import org.apache.kafka.connect.util.clusters.EmbeddedConnectCluster; | ||
|
|
@@ -65,29 +66,27 @@ public class ConnectWorkerIntegrationTest { | |
| private static final int NUM_WORKERS = 3; | ||
| private static final String CONNECTOR_NAME = "simple-source"; | ||
|
|
||
| private EmbeddedConnectCluster.Builder connectBuilder; | ||
| private EmbeddedConnectCluster connect; | ||
| Map<String, String> workerProps = new HashMap<>(); | ||
| Properties brokerProps = new Properties(); | ||
|
|
||
| @Before | ||
| public void setup() throws IOException { | ||
| // setup Connect worker properties | ||
| Map<String, String> workerProps = new HashMap<>(); | ||
| workerProps.put(OFFSET_COMMIT_INTERVAL_MS_CONFIG, String.valueOf(OFFSET_COMMIT_INTERVAL_MS)); | ||
| workerProps.put(CONNECTOR_CLIENT_POLICY_CLASS_CONFIG, "All"); | ||
|
|
||
| // setup Kafka broker properties | ||
| Properties brokerProps = new Properties(); | ||
| brokerProps.put("auto.create.topics.enable", String.valueOf(false)); | ||
|
|
||
| // build a Connect cluster backed by Kafka and Zk | ||
| connect = new EmbeddedConnectCluster.Builder() | ||
| connectBuilder = new EmbeddedConnectCluster.Builder() | ||
| .name("connect-cluster") | ||
| .numWorkers(NUM_WORKERS) | ||
| .workerProps(workerProps) | ||
| .brokerProps(brokerProps) | ||
| .build(); | ||
|
|
||
| // start the clusters | ||
| connect.start(); | ||
| .maskExitProcedures(true); // true is the default, setting here as example | ||
| } | ||
|
|
||
| @After | ||
|
|
@@ -102,6 +101,10 @@ public void close() { | |
| */ | ||
| @Test | ||
| public void testAddAndRemoveWorker() throws Exception { | ||
| connect = connectBuilder.build(); | ||
| // start the clusters | ||
| connect.start(); | ||
|
|
||
| int numTasks = 4; | ||
| // create test topic | ||
| connect.kafka().createTopic("test-topic", NUM_TOPIC_PARTITIONS); | ||
|
|
@@ -149,6 +152,10 @@ public void testAddAndRemoveWorker() throws Exception { | |
| */ | ||
| @Test | ||
| public void testRestartFailedTask() throws Exception { | ||
| connect = connectBuilder.build(); | ||
| // start the clusters | ||
| connect.start(); | ||
|
|
||
| int numTasks = 1; | ||
|
|
||
| // Properties for the source connector. The task should fail at startup due to the bad broker address. | ||
|
|
@@ -180,6 +187,56 @@ public void testRestartFailedTask() throws Exception { | |
| CONNECTOR_SETUP_DURATION_MS, "Connector tasks are not all in running state."); | ||
| } | ||
|
|
||
| /** | ||
| * Verify that a set of tasks restarts correctly after a broker goes offline and back online | ||
| */ | ||
| @Test | ||
| public void testBrokerCoordinator() throws Exception { | ||
| workerProps.put(DistributedConfig.SCHEDULED_REBALANCE_MAX_DELAY_MS_CONFIG, String.valueOf(5000)); | ||
| connect = connectBuilder.workerProps(workerProps).build(); | ||
| // start the clusters | ||
| connect.start(); | ||
| int numTasks = 4; | ||
| // create test topic | ||
| connect.kafka().createTopic("test-topic", NUM_TOPIC_PARTITIONS); | ||
|
|
||
| // setup up props for the sink connector | ||
| Map<String, String> props = new HashMap<>(); | ||
| props.put(CONNECTOR_CLASS_CONFIG, MonitorableSourceConnector.class.getSimpleName()); | ||
| props.put(TASKS_MAX_CONFIG, String.valueOf(numTasks)); | ||
| props.put("topic", "test-topic"); | ||
| props.put("throughput", String.valueOf(1)); | ||
| props.put("messages.per.poll", String.valueOf(10)); | ||
| props.put(KEY_CONVERTER_CLASS_CONFIG, StringConverter.class.getName()); | ||
| props.put(VALUE_CONVERTER_CLASS_CONFIG, StringConverter.class.getName()); | ||
|
|
||
| waitForCondition(() -> assertWorkersUp(NUM_WORKERS).orElse(false), | ||
| WORKER_SETUP_DURATION_MS, "Initial group of workers did not start in time."); | ||
|
|
||
| // start a source connector | ||
| connect.configureConnector(CONNECTOR_NAME, props); | ||
|
|
||
| waitForCondition(() -> assertConnectorAndTasksRunning(CONNECTOR_NAME, numTasks).orElse(false), | ||
| CONNECTOR_SETUP_DURATION_MS, "Connector tasks did not start in time."); | ||
|
|
||
| connect.kafka().stopOnlyKafka(); | ||
|
|
||
| waitForCondition(() -> assertWorkersUp(NUM_WORKERS).orElse(false), | ||
| WORKER_SETUP_DURATION_MS, "Group of workers did not remain the same after broker shutdown"); | ||
|
|
||
| connect.kafka().startOnlyKafkaOnSamePorts(); | ||
|
|
||
| // Allow for the workers to discover that the coordinator is unavailable | ||
| Thread.sleep(TimeUnit.SECONDS.toMillis(10)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the workers discover that the coordinator is unavailable while it is down?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That was a bit misplaced, because I actually need 3 explicit delays (due to current lack of appropriate handles from the kafka and connect embedded clusters).
Added another commit. |
||
|
|
||
| waitForCondition(() -> assertWorkersUp(NUM_WORKERS).orElse(false), | ||
| WORKER_SETUP_DURATION_MS, "Group of workers did not remain the same within the " | ||
| + "designated time."); | ||
|
|
||
| waitForCondition(() -> assertConnectorAndTasksRunning(CONNECTOR_NAME, numTasks).orElse(false), | ||
| CONNECTOR_SETUP_DURATION_MS, "Connector tasks did not start in time."); | ||
| } | ||
|
|
||
| /** | ||
| * Confirm that the requested number of workers is up and running. | ||
| * | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the significance of this check?
If a worker has one failed task and one running task, will the snapshot be reset?
Will the running task be stopped?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a standard check we have when we check the
assignmentSnapshot. I prefer to err on the safe side and use the second condition too.The snapshot refers to your complete assignment (sets of connectors, tasks, revoked connectors and revoked tasks). The failure here corresponds to whether the assignment was successful or not as a whole. Currently the options are
NO_ERRORandCONFIG_MISMATCH, in which case as the comments explain in the code the worker needs to read to the end of the config log and rejoin. A failed assignment does not have assigned tasks (the sets are empty). Even if there's an issue with assignment overwrites elsewhere, what we solve here remains unaffected.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for explaining the meaning of a failed snapshot, I wasn't clear on that before. You explanation sounds reasonable.