-
Notifications
You must be signed in to change notification settings - Fork 113
[server] Switch back to PubSubPosition based reads with offset as fallback #2242
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
base: main
Are you sure you want to change the base?
[server] Switch back to PubSubPosition based reads with offset as fallback #2242
Conversation
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.
Pull Request Overview
This PR removes the getPubSubPositionString method and its associated test, replacing its usage with direct calls to pubSubPositionDeserializer.toPosition(). The key changes enable better position comparison by using actual PubSubPosition objects rather than numeric offsets, and utilize the deserializePositionWithOffsetFallback method for safer deserialization with fallback logic.
- Removes
PubSubUtil.getPubSubPositionStringutility method and its test - Replaces numeric offset comparisons with proper position comparisons using
diffPositionand object equality - Updates
deserializePositionWithOffsetFallbackto remove theoffset > 0guard condition - Integrates proper position deserialization with fallback in
extractUpstreamPosition
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| PubSubUtil.java | Removes getPubSubPositionString utility method |
| PubSubUtilTest.java | Removes test coverage for getPubSubPositionString |
| OffsetRecord.java | Removes unused import and updates deserializePositionWithOffsetFallback condition |
| KafkaTopicDumper.java | Replaces getPubSubPositionString with direct toPosition calls |
| StoreIngestionTask.java | Updates position deserialization condition and integrates fallback in extractUpstreamPosition |
| StoreIngestionTaskTest.java | Changes test assertions to use full position equality instead of numeric offset comparison |
| PartitionTracker.java | Replaces getPubSubPositionString with direct toPosition call |
| LeaderFollowerStoreIngestionTask.java | Replaces numeric offset comparisons with proper position comparisons using diffPosition and symbolic position checks |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
clients/venice-admin-tool/src/main/java/com/linkedin/venice/KafkaTopicDumper.java
Outdated
Show resolved
Hide resolved
clients/venice-admin-tool/src/main/java/com/linkedin/venice/KafkaTopicDumper.java
Outdated
Show resolved
Hide resolved
clients/da-vinci-client/src/main/java/com/linkedin/davinci/validation/PartitionTracker.java
Outdated
Show resolved
Hide resolved
internal/venice-common/src/main/java/com/linkedin/venice/offsets/OffsetRecord.java
Outdated
Show resolved
Hide resolved
...ts/da-vinci-client/src/main/java/com/linkedin/davinci/kafka/consumer/StoreIngestionTask.java
Show resolved
Hide resolved
|
Hi there. This pull request has been inactive for 30 days. To keep our review queue healthy, we plan to close it in 7 days unless there is new activity. If you are still working on this, please push a commit, leave a comment, or convert it to draft to signal intent. Thank you for your time and contributions. |
6fa7342 to
e86b854
Compare
e86b854 to
cd66555
Compare
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.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
internal/venice-common/src/main/java/com/linkedin/venice/offsets/OffsetRecord.java
Outdated
Show resolved
Hide resolved
clients/venice-admin-tool/src/main/java/com/linkedin/venice/KafkaTopicDumper.java
Outdated
Show resolved
Hide resolved
…lback Remove additional safeguards
…bolic positions The deserializePositionWithOffsetFallback method was incorrectly treating symbolic positions (EARLIEST, LATEST) as regular positions and comparing their numeric offsets against the provided minimum offset. This caused EARLIEST (numeric offset -1) to be replaced with an offset-based position when the minimum offset was >= 0. Added a guard to detect and preserve symbolic positions before performing numeric offset comparison, ensuring they are returned as-is regardless of the minimum offset parameter. This fix resolves the failing testDeserializePositionWithOffsetFallback test in LeaderFollowerStoreIngestionTaskTest. Test: ./gradlew :clients:da-vinci-client:test --tests "com.linkedin.davinci.kafka.consumer.LeaderFollowerStoreIngestionTaskTest"
cd66555 to
a5271ff
Compare
a5271ff to
a80f0a5
Compare
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.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
internal/venice-common/src/main/java/com/linkedin/venice/pubsub/PubSubUtil.java
Show resolved
Hide resolved
internal/venice-common/src/main/java/com/linkedin/venice/pubsub/PubSubUtil.java
Show resolved
Hide resolved
| } | ||
|
|
||
| @Test | ||
| public void testGetPubSubPositionString() { |
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.
I think PubSubUtil.getPubSubPositionString() should also be removed at this point?
| * causes issues in production. | ||
| */ | ||
| public static final String SERVER_USE_UPSTREAM_PUBSUB_POSITION_WITH_FALLBACK = | ||
| "server.use.upstream.pubsub.position.with.fallback"; |
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.
| "server.use.upstream.pubsub.position.with.fallback"; | |
| "server.use.upstream.pubsub.positions"; |
I think it's okay to leave out the fallback part in the config and rely on it being mentioned in the docstring. How about a shorter name like this? If not, I feel like it should be possible to arrive at something shorter than what's in the PR.
Same with checkpointed.
| public static PubSubPosition deserializePositionWithOffsetFallback( | ||
| ByteBuffer wireFormatBytes, | ||
| long offset, | ||
| PubSubPositionDeserializer pubSubPositionDeserializer) { | ||
| // Fast path: nothing to deserialize | ||
| if (wireFormatBytes == null || !wireFormatBytes.hasRemaining()) { | ||
| return fromKafkaOffset(offset); | ||
| } | ||
|
|
||
| try { | ||
| final PubSubPosition position = pubSubPositionDeserializer.toPosition(wireFormatBytes); | ||
|
|
||
| // Guard against regressions: honor the caller-provided minimum offset. | ||
| // This applies to both symbolic and concrete positions. | ||
| if (position.getNumericOffset() < offset) { | ||
| LOGGER.info( | ||
| "Deserialized position: {} is behind the provided offset: {}. Using offset-based position.", | ||
| position.getNumericOffset(), | ||
| offset); | ||
| return fromKafkaOffset(offset); | ||
| } | ||
|
|
||
| // If position is ahead of or equal to offset, return it as-is (including symbolic positions like LATEST) | ||
| return position; | ||
| } catch (RuntimeException e) { | ||
| LOGGER.warn( | ||
| "Failed to deserialize PubSubPosition. Using offset-based position (offset={}, bufferRem={}, bufferCap={}).", | ||
| offset, | ||
| wireFormatBytes.remaining(), | ||
| wireFormatBytes.capacity(), | ||
| e); | ||
| return fromKafkaOffset(offset); | ||
| } | ||
| } |
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.
| public static PubSubPosition deserializePositionWithOffsetFallback( | |
| ByteBuffer wireFormatBytes, | |
| long offset, | |
| PubSubPositionDeserializer pubSubPositionDeserializer) { | |
| // Fast path: nothing to deserialize | |
| if (wireFormatBytes == null || !wireFormatBytes.hasRemaining()) { | |
| return fromKafkaOffset(offset); | |
| } | |
| try { | |
| final PubSubPosition position = pubSubPositionDeserializer.toPosition(wireFormatBytes); | |
| // Guard against regressions: honor the caller-provided minimum offset. | |
| // This applies to both symbolic and concrete positions. | |
| if (position.getNumericOffset() < offset) { | |
| LOGGER.info( | |
| "Deserialized position: {} is behind the provided offset: {}. Using offset-based position.", | |
| position.getNumericOffset(), | |
| offset); | |
| return fromKafkaOffset(offset); | |
| } | |
| // If position is ahead of or equal to offset, return it as-is (including symbolic positions like LATEST) | |
| return position; | |
| } catch (RuntimeException e) { | |
| LOGGER.warn( | |
| "Failed to deserialize PubSubPosition. Using offset-based position (offset={}, bufferRem={}, bufferCap={}).", | |
| offset, | |
| wireFormatBytes.remaining(), | |
| wireFormatBytes.capacity(), | |
| e); | |
| return fromKafkaOffset(offset); | |
| } | |
| } | |
| public static PubSubPosition deserializePositionWithOffsetFallback( | |
| ByteBuffer wireFormatBytes, | |
| long offset, | |
| PubSubPositionDeserializer pubSubPositionDeserializer) { | |
| if (wireFormatBytes != null && wireFormatBytes.hasRemaining()) { | |
| try { | |
| final PubSubPosition position = pubSubPositionDeserializer.toPosition(wireFormatBytes); | |
| if (position.getNumericOffset() >= offset) { | |
| return position; // Valid position: ahead of or equal to offset | |
| } | |
| LOGGER.info( | |
| "Deserialized position: {} is behind the provided offset: {}. Using offset-based position.", | |
| position.getNumericOffset(), | |
| offset); | |
| } catch (RuntimeException e) { | |
| LOGGER.warn( | |
| "Failed to deserialize PubSubPosition. Using offset-based position (offset={}, bufferRem={}, bufferCap={}).", | |
| offset, | |
| wireFormatBytes.remaining(), | |
| wireFormatBytes.capacity(), | |
| e); | |
| } | |
| } | |
| // Offset fallback for all invalid or missing cases | |
| return fromKafkaOffset(offset); | |
| } |
I see every branch having the same offset fallback return value. How about this layout, instead?
It's a bit less-explicit with the individual fallback cases, but I find it a bit more clear to have one single offset fallback case at the end.
| return pubSubContext; | ||
| } | ||
|
|
||
| PubSubPosition deserializePositionWithOffsetFallback( |
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.
Isn't this just basically the same as the PubSubUtil version? Can it be unified?
| if ((PubSubSymbolicPosition.EARLIEST.equals(latestConsumedRtPosition) | ||
| && !PubSubSymbolicPosition.EARLIEST.equals(upstreamStartPosition)) |
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.
I don't quite understand what the first condition means
| .stream() | ||
| .map(bb -> pubSubPositionDeserializer.toPosition(bb).getNumericOffset()) | ||
| .collect(Collectors.toList()); | ||
| List<Long> highWatermarkOffsets; |
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.
| List<Long> highWatermarkOffsets; | |
| List<ByteBuffer> positions = versionSwap.getLocalHighWatermarkPubSubPositions(); | |
| List<Long> offsets = versionSwap.getLocalHighWatermarks(); | |
| List<Long> highWatermarkOffsets = new ArrayList<>(); | |
| if (positions != null && !positions.isEmpty()) { | |
| for (int i = 0; i < positions.size(); i++) { | |
| long fallbackOffset = (offsets != null && i < offsets.size()) ? offsets.get(i) : -1L; | |
| PubSubPosition position = PubSubUtil | |
| .deserializePositionWithOffsetFallback(positions.get(i), fallbackOffset, pubSubPositionDeserializer); | |
| highWatermarkOffsets.add(position.getNumericOffset()); | |
| } | |
| } |
nit: how about this?
Switch back to PubSubPosition based reads with offset as fallback
Code changes
Concurrency-Specific Checks
Both reviewer and PR author to verify
synchronized,RWLock) are used where needed.ConcurrentHashMap,CopyOnWriteArrayList).How was this PR tested?
Does this PR introduce any user-facing or breaking changes?