-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-2390; OffsetOutOfRangeException should contain the Offset and Partition info. #118
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 17 commits
c032abd
d8912a2
06ad6f8
4c3cca2
08ee9bd
1d8cb21
2f16181
3650334
83872ae
a3eb97b
bc75c78
12e84b5
fb75a11
4fb44f9
f8a92b3
94b2ca0
176ef24
1085885
2e85668
d71b191
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 |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ | |
| import org.apache.kafka.common.TopicPartition; | ||
| import org.apache.kafka.common.errors.DisconnectException; | ||
| import org.apache.kafka.common.errors.InvalidMetadataException; | ||
| import org.apache.kafka.common.errors.OffsetOutOfRangeException; | ||
| import org.apache.kafka.common.metrics.Metrics; | ||
| import org.apache.kafka.common.metrics.Sensor; | ||
| import org.apache.kafka.common.metrics.stats.Avg; | ||
|
|
@@ -80,6 +81,8 @@ public class Fetcher<K, V> { | |
| private final Deserializer<K> keyDeserializer; | ||
| private final Deserializer<V> valueDeserializer; | ||
|
|
||
| private Map<TopicPartition, Long> offsetOutOfRangePartitions; | ||
|
|
||
| public Fetcher(ConsumerNetworkClient client, | ||
| int minBytes, | ||
| int maxWaitMs, | ||
|
|
@@ -108,6 +111,7 @@ public Fetcher(ConsumerNetworkClient client, | |
| this.valueDeserializer = valueDeserializer; | ||
|
|
||
| this.records = new LinkedList<PartitionRecords<K, V>>(); | ||
| this.offsetOutOfRangePartitions = new HashMap<>(); | ||
|
|
||
| this.sensors = new FetchManagerMetrics(metrics, metricGrpPrefix, metricTags); | ||
| this.retryBackoffMs = retryBackoffMs; | ||
|
|
@@ -139,6 +143,7 @@ public void onFailure(RuntimeException e) { | |
| /** | ||
| * Update the fetch positions for the provided partitions. | ||
| * @param partitions | ||
| * @throws NoOffsetForPartitionException If no offset is stored for a given partition and no reset policy is available | ||
| */ | ||
| public void updateFetchPositions(Set<TopicPartition> partitions) { | ||
| // reset the fetch position to the committed position | ||
|
|
@@ -252,16 +257,43 @@ private long listOffset(TopicPartition partition, long timestamp) { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * If any partition from previous fetchResponse contains OffsetOutOfRange error, throw | ||
| * OffsetOutOfRangeException and clear the partition list. | ||
| * | ||
| * @throws OffsetOutOfRangeException If there is OffsetOutOfRange error in fetchResponse | ||
| */ | ||
| private void throwIfOffsetOutOfRange() throws OffsetOutOfRangeException { | ||
| Map<TopicPartition, Long> currentOutOfRangePartitions = new HashMap<>(); | ||
|
|
||
| // filter offsetOutOfRangePartitions to retain only the fetchable partitions | ||
| for (Map.Entry<TopicPartition, Long> entry: this.offsetOutOfRangePartitions.entrySet()) { | ||
| if (!subscriptions.isFetchable(entry.getKey())) { | ||
| log.debug("Ignoring fetched records for {} since it is no longer fetchable", entry.getKey()); | ||
| continue; | ||
| } | ||
| Long consumed = subscriptions.consumed(entry.getKey()); | ||
|
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. Could you add some comments here for the checking of the consumed position? |
||
| if (consumed != null && entry.getValue() == consumed) | ||
| currentOutOfRangePartitions.put(entry.getKey(), entry.getValue()); | ||
| } | ||
| this.offsetOutOfRangePartitions.clear(); | ||
| if (!currentOutOfRangePartitions.isEmpty()) | ||
| throw new OffsetOutOfRangeException(currentOutOfRangePartitions); | ||
| } | ||
|
|
||
| /** | ||
| * Return the fetched records, empty the record buffer and update the consumed position. | ||
| * | ||
| * @return The fetched records per partition | ||
| * @throws OffsetOutOfRangeException If there is OffsetOutOfRange error in fetchResponse | ||
| */ | ||
| public Map<TopicPartition, List<ConsumerRecord<K, V>>> fetchedRecords() { | ||
| if (this.subscriptions.partitionAssignmentNeeded()) { | ||
| return Collections.emptyMap(); | ||
|
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 clear the exception here? If not, then we'll have the exception thrown after the rebalance.
Member
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. I think we should not clear exception, because it should be handled in the same way we handle Fetcher.records -- we will still return already fetched records, instead of clearing it, after the rebalance. What do you think?
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. Hmm... In that case, we should check that the out of range partitions are still assigned when the exception is thrown, right?
Member
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. Ah, good point! I missed it. Actually this is a good reason why we should store map first and create exception later. Let me update the patch. |
||
| } else { | ||
| Map<TopicPartition, List<ConsumerRecord<K, V>>> drained = new HashMap<>(); | ||
| throwIfOffsetOutOfRange(); | ||
|
|
||
| for (PartitionRecords<K, V> part : this.records) { | ||
| if (!subscriptions.isFetchable(part.partition)) { | ||
| log.debug("Ignoring fetched records for {} since it is no longer fetchable", part.partition); | ||
|
|
@@ -373,8 +405,11 @@ private Map<Node, FetchRequest> createFetchRequests(Cluster cluster) { | |
| fetchable.put(node, fetch); | ||
| } | ||
|
|
||
| long offset = this.subscriptions.fetched(partition); | ||
| fetch.put(partition, new FetchRequest.PartitionData(offset, this.fetchSize)); | ||
| long fetched = this.subscriptions.fetched(partition); | ||
| long consumed = this.subscriptions.consumed(partition); | ||
| // Only fetch data for partitions whose previously fetched data has been consumed | ||
| if (consumed == fetched) | ||
| fetch.put(partition, new FetchRequest.PartitionData(fetched, this.fetchSize)); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -442,9 +477,12 @@ private void handleFetchResponse(ClientResponse resp, FetchRequest request) { | |
| || partition.errorCode == Errors.UNKNOWN_TOPIC_OR_PARTITION.code()) { | ||
| this.metadata.requestUpdate(); | ||
| } else if (partition.errorCode == Errors.OFFSET_OUT_OF_RANGE.code()) { | ||
| // TODO: this could be optimized by grouping all out-of-range partitions | ||
| long fetchOffset = request.fetchData().get(tp).offset; | ||
| if (subscriptions.hasDefaultOffsetResetPolicy()) | ||
| subscriptions.needOffsetReset(tp); | ||
| else | ||
| this.offsetOutOfRangePartitions.put(tp, fetchOffset); | ||
| log.info("Fetch offset {} is out of range, resetting offset", subscriptions.fetched(tp)); | ||
| subscriptions.needOffsetReset(tp); | ||
| } else if (partition.errorCode == Errors.UNKNOWN.code()) { | ||
| log.warn("Unknown error fetching data for topic-partition {}", tp); | ||
| } else { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,17 +12,25 @@ | |
| */ | ||
| package org.apache.kafka.common.errors; | ||
|
|
||
| import org.apache.kafka.common.TopicPartition; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * This offset is either larger or smaller than the range of offsets the server has for the given partition. | ||
| * | ||
| */ | ||
| public class OffsetOutOfRangeException extends RetriableException { | ||
|
|
||
| private static final long serialVersionUID = 1L; | ||
| Map<TopicPartition, Long> offsetOutOfRangePartitions = null; | ||
|
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. Should be private and final probably. |
||
|
|
||
| public OffsetOutOfRangeException() { | ||
| } | ||
|
|
||
| public OffsetOutOfRangeException(Map<TopicPartition, Long> offsetOutOfRangePartitions) { | ||
| this.offsetOutOfRangePartitions = offsetOutOfRangePartitions; | ||
| } | ||
|
|
||
| public OffsetOutOfRangeException(String message) { | ||
| super(message); | ||
| } | ||
|
|
@@ -35,4 +43,8 @@ public OffsetOutOfRangeException(String message, Throwable cause) { | |
| super(message, cause); | ||
| } | ||
|
|
||
| public Map<TopicPartition, Long> getOutOfRangePartitions() { | ||
|
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. In Kafka, we tend to write the method name without the
Member
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. @ijuma Thanks much for your review. Will pay attention to final, private and get method name in the future patch. Please see updated patch. I didn't add final to variable offsetOutOfRangePartitions in class OffsetOutOfRangeException because otherwise I need to add 4 additional lines offsetOutOfRangePartitions=null to 4 constructor method. This seems redundant. Can you let me know if you prefer me adding final keyword and the additional 4 lines?
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. Thanks @lindong28. Regarding the final field in
Member
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. Thanks @ijuma for your review! |
||
| return offsetOutOfRangePartitions; | ||
| } | ||
|
|
||
| } | ||
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.
Should be final?