dalelane commented on code in PR #333:
URL: 
https://github.com/apache/flink-connector-kafka/pull/333#discussion_r4514144740


##########
flink-connector-kafka/src/main/java/org/apache/flink/connector/kafka/source/reader/fetcher/KafkaSourceFetcherManager.java:
##########
@@ -98,6 +98,31 @@ public void commitOffsets(
         }
     }
 
+    /**
+     * Commits the given offsets synchronously when the fetcher closes its 
consumer, after {@link
+     * #close(long)} shut the fetcher down. Must be called before {@link 
#close(long)}.
+     */
+    public void commitOffsetsOnClose(
+            Map<TopicPartition, OffsetAndMetadata> offsetsToCommit,
+            Set<TopicPartition> partitionsWithoutOffset) {
+        // A fetcher shut down by close() or by an error stays listed until it 
exits, so try each
+        // of them.
+        for (SplitFetcher<ConsumerRecord<byte[], byte[]>, KafkaPartitionSplit> 
fetcher :
+                fetchers.values()) {
+            if (((KafkaPartitionSplitReader) fetcher.getSplitReader())
+                    .commitOffsetsOnClose(offsetsToCommit, 
partitionsWithoutOffset)) {
+                return;
+            }
+        }
+        // No fetcher runs, or all are already closing their consumers. Only a 
fetcher thread may
+        // use a consumer, so start one that commits as it closes.
+        final SplitFetcher<ConsumerRecord<byte[], byte[]>, 
KafkaPartitionSplit> splitFetcher =
+                createSplitFetcher();

Review Comment:
   In my earlier comment I said the close commit runs in the same 
KafkaPartitionSplitReader that fetched the records, so reconciliation has the 
state it needs. That's true when a fetcher is still running, but I missed this 
path.
   
   When no fetcher is left, for example after a fetcher fails and the job fails 
over, this starts a new one. Its split reader has no memory of earlier commits, 
so it can't stop the committed offset from moving backwards:
   
   1. A checkpoint holds offset 1. Reconciliation moves it past the commit 
marker, and 2 is committed.
   2. The next checkpoint also holds 1, and completes. Its commit is queued.
   3. The fetcher fails before running that commit.
   4. On close, a new fetcher commits 1. The committed offset goes back from 2 
to 1.
   
   The gap isn't new: commitOffsets already starts fresh fetchers like this, 
and my PR only stopped the offset moving backwards while a partition stays 
assigned to a reader. But this commit adds another way in.
   
   Could a fetcher started only to commit check the group's committed offsets 
first (consumer.committed), and skip anything lower? 
   
   Checking first would also mean that in cases where there's nothing it can 
commit (partitions without an offset) it'd avoid unnecessarily creating a 
consumer 



##########
flink-connector-kafka/src/main/java/org/apache/flink/connector/kafka/source/reader/KafkaPartitionSplitReader.java:
##########
@@ -64,6 +66,10 @@
 public class KafkaPartitionSplitReader
         implements SplitReader<ConsumerRecord<byte[], byte[]>, 
KafkaPartitionSplit> {
     private static final Logger LOG = 
LoggerFactory.getLogger(KafkaPartitionSplitReader.class);
+    // The commit returns once Kafka acknowledges it, and only an unreachable 
group coordinator
+    // makes it delay closing the consumer by this long. The reader waits for 
its fetcher to close
+    // for at most source.reader.close.timeout, 30 seconds by default.
+    private static final Duration COMMIT_ON_CLOSE_TIMEOUT = 
Duration.ofSeconds(10);

Review Comment:
   I agree that this would [usually be within 
milliseconds](https://github.com/apache/flink-connector-kafka/pull/333/changes#r4486512435),
 given that it returns as soon as the broker acks. But what happens when there 
is no ack from the group coordinator - if the group coordinator can't be 
reached or has stopped responding while the job is stopping or falling over?  
(With the classic protocol, that is when the latest async commit is still in 
flight)
   
   In that case, closing the fetcher does this:
    
   (10 seconds after commit request) `commitSync` gives up - the request is 
still in flight, `consumer.close()` starts waiting for it
   
   (30 seconds after commit request) commit request times out 
(request.timeout.ms). `close()` stops waiting and returns
    
   This means the fetcher can take about 30 seconds to close. The reader only 
waits 30 seconds (source.reader.close.timeout) then logs and continues. The 
fetcher thread keeps running in the background with the consumer. 
   
   The comment on COMMIT_ON_CLOSE_TIMEOUT suggest the aim was for the commit to 
fit in the time budget, but this doesn't count `consumer.close()`
   
   This matters beyond stop-with-savepoint, because the commit runs on every 
close (failover, cancel, and DynamicKafkaSourceReader restarting its 
sub-readers). Failovers caused by broker trouble could hit this. 
DynamicKafkaSourceReader closes its sub-readers one at a time on the task 
thread, so each cluster that has stopped responding adds its own wait.
   
   Could the commit and consumer.close() share what's left of the close budget, 
so together they stay inside source.reader.close.timeout? 
   
   (And it'd help to make it clear in the PR description that this applies to 
every close, not only stop-with-savepoint)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to