Skip to content

Commit ebaffd4

Browse files
committed
Use currentLag and position to set end offset estimate
1 parent 8e1389c commit ebaffd4

1 file changed

Lines changed: 34 additions & 80 deletions

File tree

sdks/java/io/kafka/src/main/java/org/apache/beam/sdk/io/kafka/ReadFromKafkaDoFn.java

Lines changed: 34 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@
1919

2020
import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkState;
2121

22-
import java.io.Closeable;
2322
import java.math.BigDecimal;
2423
import java.math.MathContext;
2524
import java.time.Duration;
2625
import java.util.Collections;
2726
import java.util.List;
2827
import java.util.Map;
2928
import java.util.Optional;
30-
import java.util.function.Supplier;
29+
import java.util.concurrent.atomic.AtomicLong;
3130
import org.apache.beam.sdk.coders.Coder;
3231
import org.apache.beam.sdk.io.kafka.KafkaIO.ReadSourceDescriptors;
3332
import org.apache.beam.sdk.io.kafka.KafkaIOUtils.MovingAvg;
@@ -49,7 +48,6 @@
4948
import org.apache.beam.sdk.transforms.splittabledofn.WatermarkEstimator;
5049
import org.apache.beam.sdk.transforms.splittabledofn.WatermarkEstimators.MonotonicallyIncreasing;
5150
import org.apache.beam.sdk.transforms.windowing.BoundedWindow;
52-
import org.apache.beam.sdk.util.ExpiringMemoizingSerializableSupplier;
5351
import org.apache.beam.sdk.util.MemoizingPerInstantiationSerializableSupplier;
5452
import org.apache.beam.sdk.util.Preconditions;
5553
import org.apache.beam.sdk.util.SerializableSupplier;
@@ -71,6 +69,8 @@
7169
import org.apache.kafka.clients.consumer.ConsumerConfig;
7270
import org.apache.kafka.clients.consumer.ConsumerRecord;
7371
import org.apache.kafka.clients.consumer.ConsumerRecords;
72+
import org.apache.kafka.clients.consumer.InvalidOffsetException;
73+
import org.apache.kafka.common.KafkaException;
7474
import org.apache.kafka.common.PartitionInfo;
7575
import org.apache.kafka.common.TopicPartition;
7676
import org.apache.kafka.common.config.ConfigDef;
@@ -235,30 +235,12 @@ public MovingAvg load(KafkaSourceDescriptor kafkaSourceDescriptor)
235235
CacheBuilder.newBuilder()
236236
.concurrencyLevel(Runtime.getRuntime().availableProcessors())
237237
.weakValues()
238-
.removalListener(
239-
(RemovalNotification<KafkaSourceDescriptor, KafkaLatestOffsetEstimator>
240-
notification) -> {
241-
final @Nullable KafkaLatestOffsetEstimator value;
242-
if (notification.getCause() == RemovalCause.COLLECTED
243-
&& (value = notification.getValue()) != null) {
244-
value.close();
245-
}
246-
})
247238
.build(
248-
new CacheLoader<KafkaSourceDescriptor, KafkaLatestOffsetEstimator>() {
239+
new CacheLoader<KafkaSourceDescriptor, AtomicLong>() {
249240
@Override
250-
public KafkaLatestOffsetEstimator load(
251-
final KafkaSourceDescriptor sourceDescriptor) {
252-
LOG.info(
253-
"Creating Kafka consumer for offset estimation for {}",
254-
sourceDescriptor);
255-
final Map<String, Object> config =
256-
KafkaIOUtils.overrideBootstrapServersConfig(
257-
consumerConfig, sourceDescriptor);
258-
final Consumer<byte[], byte[]> consumer =
259-
consumerFactoryFn.apply(config);
260-
return new KafkaLatestOffsetEstimator(
261-
consumer, sourceDescriptor.getTopicPartition());
241+
public AtomicLong load(final KafkaSourceDescriptor sourceDescriptor) {
242+
LOG.info("Creating end offset estimator for {}", sourceDescriptor);
243+
return new AtomicLong(Long.MIN_VALUE);
262244
}
263245
}));
264246
this.pollConsumerCacheSupplier =
@@ -319,8 +301,7 @@ public Consumer<byte[], byte[]> load(
319301
private final SerializableSupplier<LoadingCache<KafkaSourceDescriptor, MovingAvg>>
320302
avgRecordSizeCacheSupplier;
321303

322-
private final SerializableSupplier<
323-
LoadingCache<KafkaSourceDescriptor, KafkaLatestOffsetEstimator>>
304+
private final SerializableSupplier<LoadingCache<KafkaSourceDescriptor, AtomicLong>>
324305
latestOffsetEstimatorCacheSupplier;
325306

326307
private final SerializableSupplier<LoadingCache<KafkaSourceDescriptor, Consumer<byte[], byte[]>>>
@@ -329,8 +310,7 @@ public Consumer<byte[], byte[]> load(
329310
private transient @MonotonicNonNull LoadingCache<KafkaSourceDescriptor, MovingAvg>
330311
avgRecordSizeCache;
331312

332-
private transient @MonotonicNonNull LoadingCache<
333-
KafkaSourceDescriptor, KafkaLatestOffsetEstimator>
313+
private transient @MonotonicNonNull LoadingCache<KafkaSourceDescriptor, AtomicLong>
334314
latestOffsetEstimatorCache;
335315

336316
private transient @MonotonicNonNull LoadingCache<KafkaSourceDescriptor, Consumer<byte[], byte[]>>
@@ -349,46 +329,6 @@ public Consumer<byte[], byte[]> load(
349329
@VisibleForTesting
350330
static final String RAW_SIZE_METRIC_PREFIX = KafkaUnboundedReader.RAW_SIZE_METRIC_PREFIX;
351331

352-
/**
353-
* A {@link GrowableOffsetRangeTracker.RangeEndEstimator} which uses a Kafka {@link Consumer} to
354-
* fetch backlog.
355-
*/
356-
private static class KafkaLatestOffsetEstimator
357-
implements GrowableOffsetRangeTracker.RangeEndEstimator, Closeable {
358-
private final Consumer<byte[], byte[]> offsetConsumer;
359-
private final Supplier<Long> offsetSupplier;
360-
361-
KafkaLatestOffsetEstimator(
362-
final Consumer<byte[], byte[]> offsetConsumer, final TopicPartition topicPartition) {
363-
this.offsetConsumer = offsetConsumer;
364-
this.offsetSupplier =
365-
new ExpiringMemoizingSerializableSupplier<>(
366-
() -> {
367-
try {
368-
return offsetConsumer
369-
.endOffsets(Collections.singleton(topicPartition))
370-
.getOrDefault(topicPartition, Long.MIN_VALUE);
371-
} catch (Throwable t) {
372-
LOG.error("Failed to get end offset for {}", topicPartition, t);
373-
return Long.MIN_VALUE;
374-
}
375-
},
376-
Duration.ofSeconds(1),
377-
Long.MIN_VALUE,
378-
Duration.ZERO);
379-
}
380-
381-
@Override
382-
public long estimate() {
383-
return offsetSupplier.get();
384-
}
385-
386-
@Override
387-
public void close() {
388-
offsetConsumer.close();
389-
}
390-
}
391-
392332
@GetInitialRestriction
393333
@RequiresNonNull({"pollConsumerCache"})
394334
public OffsetRange initialRestriction(@Element KafkaSourceDescriptor kafkaSourceDescriptor) {
@@ -500,8 +440,8 @@ public double getSize(
500440
@RequiresNonNull({"latestOffsetEstimatorCache"})
501441
public UnsplittableRestrictionTracker<OffsetRange, Long> restrictionTracker(
502442
@Element KafkaSourceDescriptor kafkaSourceDescriptor, @Restriction OffsetRange restriction) {
503-
final LoadingCache<KafkaSourceDescriptor, KafkaLatestOffsetEstimator>
504-
latestOffsetEstimatorCache = this.latestOffsetEstimatorCache;
443+
final LoadingCache<KafkaSourceDescriptor, AtomicLong> latestOffsetEstimatorCache =
444+
this.latestOffsetEstimatorCache;
505445

506446
if (restriction.getTo() < Long.MAX_VALUE) {
507447
return new UnsplittableRestrictionTracker<>(new OffsetRangeTracker(restriction));
@@ -510,9 +450,10 @@ public UnsplittableRestrictionTracker<OffsetRange, Long> restrictionTracker(
510450
// OffsetEstimators are cached for each topic-partition because they hold a stateful connection,
511451
// so we want to minimize the amount of connections that we start and track with Kafka. Another
512452
// point is that it has a memoized backlog, and this should make that more reusable estimations.
453+
final AtomicLong latestOffsetEstimator =
454+
latestOffsetEstimatorCache.getUnchecked(kafkaSourceDescriptor);
513455
return new UnsplittableRestrictionTracker<>(
514-
new GrowableOffsetRangeTracker(
515-
restriction.getFrom(), latestOffsetEstimatorCache.getUnchecked(kafkaSourceDescriptor)));
456+
new GrowableOffsetRangeTracker(restriction.getFrom(), latestOffsetEstimator::get));
516457
}
517458

518459
@ProcessElement
@@ -525,14 +466,13 @@ public ProcessContinuation processElement(
525466
throws Exception {
526467
final LoadingCache<KafkaSourceDescriptor, MovingAvg> avgRecordSizeCache =
527468
this.avgRecordSizeCache;
528-
final LoadingCache<KafkaSourceDescriptor, KafkaLatestOffsetEstimator>
529-
latestOffsetEstimatorCache = this.latestOffsetEstimatorCache;
469+
final LoadingCache<KafkaSourceDescriptor, AtomicLong> latestOffsetEstimatorCache =
470+
this.latestOffsetEstimatorCache;
530471
final LoadingCache<KafkaSourceDescriptor, Consumer<byte[], byte[]>> pollConsumerCache =
531472
this.pollConsumerCache;
532473

533474
final MovingAvg avgRecordSize = avgRecordSizeCache.get(kafkaSourceDescriptor);
534-
final KafkaLatestOffsetEstimator latestOffsetEstimator =
535-
latestOffsetEstimatorCache.get(kafkaSourceDescriptor);
475+
final AtomicLong latestOffsetEstimator = latestOffsetEstimatorCache.get(kafkaSourceDescriptor);
536476
final Consumer<byte[], byte[]> consumer = pollConsumerCache.get(kafkaSourceDescriptor);
537477
final Deserializer<K> keyDeserializerInstance =
538478
Preconditions.checkStateNotNull(this.keyDeserializerInstance);
@@ -580,6 +520,20 @@ public ProcessContinuation processElement(
580520
// Fetch the next records.
581521
final ConsumerRecords<byte[], byte[]> rawRecords = consumer.poll(remainingTimeout);
582522
final Duration elapsed = pollTimer.elapsed();
523+
try {
524+
final long position = consumer.position(topicPartition);
525+
consumer
526+
.currentLag(topicPartition)
527+
.ifPresent(lag -> latestOffsetEstimator.lazySet(position + lag));
528+
} catch (InvalidOffsetException e) {
529+
// The position is undefined or out of range.
530+
latestOffsetEstimator.lazySet(Long.MIN_VALUE);
531+
} catch (KafkaException e) {
532+
// Wakeups, interrupts, authentication failures, authorization failures, timeouts and
533+
// unrecoverable errors can be ignored. This routine is reattempted on every iteration
534+
// and fallback methods would likely trigger the same unrecoverable errors.
535+
}
536+
583537
try {
584538
remainingTimeout = remainingTimeout.minus(elapsed);
585539
} catch (ArithmeticException e) {
@@ -687,7 +641,7 @@ public ProcessContinuation processElement(
687641

688642
final long estimatedBacklogBytes =
689643
(long)
690-
(BigDecimal.valueOf(latestOffsetEstimator.estimate())
644+
(BigDecimal.valueOf(latestOffsetEstimator.get())
691645
.subtract(BigDecimal.valueOf(expectedOffset), MathContext.DECIMAL128)
692646
.doubleValue()
693647
* avgRecordSize.get());
@@ -752,8 +706,8 @@ public void setup() throws Exception {
752706
public void teardown() throws Exception {
753707
final LoadingCache<KafkaSourceDescriptor, MovingAvg> avgRecordSizeCache =
754708
this.avgRecordSizeCache;
755-
final LoadingCache<KafkaSourceDescriptor, KafkaLatestOffsetEstimator>
756-
latestOffsetEstimatorCache = this.latestOffsetEstimatorCache;
709+
final LoadingCache<KafkaSourceDescriptor, AtomicLong> latestOffsetEstimatorCache =
710+
this.latestOffsetEstimatorCache;
757711
final LoadingCache<KafkaSourceDescriptor, Consumer<byte[], byte[]>> pollConsumerCache =
758712
this.pollConsumerCache;
759713

0 commit comments

Comments
 (0)