[controller] Remove topic-based concurrent push detection and clarify push-blocking messaging#2896
Conversation
(ConcurrentPushDetectionStrategy) Removes the ConcurrentPushDetectionStrategy flag and makes parent-version-status tracking the only path for detecting an in-flight push. Production parent controllers were already running PARENT_VERSION_STATUS_ONLY, so this is behavior-preserving for LinkedIn; it changes the OSS default (was DUAL). - VeniceParentHelixAdmin.getTopicForCurrentPushJob now always delegates to the parent-version-status path. Deletes getTopicForCurrentPushJobTopicBasedTracking and the orphaned helpers getKafkaTopicsByAge and truncateTopicsBasedOnMaxErroredTopicNumToKeep. - Removes the now-dead isTopicWriteNeeded() gates: the parent no longer creates or truncates the parent version topic (addVersion, shouldSkipTruncatingTopic, rollForwardToFutureVersion, the push-completion path, killOfflinePush, and DeferredVersionSwapService). - Deletes the ConcurrentPushDetectionStrategy enum, the concurrent.push.detection.strategy config key, and the cluster-config field/parse/getter. - Updates controller tests accordingly. DeferredVersionSwapService (controller.deferred.version.swap.service.enabled) is a separate, prod-enabled feature and is intentionally kept. Testing Done: - :services:venice-controller:test for TestVeniceParentHelixAdmin (88), TestVeniceHelixAdmin (43), TestDeferredVersionSwapService (19), and TestDeferredVersionSwapServiceWithSequentialRollout (8) -- all pass. - integrationTest TestDeferredVersionSwap and TestTargetedRegionPushWithNativeReplication -- all pass (incl. target-region deferred swap). - spotbugsMain (venice-controller + venice-common) and spotlessApply clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rent push When a new push is blocked, the rejection message branched only on version.isVersionSwapDeferred(), so a deferred-swap-enabled version whose push was still in progress was wrongly reported as "make that version current" (implying it only needed a roll-forward). That ambiguity misled a prior investigation that was actually a concurrent push, not a deferred-swap wait. - ConcurrentBatchPushException now distinguishes the two cases by the version's actual status: a deferred version that is ONLINE but not yet current in all regions is reported as waiting on deferred version swap (with per-region current versions and the target swap region); everything else is reported as an in-progress concurrent push (including the version status). - validateChildCurrentVersions now logs the full per-region picture and names the deferred (colo-by-colo) version swap, instead of logging only the first mismatching region. Testing Done: - :services:venice-controller:test TestVeniceParentHelixAdmin (89, incl. the new testDeferredVersionSwapWaitMessageIsDistinctFromConcurrentPush) -- all pass. - Updated the concurrent-push assertions in TestParentControllerWithMultiDataCenter to the new wording (this integration test was not re-run locally). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR removes the legacy/topic-based concurrent push detection path from the parent controller and standardizes on parent-version-status-based tracking, while also improving the push-blocking messaging to clearly distinguish “in-progress concurrent push” vs “waiting on deferred version swap”.
Changes:
- Remove
ConcurrentPushDetectionStrategyand theconcurrent.push.detection.strategyconfig, along with topic-based current-push detection and related topic truncation gates on the parent controller. - Update parent/controller logic and tests to reflect that parent controllers no longer create/truncate version topics (and simplify
shouldSkipTruncatingTopicaccordingly). - Clarify
ConcurrentBatchPushExceptionmessaging and logging to explicitly call out deferred version swap waits (with per-region current versions and target swap region).
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| services/venice-controller/src/main/java/com/linkedin/venice/controller/VeniceParentHelixAdmin.java | Removes topic-based detection path; updates push-blocking message; removes parent VT truncation gates. |
| services/venice-controller/src/main/java/com/linkedin/venice/controller/VeniceHelixAdmin.java | Stops parent controllers from creating version topics; simplifies truncation-skip logic for parents. |
| services/venice-controller/src/main/java/com/linkedin/venice/controller/VeniceControllerClusterConfig.java | Removes parsing/storage of the concurrent push detection strategy config. |
| services/venice-controller/src/main/java/com/linkedin/venice/controller/DeferredVersionSwapService.java | Removes parent VT truncation behavior from deferred swap promotion. |
| services/venice-controller/src/main/java/com/linkedin/venice/controller/VeniceController.java | Updates TODO/commentary now that parent no longer writes version topics. |
| internal/venice-common/src/main/java/com/linkedin/venice/meta/ConcurrentPushDetectionStrategy.java | Deletes the enum (strategy fully removed). |
| internal/venice-common/src/main/java/com/linkedin/venice/ConfigKeys.java | Removes the concurrent.push.detection.strategy key. |
| services/venice-controller/src/test/java/com/linkedin/venice/controller/TestVeniceParentHelixAdmin.java | Updates tests for new behavior and adds coverage for distinct deferred-swap vs concurrent-push messaging. |
| services/venice-controller/src/test/java/com/linkedin/venice/controller/TestVeniceHelixAdmin.java | Simplifies truncation-skip tests now that parent always skips. |
| services/venice-controller/src/test/java/com/linkedin/venice/controller/AbstractTestVeniceParentHelixAdmin.java | Removes now-dead strategy stubbing. |
| services/venice-controller/src/test/java/com/linkedin/venice/controller/TestDeferredVersionSwapService.java | Removes now-dead strategy stubbing. |
| services/venice-controller/src/test/java/com/linkedin/venice/controller/TestDeferredVersionSwapServiceWithSequentialRollout.java | Removes now-dead strategy stubbing and truncation assertions. |
| internal/venice-test-common/src/integrationTest/java/com/linkedin/venice/controller/TestParentControllerWithMultiDataCenter.java | Updates assertions for the clarified push-blocking message text. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…b topic lookup CI fix: getTopicForCurrentPushJob blocked a new push whenever the latest parent version was STARTED, without polling child job status. The parent Version status only advances out of STARTED when a job-status poll observes a terminal child status (handleTerminalJobStatus); there is no synchronous push-completion callback. So a completed empty/batch push left the parent version at STARTED and blocked every subsequent push until the ~10-minute background checker ran. The removed topic-based path used to poll and resolve this; with version-status now the only path, the regression surfaced in integration tests (empty_push / request_topic / update_store rejected with "ongoing push ... status STARTED"). Fix: let STARTED fall through to the existing child-status polling branch (which also drives the STARTED -> ONLINE transition). CREATED and PUSHED still block outright -- PUSHED is a target-region deferred-swap push awaiting roll-forward, where the children already report terminal status and polling would wrongly unblock the next push. Cleanups (no behavior change): - Inline getTopicForCurrentPushJobParentVersionStatusBasedTracking into getTopicForCurrentPushJob; the separate name implied a sibling strategy that no longer exists. - Remove dead existingVersionTopicsForStore() and the unused TOPIC_DELETION_DELAY_MS constant (last callers were the removed topic-based tracking). - Correct the stale maxErroredTopicNumToKeep Javadoc that described parent version-topic truncation the parent no longer performs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s review testDeferredVersionSwap regression: the prior STARTED-polling change let a second push through while the first push was deferring its version swap. Polling a deferred STARTED version advances it to a terminal status via getOffLineJobStatus and returns empty, unblocking the next push. Decide deferred swaps up front instead: a version that defers its swap blocks the next push until it is rolled forward and current in every region, regardless of push status. testGetClusterStaleStores regression: getTopicForCurrentPushJob re-polled a non-deferred ONLINE version. When a region has that version deleted/rolled back (a stale-store condition), the aggregate child status is non-terminal, so the version was misreported as an in-flight push and the store was dropped from the stale audit. A non-deferred ONLINE version has no ongoing push, so return empty without polling. Only STARTED now polls to disambiguate. Address Copilot review comment: the post-terminal block no longer truncates the parent version topic, so only append a status detail when the stream-reprocessing topic is actually truncated, and name it accordingly. Remove testDeferredVersionSwapWaitMessageIsDistinctFromConcurrentPush. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| return latestTopic; | ||
| } | ||
|
|
||
| if (lastVersion.getStatus() == CREATED || lastVersion.getStatus() == PUSHED) { |
There was a problem hiding this comment.
Why did we remove the STARTED status check?
There was a problem hiding this comment.
🤖 Good catch — STARTED was removed from this block intentionally, but the reason wasn't spelled out clearly. A STARTED version may already be terminal in child regions but not yet reflected on the parent (the parent only transitions out of STARTED once a job-status poll observes a terminal child status). Polling PUSHED children, on the other hand, would always return terminal and incorrectly unblock the next push. So STARTED is handled by the polling branch further below, while CREATED and PUSHED block unconditionally here. Added a comment to the code to make this explicit.
| : ". An ongoing push with pushJobId " + existingPushJobId + " and topic " + currentPushTopic.get() | ||
| + " is found and it must be terminated before another push can be started."; | ||
| String msg; | ||
| if (version.isVersionSwapDeferred() && version.getStatus() == ONLINE) { |
There was a problem hiding this comment.
Should we check for PUSHED instead? ONLINE means ingestion and version swap completed so the log below will never log
There was a problem hiding this comment.
🤖 Yes, good catch. Changed to PUSHED. PUSHED is the parent-side status when a deferred-swap version has completed its push but its version swap is still pending — i.e., when a new push should be blocked. ONLINE on the parent for a deferred-swap version means the swap has already completed (and validateChildCurrentVersions would have unblocked the push upstream), so the ONLINE branch in incrementVersionIdempotent would never actually be reached. Fixed and re-added the corresponding test (testDeferredVersionSwapWaitMessageIsDistinctFromConcurrentPush) with VersionStatus.PUSHED.
- Document that isIncrementalPush/isRepush params in getTopicForCurrentPushJob are unused by the parent implementation (kept for call-site compatibility) - Add pushJobId to CREATED/PUSHED log in getTopicForCurrentPushJob - Fix deferred-swap push-blocking check: PUSHED (not ONLINE) is the correct status when a push is done but its deferred version swap is still pending - Update shouldSkipTruncatingTopic @param javadoc: clusterName is unused, kept for override compatibility - Re-add testDeferredVersionSwapWaitMessageIsDistinctFromConcurrentPush with VersionStatus.PUSHED (was removed in prior commit when ONLINE was used) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…test setup Parent controllers no longer write version topics, so the parent-fabric topic cleanup subclass is dead code. Remove TopicCleanupServiceForParentController and always use the base TopicCleanupService; drop its dedicated tests along with TestTopicCleanupServiceForMultiKafkaClusters, whose entire premise (iterating multiple parent Kafka fabrics) no longer exists. Also: - Simplify shouldSkipTruncatingTopic(clusterName) call site to isParent() directly and remove the now-redundant wrapper method and its tests. - Simplify testDeferredVersionSwapWaitMessageIsDistinctFromConcurrentPush's incrementVersionIdempotent stubbing/invocation to use the 5-arg overload. - Ignore the impeccable hook cache file (.impeccable/) which is regenerated on every run. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Was accidentally committed in 46953d9; it is a regenerated pre-commit hook cache file and should not be version controlled. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Correct maxErroredTopicNumToKeep Javadoc: it also gates truncation/ status-detail behavior in truncateTopicsOptionally, not just cleanup in killOfflinePush and a diagnostic detail in getOffLineJobStatus. - getTopicForCurrentPushJob: separate the NON_EXISTING_VERSION check from the lastVersion == null check and log an accurate message for the latter (largestUsedVersionNumber can point at a version that doesn't exist, e.g. after deleteVersion or during data recovery); both still return empty. - validateChildCurrentVersions: take the Version instead of just its number so the log line can include the version status, since the method can be invoked for a deferred-swap version in any status, not only once it has entered the swap phase. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Problem Statement
Two related issues in the parent controller's concurrent-push detection:
Dead flag.
ConcurrentPushDetectionStrategy(concurrent.push.detection.strategy) gated two ways of detecting an in-flight push: the legacy "topic-based" path (derives the current push from parent version topics) and the newer "parent-version-status" path. Production parent controllers have fully migrated toPARENT_VERSION_STATUS_ONLY, so the topic-based path and the flag are dead in production, yet they still carried significant code (the parent creating/truncating version topics, orphan-topic cleanup, etc.). ATODOinVeniceControllercalled for removing it once the new mode was fully rolled out.Ambiguous push-blocking message. When a new push is rejected because a prior version exists, the rejection message branched only on
version.isVersionSwapDeferred(). A deferred-swap-enabled version whose push was still in progress was therefore reported as "make that version current" — implying it only needed a roll-forward. This ambiguity sent an investigation down the wrong path on an issue that was actually a concurrent push, not a deferred (colo-by-colo) version-swap wait.Solution
Remove topic-based tracking; make version-status tracking the only path (commit 1):
getTopicForCurrentPushJobalways delegates to the parent-version-status path. DeletesgetTopicForCurrentPushJobTopicBasedTrackingand the now-orphaned helpersgetKafkaTopicsByAge/truncateTopicsBasedOnMaxErroredTopicNumToKeep.isTopicWriteNeeded()gates: the parent no longer creates or truncates the parent version topic (addVersion,shouldSkipTruncatingTopic,rollForwardToFutureVersion, the push-completion path,killOfflinePush,DeferredVersionSwapService).ConcurrentPushDetectionStrategyenum, theconcurrent.push.detection.strategyconfig key, and the cluster-config field/parse/getter.PARENT_VERSION_STATUS_ONLY); it changes the OSS default, which wasDUAL.DeferredVersionSwapService(controller.deferred.version.swap.service.enabled) is a separate, prod-enabled feature and is intentionally kept.Clarify the push-blocking message (commit 2):
ConcurrentBatchPushExceptionnow distinguishes the cases by the version's actual status: a deferred version that isPUSHED(push complete, swap not yet done) but not yet current in all regions is reported as waiting on deferred version swap (with per-region current versions and the target swap region); everything else is reported as an in-progress concurrent push (including the version status).validateChildCurrentVersionslogs the full per-region picture and names the deferred (colo-by-colo) version swap, instead of logging only the first mismatching region.Poll child status for
STARTEDversions; simplify push-job topic lookup (commit 3):getTopicForCurrentPushJobnow branches explicitly on version status: a deferred-swap version always blocks until it rolls forward in every region;CREATED/PUSHEDalways block (a future version already occupies the store); a non-deferredONLINEversion has no ongoing push and never blocks (also fixes a stale-store misdetection); onlySTARTEDpolls the child job status, since aSTARTEDversion can be terminal in the children but not yet reflected on the parent.Address review feedback (commit 4,
46953d997):getTopicForCurrentPushJob'sisIncrementalPush/isRepushparameters are unused by this parent-only implementation.pushJobIdto the CREATED/PUSHED log line.PUSHEDinstead ofONLINE—PUSHEDis the correct in-flight state; a deferred-swap version reachingONLINEon the parent means the swap already completed, so that branch was unreachable.shouldSkipTruncatingTopicJavadoc.Remove
TopicCleanupServiceForParentController(commit 5,85f076cd4):VeniceControllernow always constructs the baseTopicCleanupService. Removed its dedicated test andTestTopicCleanupServiceForMultiKafkaClusters, whose entire premise (multi-parent-fabric iteration) no longer exists anywhere.shouldSkipTruncatingTopic(clusterName)(return isParent()) at its one call site and removed the wrapper method and its two unit tests.Code changes
concurrent.push.detection.strategy); no new config added.validateChildCurrentVersions,getTopicForCurrentPushJob); volume is unchanged (at most one line per blocked push), so no new rate-limiting is needed.Concurrency-Specific Checks
How was this PR tested?
testDeferredVersionSwapWaitMessageIsDistinctFromConcurrentPush).TestVeniceParentHelixAdmin,TestVeniceHelixAdmin,TestDeferredVersionSwapService,TestDeferredVersionSwapServiceWithSequentialRollout,TestTopicCleanupService— all pass.TestDeferredVersionSwapandTestTargetedRegionPushWithNativeReplication— all pass (incl. target-region deferred swap).TestParentControllerWithMultiDataCenterassertions were updated to the new wording, but that suite was not re-run locally.spotbugsMain(venice-controller + venice-common) andspotlessApplyare clean.Does this PR introduce any user-facing or breaking changes?
DUAL(ran both paths, returned the topic-based result) to parent-version-status-only. Theconcurrent.push.detection.strategyconfig is removed and ignored if still set. Behavior is equivalent for the supported deferred-swap and concurrent-push scenarios (validated by the deferred-swap / target-region integration tests); LinkedIn production already ran the version-status path.ConcurrentBatchPushExceptiontext shown to push jobs now distinguishes "waiting on deferred version swap" (with per-region status) from "ongoing concurrent push" (with the version status).TopicCleanupServiceForParentController: since the parent doesn't write version topics anymore, it now uses the sameTopicCleanupServiceas child controllers.🤖 Generated with Claude Code