Add persist-recursive watch for controller current-state (flag-gated)#194
Draft
LZD-PratyushBhatt wants to merge 3 commits into
Draft
Add persist-recursive watch for controller current-state (flag-gated)#194LZD-PratyushBhatt wants to merge 3 commits into
LZD-PratyushBhatt wants to merge 3 commits into
Conversation
LZD-PratyushBhatt
requested review from
PranaviAncha,
arkmish,
kabragaurav,
laxman-ch,
ngngwr,
proud-parselmouth and
thestreak101
as code owners
June 8, 2026 14:31
LZD-PratyushBhatt
force-pushed
the
lzd/current-state-persist-recursive-watch
branch
from
June 8, 2026 14:32
ce65ddc to
e61e1ed
Compare
LZD-PratyushBhatt
marked this pull request as draft
June 8, 2026 14:33
LZD-PratyushBhatt
force-pushed
the
lzd/current-state-persist-recursive-watch
branch
from
June 8, 2026 14:47
e61e1ed to
e614288
Compare
ZkClient.addPersistListener/removePersistListener selected the child- vs data-listener map via `instanceof`. A listener that implements BOTH IZkChildListener and IZkDataListener (Helix's CallbackHandler) always matched the IZkChildListener branch first, so a data-change subscription on such a listener was registered in the child-listener map. Under usePersistWatcher mode its NodeDataChanged events were then dispatched to an empty data-listener set and silently dropped (e.g. resource-level customized-state value updates never reached the controller). Replace the Object-typed addPersistListener/removePersistListener with type-explicit overloads (IZkChildListener vs IZkDataListener) so the caller selects the target map; the kind is no longer inferred from the runtime type. Behavior is unchanged for clients not using persist watches. Add TestZkClientPersistWatcher#testDualInterfaceListenerDataChangeUnderPersistWatcher, which subscribes both child and data changes with a single dual-interface listener and asserts data-change delivery (fails without this fix). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ted) The controller cold-rebuilds every participant state subtree on each leadership handoff with one child watch plus one data watch per partition (O(N*M)), re-registered on every session change. On the Venice super-cluster this is 10^5-10^6 watches re-armed per handoff and the dominant cause of multi-minute MissingTopState SLA breaches. Behind a flag (helix.controller.participantState.persistRecursiveWatch.enabled, default off), the controller's ZkClient runs with usePersistWatcher=true and each per-participant current-state CallbackHandler installs ONE ZooKeeper 3.6+ PERSISTENT_RECURSIVE watch over its subtree instead of the per-child watch loop, collapsing the watch footprint and per-handoff re-subscribe from O(N*M) to O(1). The recursive watch is scoped to the two high-cardinality current-state subtrees: CURRENT_STATE (CURRENTSTATES) and TASK_CURRENT_STATE (TASKCURRENTSTATES). All other change types (CUSTOMIZED_STATE, cluster-level views) keep per-node watches; with usePersistWatcher on these become PERSISTENT per-node watches and continue to drive customized-view aggregation correctly (see the dual-interface listener-routing fix this builds on). The recursive watch is installed once on INIT and removed in reset() (session change / shutdown) for an O(1) re-arm; it is never re-armed on CALLBACK/FINALIZE, which would otherwise leak a CallbackHandler and a server-side watch per departed session on churn. If the client does not support persist watches the handler falls back to the per-node subscribe loop. - zookeeper-api: expose subscribe/unsubscribePersistRecursiveListener on RealmAwareZkClient (default-throwing; DedicatedZkClient delegates) + usePersistWatcher in RealmAwareZkClientConfig, threaded through HelixZkClient.ZkClientConfig and DedicatedZkClientFactory so single-realm (non-multi-zk) controller clients actually run in persist mode. - helix-common: SystemPropertyKeys.PARTICIPANT_STATE_PERSIST_RECURSIVE_WATCH_ENABLED. - helix-core: ZKHelixManager builds the controller client in persist mode when enabled; CallbackHandler installs/handles (handleZNodeChange)/removes the recursive watch for CURRENT_STATE/TASK_CURRENT_STATE, with per-node fallback. - test: TestCurrentStatePersistRecursiveWatch (flag on) converges and re-converges after a participant failure and asserts the controller's client-side recursive watch is removed on participant departure (no leak); TestCustomizedViewAggregation passes with the flag on. Flag-off regressions unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
LZD-PratyushBhatt
force-pushed
the
lzd/current-state-persist-recursive-watch
branch
from
June 9, 2026 06:56
e614288 to
53a161d
Compare
Follow-up hardening from a deep correctness review of the flag-gated persist-recursive current-state watch. No behavior change when the flag is off. - ParticipantManager.createLiveInstance: read the existing live-instance with watch=false. The previous one-shot (watch=true) read throws IllegalArgumentException on a persist-watcher client, so a CONTROLLER_PARTICIPANT with the flag enabled would abort new-session handling on the duplicate-live-instance race (fast controller restart). The watch was never consumed. - CallbackHandler: gate the recursive watch on instance type (CONTROLLER/CONTROLLER_PARTICIPANT) in addition to the flag and change type, so a spectator/participant current-state handler sharing the JVM does not attempt a recursive subscribe on a non-persist client (which would warn and fall back). - CallbackHandler: make _ready volatile (read on the ZkEventThread by the new handleZNodeChange) and tighten the recursive path guard to respect the '/' segment boundary (equals(_path) || startsWith(_path + "/")). - ZkClient.executeWithInPersistListenerMutex: only unlock when the lock was actually acquired, so an interrupted lockInterruptibly() does not throw IllegalMonitorStateException from finally and mask the original exception. - Add TestCurrentStatePersistRecursiveWatch#testRecursiveWatchReArmedOnControllerSessionExpiry: expires the controller's own ZK session and asserts the recursive watch is re-installed on the new session and current-state delivery resumes (the controller-failover re-arm path). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
NA
Issues
(#200 - Link your issue number here: You can write "Fixes #XXX". Please use the proper keyword so that the issue gets closed automatically. See https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue
Any of the following keywords can be used: close, closes, closed, fix, fixes, fixed, resolve, resolves, resolved)
Description
(Write a concise description including what, why, how)
Tests
(List the names of added unit/integration tests)
(If CI test fails due to known issue, please specify the issue and test PR locally. Then copy & paste the result of "mvn test" to here.)
Changes that Break Backward Compatibility (Optional)
(Consider including all behavior changes for public methods or API. Also include these changes in merge description so that other developers are aware of these changes. This allows them to make relevant code changes in feature branches accounting for the new method/API behavior.)
Documentation (Optional)
(Link the GitHub wiki you added)
Commits
Code Quality
(helix-style-intellij.xml if IntelliJ IDE is used)