-
Notifications
You must be signed in to change notification settings - Fork 15.1k
KAFKA-7109: Close cached fetch sessions in the broker on consumer close #5407
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 all commits
f8df762
bdbd646
ca375de
86baa71
ee577ba
dfa222f
94417ac
dc33ae0
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 |
|---|---|---|
|
|
@@ -54,6 +54,7 @@ | |
| import org.apache.kafka.common.record.RecordBatch; | ||
| import org.apache.kafka.common.record.Records; | ||
| import org.apache.kafka.common.record.TimestampType; | ||
| import org.apache.kafka.common.requests.FetchMetadata; | ||
| import org.apache.kafka.common.requests.FetchRequest; | ||
| import org.apache.kafka.common.requests.FetchResponse; | ||
| import org.apache.kafka.common.requests.IsolationLevel; | ||
|
|
@@ -1516,11 +1517,33 @@ private static String partitionLeadMetricName(TopicPartition tp) { | |
|
|
||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| private void closeFetchSessions(Timer timer) { | ||
| Cluster cluster = metadata.fetch(); | ||
|
|
||
| for (Map.Entry<Integer, FetchSessionHandler> entry : sessionHandlers.entrySet()) { | ||
| Integer nodeId = entry.getKey(); | ||
| int sessionId = entry.getValue().sessionId(); | ||
| FetchMetadata closeSessionMetadata = new FetchMetadata(sessionId, FetchMetadata.FINAL_EPOCH); | ||
|
|
||
| final FetchRequest.Builder closeSessionRequest = FetchRequest.Builder | ||
| .forConsumer(this.maxWaitMs, this.minBytes, Collections.emptyMap()) | ||
| .metadata(closeSessionMetadata); | ||
| client.send(cluster.nodeById(nodeId), closeSessionRequest); | ||
|
Member
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. Don't we have to do something with the futures returned by
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. We had discussed offline that there is no need to wait and ensure the requests completed. It makes sense to make it best effort, otherwise it can slow down client termination and I assume people prefer the client terminates quicker rather than make sure no unused sessions are in the broker
Member
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. We should always document things like that so that people reading the code are aware of it. My point was more subtle though, how do we make sure we actually sent the request?
Member
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. More specifically, there's no point in having this if we close the network client before we even get to the point where the request has reached the network layer.
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. Oh, I did not know that was a possibility. I'll dive in.
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. It probably makes sense to wait a short time, but not very long, to send the request to the broker. I'm not sure how that interacts with the rest of the client close logic
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. Yeah, good catch @ijuma. This is the code for closing the ClientUtils.closeQuietly(fetcher, "fetcher", firstException);
ClientUtils.closeQuietly(interceptors, "consumer interceptors", firstException);
ClientUtils.closeQuietly(metrics, "consumer metrics", firstException);
ClientUtils.closeQuietly(client, "consumer network client", firstException);
ClientUtils.closeQuietly(keyDeserializer, "consumer key deserializer", firstException);
ClientUtils.closeQuietly(valueDeserializer, "consumer value deserializer", firstException);Meaning there's a pretty big chance the client is closed before the request are sent. But then I read the Javadoc of Meaning this code doesn't transmit any requests since I see two approaches to this
In both cases, we will slow down the closing of the consumer for a bit, as we will have to block as far as I can tell. As a sidenote - this code would also need to handle exceptions as it currently doesn't. My idea is to only catch & log the error.
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.
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. The consumer close code needs to block until the fetch sessions are closed. |
||
| } | ||
| client.poll(timer); | ||
| } | ||
|
|
||
| public void close(Timer timer) { | ||
| if (nextInLineRecords != null) | ||
| nextInLineRecords.drain(); | ||
| decompressionBufferSupplier.close(); | ||
| closeFetchSessions(timer); | ||
| sessionHandlers.clear(); | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| close(time.timer(Long.MAX_VALUE)); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,6 +64,7 @@ | |
| import org.apache.kafka.common.record.Records; | ||
| import org.apache.kafka.common.record.SimpleRecord; | ||
| import org.apache.kafka.common.record.TimestampType; | ||
| import org.apache.kafka.common.requests.FetchMetadata; | ||
| import org.apache.kafka.common.requests.AbstractRequest; | ||
| import org.apache.kafka.common.requests.ApiVersionsResponse; | ||
| import org.apache.kafka.common.requests.FetchRequest; | ||
|
|
@@ -309,6 +310,26 @@ public void testClearBufferedDataForTopicPartitions() { | |
| assertFalse(fetcher.hasCompletedFetches()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testFetcherCloseClosesFetchSessionsInBroker() throws Exception { | ||
| subscriptions.assignFromUser(singleton(tp0)); | ||
| subscriptions.seek(tp0, 0); | ||
| FetchResponse<MemoryRecords> fetchResponse = fullFetchResponse(tp0, this.records, Errors.NONE, 100L, 0); | ||
| client.prepareResponse(fetchResponse); | ||
| fetcher.sendFetches(); | ||
| consumerClient.poll(time.timer(0)); | ||
| assertEquals(0, consumerClient.pendingRequestCount(node)); | ||
| this.fetcher.close(); // should send request to close the session | ||
|
|
||
| assertEquals(1, consumerClient.pendingRequestCount(node)); | ||
| assertEquals(1, client.inFlightRequestCount()); | ||
| assertEquals(1, consumerClient.pendingRequestCount(node)); | ||
| FetchRequest sessionCloseReq = (FetchRequest) client.requests().peek().requestBuilder().build(); | ||
| assertEquals(fetchResponse.sessionId(), sessionCloseReq.metadata().sessionId()); | ||
| assertTrue(sessionCloseReq.fetchData().isEmpty()); | ||
| assertEquals(FetchMetadata.FINAL_EPOCH, sessionCloseReq.metadata().epoch()); // final epoch indicates we want to close the session | ||
|
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. Should we also check that there are no partitions contained in the request?
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. Also we should match the fetch session with the earlier one
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. Absolutely. Done |
||
| } | ||
|
|
||
| @Test | ||
| public void testFetchSkipsBlackedOutNodes() { | ||
| subscriptions.assignFromUser(singleton(tp0)); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.