-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-12268: Implement task idling semantics via currentLag API #10137
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 2 commits
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 |
|---|---|---|
|
|
@@ -27,7 +27,6 @@ | |
| import org.apache.kafka.clients.consumer.internals.ConsumerInterceptors; | ||
| import org.apache.kafka.clients.consumer.internals.ConsumerMetadata; | ||
| import org.apache.kafka.clients.consumer.internals.ConsumerNetworkClient; | ||
| import org.apache.kafka.clients.consumer.internals.FetchedRecords; | ||
| import org.apache.kafka.clients.consumer.internals.Fetcher; | ||
| import org.apache.kafka.clients.consumer.internals.FetcherMetricsRegistry; | ||
| import org.apache.kafka.clients.consumer.internals.KafkaConsumerMetrics; | ||
|
|
@@ -74,6 +73,7 @@ | |
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.Optional; | ||
| import java.util.OptionalLong; | ||
| import java.util.Properties; | ||
| import java.util.Set; | ||
| import java.util.concurrent.TimeUnit; | ||
|
|
@@ -578,6 +578,7 @@ public class KafkaConsumer<K, V> implements Consumer<K, V> { | |
| private final Deserializer<V> valueDeserializer; | ||
| private final Fetcher<K, V> fetcher; | ||
| private final ConsumerInterceptors<K, V> interceptors; | ||
| private final IsolationLevel isolationLevel; | ||
|
|
||
| private final Time time; | ||
| private final ConsumerNetworkClient client; | ||
|
|
@@ -736,7 +737,7 @@ public KafkaConsumer(Map<String, Object> configs, | |
|
|
||
| FetcherMetricsRegistry metricsRegistry = new FetcherMetricsRegistry(Collections.singleton(CLIENT_ID_METRIC_TAG), metricGrpPrefix); | ||
| ChannelBuilder channelBuilder = ClientUtils.createChannelBuilder(config, time, logContext); | ||
| IsolationLevel isolationLevel = IsolationLevel.valueOf( | ||
| this.isolationLevel = IsolationLevel.valueOf( | ||
| config.getString(ConsumerConfig.ISOLATION_LEVEL_CONFIG).toUpperCase(Locale.ROOT)); | ||
| Sensor throttleTimeSensor = Fetcher.throttleTimeSensor(metrics, metricsRegistry); | ||
| int heartbeatIntervalMs = config.getInt(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG); | ||
|
|
@@ -849,6 +850,7 @@ public KafkaConsumer(Map<String, Object> configs, | |
| this.keyDeserializer = keyDeserializer; | ||
| this.valueDeserializer = valueDeserializer; | ||
| this.fetcher = fetcher; | ||
| this.isolationLevel = IsolationLevel.READ_UNCOMMITTED; | ||
| this.interceptors = Objects.requireNonNull(interceptors); | ||
| this.time = time; | ||
| this.client = client; | ||
|
|
@@ -1235,7 +1237,7 @@ private ConsumerRecords<K, V> poll(final Timer timer, final boolean includeMetad | |
| } | ||
| } | ||
|
|
||
| final FetchedRecords<K, V> records = pollForFetches(timer); | ||
| final Map<TopicPartition, List<ConsumerRecord<K, V>>> records = pollForFetches(timer); | ||
| if (!records.isEmpty()) { | ||
| // before returning the fetched records, we can send off the next round of fetches | ||
| // and avoid block waiting for their responses to enable pipelining while the user | ||
|
|
@@ -1269,12 +1271,12 @@ boolean updateAssignmentMetadataIfNeeded(final Timer timer, final boolean waitFo | |
| /** | ||
| * @throws KafkaException if the rebalance callback throws exception | ||
| */ | ||
| private FetchedRecords<K, V> pollForFetches(Timer timer) { | ||
| private Map<TopicPartition, List<ConsumerRecord<K, V>>> pollForFetches(Timer timer) { | ||
| long pollTimeout = coordinator == null ? timer.remainingMs() : | ||
| Math.min(coordinator.timeToNextPoll(timer.currentTimeMs()), timer.remainingMs()); | ||
|
|
||
| // if data is available already, return it immediately | ||
| final FetchedRecords<K, V> records = fetcher.fetchedRecords(); | ||
| final Map<TopicPartition, List<ConsumerRecord<K, V>>> records = fetcher.fetchedRecords(); | ||
| if (!records.isEmpty()) { | ||
| return records; | ||
| } | ||
|
|
@@ -2219,6 +2221,25 @@ public Map<TopicPartition, Long> endOffsets(Collection<TopicPartition> partition | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Get the consumer's current lag on the partition. Returns an "empty" {@link OptionalLong} if the lag is not known, | ||
| * for example if there is no position yet, or if the end offset is not known yet. | ||
| * | ||
| * <p> | ||
| * This method uses locally cached metadata and never makes a remote call. | ||
| * | ||
| * @param topicPartition The partition to get the lag for. | ||
| * | ||
| * @return This {@code Consumer} instance's current lag for the given partition. | ||
| * | ||
| * @throws IllegalStateException if the {@code topicPartition} is not assigned | ||
| **/ | ||
| @Override | ||
| public OptionalLong currentLag(TopicPartition topicPartition) { | ||
| final Long lag = subscriptions.partitionLag(topicPartition, isolationLevel); | ||
|
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. Other methods call
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. Thanks, I overlooked that, and it's a good idea. |
||
| return lag == null ? OptionalLong.empty() : OptionalLong.of(lag); | ||
| } | ||
|
|
||
| /** | ||
| * Return the current group metadata associated with this consumer. | ||
| * | ||
|
|
||
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.
Pardon me, KIP-695 does not include this change. It seems KIP-695 is still based on
metadata? Please correct me If I misunderstand anything :)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.
Woah, you are fast, @chia7712 !
I just sent a message to the vote thread. I wanted to submit this PR first so that the vote thread message can have the full context available.
Do you mind reading over what I said there? If it sounds good to you, then I'll update the KIP, and we can maybe put this whole mess to bed.
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.
Quick question, should the API take a
Collection<TopicPartition>like other APIs?Uh oh!
There was an error while loading. Please reload this page.
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.
Thanks @ijuma, I considered it, but decided on the current API because:
Map<TP, Long>, with mappings missing for unknown lags (which creates unfortunate null semantics for users), or aMap<TP, OptionalLong>which creates a complex-to-understand two hop lookup (lag:=result.get(tp).get()). Or else, we could return a more complex domain object object like @chia7712 proposed in the mailing list. All these complications seem like unnecessary complexity in the case of this particular API, given the first point.WDYT?
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.
If there is no batching benefit, then the simpler API makes sense. @hachikuji Any reason why batching could be useful here?
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.
For API calls that may incur a broker round trip, have batching of partitions makes sense. For this API I think single partition lookup is good enough.
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.
If we concern that users may call this function too frequent looping a large number of partitions, and each call is synchronizing on the subscription state, then maybe we can make it in a batching mode.
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.
That's a good point, @guozhangwang. Entering the synchronized block will have some overhead each time it's called.
I think we can just reason about the use cases here. My guess is that people would either tend to spot-check specific lags, as we are doing here, or they would tend to periodically check all lags. In the former case, I'd hazard that the current API is fine. In the latter case, we'd face more overhead. I'm sure this is motivated reasoning, but perhaps we can lump the latter case in with @chia7712 's suggestion to expose more metadata and defer it to the future.