KAFKA-7044: Fix Fetcher.fetchOffsetsByTimes and NPE in describe consumer group#5627
KAFKA-7044: Fix Fetcher.fetchOffsetsByTimes and NPE in describe consumer group#5627hachikuji merged 6 commits intoapache:trunkfrom
Conversation
There was a problem hiding this comment.
There seems to be some redundance between partitionsToRetry and remainingToSearch. It might be nicer if we could get rid of remainingToSearch so that we only had to rely on ListOffsetResult to know if we should retry. I think the only thing we need is to avoid losing partitions in the call to groupListOffsetRequests.
There was a problem hiding this comment.
Ah yes, we could add partitions we lose in the groupListOffsetRequest to partitionsToRetry, since sendListOffsetsRequests is called only from that one method... Let me try that.
8af4aff to
aec7385
Compare
hachikuji
left a comment
There was a problem hiding this comment.
Thanks for the updates. Left a couple more comments.
| return result; | ||
|
|
||
| remainingToSearch.keySet().removeAll(result.fetchedOffsets.keySet()); | ||
| remainingToSearch = timestampsToSearch.entrySet().stream() |
There was a problem hiding this comment.
I think this can be replaced with remainingToSearch.keySet().retainAll(value.partitionsToRetry).
| final RequestFuture<ListOffsetResult> listOffsetRequestsFuture = new RequestFuture<>(); | ||
| final Map<TopicPartition, OffsetData> fetchedTimestampOffsets = new HashMap<>(); | ||
| final Set<TopicPartition> partitionsToRetry = new HashSet<>(); | ||
| final Set<TopicPartition> partitionsRequireMetadataUpdate = new HashSet<>(timestampsToSearch.keySet()); |
There was a problem hiding this comment.
Another idea might be to pass partitionsToRetry into groupListOffsetRequests.
| log.debug("Leader {} for partition {} is unavailable for fetching offset until reconnect backoff expires", | ||
| info.leader(), tp); | ||
| info.leader(), tp); | ||
| partitionsToRetry.add(tp); |
There was a problem hiding this comment.
I moved the logic for getting the list of partitions that do not have available leader to groupListOffsetRequests . I was contemplating whether we should add partition for which we have a failed connection to leader to partitionsToRetry. Seems like we should, because we may be able to reconnect before the list offsets timeout.
| logEndOffsetResult._2 match { | ||
| case LogOffsetResult.LogOffset(logEndOffset) => getDescribePartitionResult(logEndOffsetResult._1, Some(logEndOffset)) | ||
| case LogOffsetResult.Unknown => getDescribePartitionResult(logEndOffsetResult._1, None) | ||
| case LogOffsetResult.Ignore => null |
There was a problem hiding this comment.
As far as I can tell, this was the only use of LogOffsetResult.Ignore. Maybe we can get rid of it?
If we did that, then we could probably also get rid of LogOffsetResult and replace it with a simple Option[Long], but we can leave that for a separate PR.
hachikuji
left a comment
There was a problem hiding this comment.
LGTM. We can do the cleanup of LogOffsetResult separately.
…mer group (#5627) A call to `kafka-consumer-groups --describe --group ...` can result in NullPointerException for two reasons: 1) `Fetcher.fetchOffsetsByTimes()` may return too early, without sending list offsets request for topic partitions that are not in cached metadata. 2) `ConsumerGroupCommand.getLogEndOffsets()` and `getLogStartOffsets()` assumed that endOffsets()/beginningOffsets() which eventually call Fetcher.fetchOffsetsByTimes(), would return a map with all the topic partitions passed to endOffsets()/beginningOffsets() and that values are not null. Because of (1), null values were possible if some of the topic partitions were already known (in metadata cache) and some not (metadata cache did not have entries for some of the topic partitions). However, even with fixing (1), endOffsets()/beginningOffsets() may return a map with some topic partitions missing, when list offset request returns a non-retriable error. This happens in corner cases such as message format on broker is before 0.10, or maybe in cases of some other errors. Testing: -- added unit test to verify fix in Fetcher.fetchOffsetsByTimes() -- did some manual testing with `kafka-consumer-groups --describe`, causing NPE. Was not able to reproduce any NPE cases with DescribeConsumerGroupTest.scala, Reviewers: Jason Gustafson <jason@confluent.io>
…mer group (#5627) A call to `kafka-consumer-groups --describe --group ...` can result in NullPointerException for two reasons: 1) `Fetcher.fetchOffsetsByTimes()` may return too early, without sending list offsets request for topic partitions that are not in cached metadata. 2) `ConsumerGroupCommand.getLogEndOffsets()` and `getLogStartOffsets()` assumed that endOffsets()/beginningOffsets() which eventually call Fetcher.fetchOffsetsByTimes(), would return a map with all the topic partitions passed to endOffsets()/beginningOffsets() and that values are not null. Because of (1), null values were possible if some of the topic partitions were already known (in metadata cache) and some not (metadata cache did not have entries for some of the topic partitions). However, even with fixing (1), endOffsets()/beginningOffsets() may return a map with some topic partitions missing, when list offset request returns a non-retriable error. This happens in corner cases such as message format on broker is before 0.10, or maybe in cases of some other errors. Testing: -- added unit test to verify fix in Fetcher.fetchOffsetsByTimes() -- did some manual testing with `kafka-consumer-groups --describe`, causing NPE. Was not able to reproduce any NPE cases with DescribeConsumerGroupTest.scala, Reviewers: Jason Gustafson <jason@confluent.io>
…mer group (apache#5627) A call to `kafka-consumer-groups --describe --group ...` can result in NullPointerException for two reasons: 1) `Fetcher.fetchOffsetsByTimes()` may return too early, without sending list offsets request for topic partitions that are not in cached metadata. 2) `ConsumerGroupCommand.getLogEndOffsets()` and `getLogStartOffsets()` assumed that endOffsets()/beginningOffsets() which eventually call Fetcher.fetchOffsetsByTimes(), would return a map with all the topic partitions passed to endOffsets()/beginningOffsets() and that values are not null. Because of (1), null values were possible if some of the topic partitions were already known (in metadata cache) and some not (metadata cache did not have entries for some of the topic partitions). However, even with fixing (1), endOffsets()/beginningOffsets() may return a map with some topic partitions missing, when list offset request returns a non-retriable error. This happens in corner cases such as message format on broker is before 0.10, or maybe in cases of some other errors. Testing: -- added unit test to verify fix in Fetcher.fetchOffsetsByTimes() -- did some manual testing with `kafka-consumer-groups --describe`, causing NPE. Was not able to reproduce any NPE cases with DescribeConsumerGroupTest.scala, Reviewers: Jason Gustafson <jason@confluent.io>
kafka-consumer-groups --describe --group ...can result in NullPointerException for two reasons:ConsumerGroupCommand.getLogEndOffsets()andgetLogStartOffsets()assumed that endOffsets()/beginningOffsets() which eventually call Fetcher.fetchOffsetsByTimes(), would return a map with all the topic partitions passed to endOffsets()/beginningOffsets() and that values are not null. Because of (1), null values were possible if some of the topic partitions were already known (in metadata cache) and some not (metadata cache did not have entries for some of the topic partitions). However, even with fixing (1), endOffsets()/beginningOffsets() may return a map with some topic partitions missing, when list offset request returns a non-retriable error. This happens in corner cases such as message format on broker is before 0.10, or maybe in cases of some other errors.Testing:
-- added unit test to verify fix in Fetcher.fetchOffsetsByTimes()
-- did some manual testing with
kafka-consumer-groups --describe, causing NPE. Was not able to reproduce any NPE cases with DescribeConsumerGroupTest.scala,Committer Checklist (excluded from commit message)