Repository navigation
MINOR: Skip further reads from log for share fetches if max records reached #23892
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 1 commit
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 |
|---|---|---|
|
|
@@ -26,6 +26,8 @@ | |
| import org.apache.kafka.common.errors.UnknownTopicOrPartitionException; | ||
| import org.apache.kafka.common.message.ShareFetchResponseData; | ||
| import org.apache.kafka.common.protocol.Errors; | ||
| import org.apache.kafka.common.record.internal.RecordBatch; | ||
| import org.apache.kafka.common.record.internal.Records; | ||
| import org.apache.kafka.common.utils.Time; | ||
| import org.apache.kafka.raft.errors.NotLeaderException; | ||
| import org.apache.kafka.server.metrics.KafkaMetricsGroup; | ||
|
|
@@ -840,6 +842,14 @@ CompletableFuture<LinkedHashMap<TopicIdPartition, LogReadResult>> combineLogRead | |
| return CompletableFuture.completedFuture(existingFetchedData); | ||
| } | ||
|
|
||
| // The already fetched records are handed out first, so only the remainder of the record budget | ||
| // is available to the missing partitions. If nothing remains then nothing from them could be | ||
| // acquired, so skip the read altogether; the partitions are simply absent from the response and | ||
| // their locks are released along with the rest of the acquired partitions. | ||
| if (remainingRecordBudget(shareFetch.maxFetchRecords(), topicPartitionData, existingFetchedData) <= 0) { | ||
| return CompletableFuture.completedFuture(existingFetchedData); | ||
| } | ||
|
|
||
| return readFromLog( | ||
| missingLogReadTopicPartitions, | ||
| partitionMaxBytesStrategy.maxBytes(shareFetch.fetchParams().maxBytes, | ||
|
|
@@ -850,6 +860,61 @@ CompletableFuture<LinkedHashMap<TopicIdPartition, LogReadResult>> combineLogRead | |
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the record budget left for further reads once the already fetched data has been handed out, | ||
| * or 0 if the budget is exhausted. Records are counted by offset span, from the fetch offset to the last | ||
| * offset of the last batch, which is the same approximation {@link SharePartition#acquire} makes when it | ||
| * bounds the acquired records by max fetch records. The span starts at the fetch offset rather than at | ||
| * the base offset of the first batch because the fetched data starts at a batch boundary, and the records | ||
| * of that batch prior to the fetch offset are already in-flight or acknowledged, hence are not handed out | ||
| * again and must not consume the budget. | ||
| * <ul> | ||
| * <li>{@link RecordBatch#lastOffset()} is called at most once per partition, on the last batch, rather | ||
| * than on every batch. For file backed records the base offset comes from the bytes already read while | ||
| * iterating, whereas the last offset may require loading the batch header from the file channel.</li> | ||
| * <li>Iteration stops as soon as the base offsets alone prove the budget is spent. Base offsets are | ||
| * strictly increasing, so the offsets between the start of the span and a batch's base offset are | ||
| * already accounted for and the batch itself holds at least one more record.</li> | ||
| * </ul> | ||
| * | ||
| * @param maxFetchRecords The record budget of the share fetch request. | ||
| * @param fetchOffsets The fetch offsets of the acquired topic partitions. | ||
| * @param fetchedData The already fetched data, keyed by topic partition. | ||
| * @return The remaining record budget, or 0 if none remains. | ||
| */ | ||
| private static int remainingRecordBudget(int maxFetchRecords, | ||
| Map<TopicIdPartition, Long> fetchOffsets, | ||
| Map<TopicIdPartition, LogReadResult> fetchedData) { | ||
| long remaining = maxFetchRecords; | ||
| for (Map.Entry<TopicIdPartition, LogReadResult> entry : fetchedData.entrySet()) { | ||
| FetchDataInfo fetchDataInfo = entry.getValue().info(); | ||
| if (fetchDataInfo == null || fetchDataInfo.records == null) { | ||
| continue; | ||
| } | ||
|
|
||
| Records records = fetchDataInfo.records; | ||
| // The span starts at the fetch offset, or at the base offset of the first batch if that is | ||
| // higher or the fetch offset is unknown. | ||
| long startOffset = fetchOffsets.getOrDefault(entry.getKey(), -1L); | ||
|
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. what is the case when startOffset is not found in
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. This is just defensive code, ideally fetchOffset should not be empty and in case it is then it anyways gets reset while comparing against baseOffset.
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. okay, I was thinking maybe if such scenario arrives, we should put a WARNING log and return, but if you think that this is fine, we can go ahead with this way as well |
||
| RecordBatch lastBatch = null; | ||
| for (RecordBatch batch : records.batches()) { | ||
| if (lastBatch == null) { | ||
| startOffset = Math.max(startOffset, batch.baseOffset()); | ||
| } else if (batch.baseOffset() - startOffset >= remaining) { | ||
| return 0; | ||
| } | ||
| lastBatch = batch; | ||
| } | ||
| if (lastBatch != null) { | ||
| remaining -= Math.max(0, lastBatch.lastOffset() - startOffset + 1); | ||
| if (remaining <= 0) { | ||
| return 0; | ||
| } | ||
| } | ||
| } | ||
| return (int) remaining; | ||
| } | ||
|
|
||
| // Visible for testing. | ||
| void releasePartitionLocks(Set<TopicIdPartition> topicIdPartitions) { | ||
| topicIdPartitions.forEach(tp -> { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.