feat: server-side RemoveRecursive, CheckStat, TryRemove, FilteredList,ListRecursive for clickhouse - #398
Conversation
|
Unit test report for commit 62f830d. All test cases passed! Successful Test Cases
|
|
Integration test report for commit 62f830d. All test cases passed! Successful Test Cases
|
f31f564 to
5ff46a2
Compare
…WithStatsAndData, ListRecursive Adds 5 ClickHouse Keeper operations to replace client-side fallback loops with single Raft entries: - RemoveRecursive (503): DFS tree traversal + leaf-to-root removal. ZNONODE on nonexistent path, ZNOTEMPTY if remove_nodes_limit exceeded. - CheckStat (504): Compares version + cversion + aversion in one call. Returns ZBADVERSION on mismatch. - TryRemove (505): Remove that returns ZKOK on nonexistent nodes. try_remove flag set via factory template for OpNum 505. - FilteredListWithStatsAndData (506): Extended FilteredList with with_stat/with_data wire fields. - ListRecursive (507): DFS traversal returning all descendant paths, respects max_entries limit. Feature flags extended (REMOVE_RECURSIVE..GET_CHILDREN_RECURSIVE), bitmask bumped to \xFF\x80. Test client helpers and 4 unit + 3 integration tests included. Co-Authored-By: Claude <noreply@anthropic.com>
5ff46a2 to
c180905
Compare
Three tests were flaky under sanitizer slowdown (confirmed against master CI, which fails the same tests under msan): - test_random_requests: reduced iters 10 -> 3. ~3000 serial round-trips x2 clients exceeded the 300s timeout under tsan/msan (~10x slowdown). Still exercises hundreds of randomized ops. - test_invalid_timeout_setting: added start_wait=True so the 4lw command waits for the server to rejoin the cluster instead of racing startup (was ConnectionLoss). - test_snapshot_clear: poll for the snapshot dir instead of a fixed sleep(1); under asan the dir wasn't created yet when ls ran. All three pass locally. Co-Authored-By: Claude <noreply@anthropic.com>
Code reviewFound 4 issues:
RaftKeeper/src/Service/KeeperStore.cpp Lines 623 to 627 in 2d32c46
RaftKeeper/src/Service/KeeperStore.cpp Lines 114 to 126 in 2d32c46
RaftKeeper/src/ZooKeeper/ZooKeeperCommon.cpp Lines 657 to 659 in 2d32c46
RaftKeeper/src/Service/KeeperStore.cpp Lines 862 to 864 in 2d32c46 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
|
We need more test cases. |
…teredListWithStatsAndData Addresses 4 issues from PR JDRaftKeeper#398 review (JackyWoo): 1. StoreRequestRemoveRecursive now decrements the surviving parent's numChildren and advances pzxid, matching StoreRequestRemove. Stale numChildren previously produced wrong cversion via statForResponse(). 2. shouldIncreaseZxid now excludes OpNum::ListRecursive; it is a read and must not consume a zxid. 3. ZooKeeperRemoveRequest::makeResponse() returns ZooKeeperTryRemoveResponse when try_remove, so TryRemove sub-responses carry OpNum::TryRemove. 4. FilteredListWithStatsAndData (506) now has a dedicated response type carrying per-child stats/data; StoreRequestList populates them when with_stat/with_data are set. Server cast fixed to ListResponse base (the 506 response is not a ZooKeeperListResponse). Tests: RemoveRecursive asserts parent numChildren; ListRecursive and FilteredListWithStatsAndData assert zxid stability; new FilteredListWithStatsAndData test verifies per-child data round-trips. All 67 unit + 20 test_back_to_back integration tests pass. Co-Authored-By: Claude <noreply@anthropic.com>
Code reviewThe 4 previously reported issues have been fixed. Found 1 remaining issue:
RaftKeeper/src/Service/WatchManager.cpp Lines 33 to 45 in 62ff94a 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
getOpNum() returns OpNum::TryRemove when try_remove=true, but
WatchManager::processWatches(opnum) switch only handled Remove.
TryRemove fell through to default: return {} — clients watching a
node deleted via TryRemove never received the DELETED event.
Added TryRemove case alongside Remove in the switch.
Co-Authored-By: Claude <noreply@anthropic.com>
Code reviewFound 1 issue:
RaftKeeper/src/Service/KeeperStore.cpp Lines 497 to 498 in f95d85b 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
|
There should be test cases for every changes for more code is generated by Agent. |
registerWatches classified only List/SimpleList/FilteredList as List (child) watches; FilteredListWithStatsAndData (506, which carries has_watch) fell through to a Data watch. A client watching children via 506 would get the wrong watch type and miss child-change events. Co-Authored-By: Claude <noreply@anthropic.com>
Match ClickHouse Keeper, which allows CheckStat and TryRemove as multi subrequests (its client dispatches by request class, so they share the Check/Remove classes). RaftKeeper dispatches by opnum, so StoreRequestMultiTxn threw 'Illegal command' for them despite advertising the CHECK_STAT/TRY_REMOVE feature flags — a ClickHouse client sending such a multi would be rejected. - TryRemove -> StoreRequestRemove (handles try_remove, has undo) - CheckStat -> StoreRequestCheckStat (read-only condition, no-op undo) RemoveRecursive/ListRecursive are intentionally still rejected in multi: StoreRequestRemoveRecursive has no undo, so it cannot roll back if a later subrequest fails. Adding it needs undo support first (follow-up). Test: MultiWriteWithCheckStatAndTryRemove verifies both in one write multi. 68 unit + 20 integration tests pass. Co-Authored-By: Claude <noreply@anthropic.com>
StoreRequestRemoveRecursive now snapshots every node it removes (clone() preserves each node's children set) and returns an Undo that re-adds them, restoring acl usage, ephemeral owners, and the surviving parent's children/numChildren/pzxid. This makes it safe as a multi subrequest, which is now enabled (matching ClickHouse Keeper). RaftKeeper already had closure-based rollback in StoreRequestMultiTxn; this just gives RemoveRecursive a working Undo instead of an empty one — no architectural change needed. Test: MultiRemoveRecursiveRollback puts RemoveRecursive + a failing Check in one multi and asserts the whole subtree (nodes, data, parent link, parent stat) is restored on rollback. 69 unit + 20 integration tests pass. Co-Authored-By: Claude <noreply@anthropic.com>
processRequest's watch-firing logic fired a watch for a single path (zk_request->getPath()), but RemoveRecursive deletes an entire subtree. Descendant nodes' watchers were never notified. Track the removed paths on StoreRequestRemoveRecursive and fire a DELETED watch for each. Also adds integration coverage for CheckStat, FilteredListWithStatsAndData, TryRemove/RemoveRecursive watch firing, RemoveRecursive/ListRecursive limits, and RemoveRecursive rollback inside multi transactions. Co-Authored-By: Claude <noreply@anthropic.com>
start(node) backgrounded node4's restart with start_wait=True, which makes start_raftkeeper() run its own wait_for_join_cluster(60) and kill -9 the process on timeout as a fallback. waiter.wait(timeout=50) doesn't cancel that thread, so it keeps running independently of the test's own later, separate node4.wait_for_join_cluster() call (issued only after nodes 1-3 are reconfigured). Under ASAN's slowdown the two checks landed close enough together that the background thread's stale timeout fired and killed node4's process right after the foreground had already validated it and started writing, producing a ConnectionLoss. test_three_nodes_two_alive already uses start_wait=False for this same background-start + explicit-wait_for_join_cluster idiom; apply the same here so the background thread only confirms the OS process launched and leaves the join check solely to the explicit call. Co-Authored-By: Claude <noreply@anthropic.com>
Session expiry needs up to ~1.5s beyond the negotiated timeout to be observable (500ms scan period + Raft commit round-trip), so under sanitizer slowdown the 1s-margin checks were flaky: the same test failed in master's own msan run with the identical assert 20 == 0. Adds 1s margin to each expiry check while keeping the ordering assertions (shorter-timeout sessions must expire before longer ones). Co-Authored-By: Claude <noreply@anthropic.com>
Code review (re-review)The new commits fix RemoveRecursive descendant watches (fires DELETED per removed path), FilteredListWithStatsAndData watch type registration (now treated as List), and add rollback + Multi-transaction support for RemoveRecursive/CheckStat/TryRemove. Good progress. One issue from the previous round still stands:
RaftKeeper/src/Service/KeeperStore.cpp Lines 496 to 499 in 47e4ba4 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
TryRemove returns ZOK even when the node does not exist, but watch firing was gated only on response->error == ZOK, so a TryRemove on a nonexistent path fired Event::DELETED and consumed the path's watches even though nothing was deleted (single-request path and Multi sub-requests alike). Track whether StoreRequestRemove actually removed a node (mirrors RemoveRecursive::removed_paths) and gate watch firing on it. Co-Authored-By: Claude <noreply@anthropic.com>
The tsan integration run aborted node1 with a ThreadSanitizer data race in NuRaft's commit-callback timeout path (result_code_ read outside commit_ret_elems_lock_ while the commit thread writes it under the lock). The race is only reachable when a commit exceeds client_req_timeout, and this test dir had it at 1s: under sanitizer slowdown session-establishment commits alone take ~1.3s, so the timeout path was hit routinely. With halt_on_error=1 the leader (only voter; nodes 2/3 are learners) died and 12 subsequent tests failed waiting for a cluster that could never elect a leader. NuRaft is a pinned upstream submodule, so fix on our side: raise the test configs' operation_timeout_ms (which also derives client_req_timeout_ms and the forwarder deadline) from 1s to 10s. Commits that used to trip the 1s timeout now stay on the normal path. test_cmd_conf asserts the configured value; updated to match. Co-Authored-By: Claude <noreply@anthropic.com>
Code review (re-review)No issues found. Checked for bugs and CLAUDE.md compliance. The spurious DELETED watch for TryRemove-on-missing-node is fixed: a 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
Which issues of this PR fixes:
N/A — new feature: server-side ClickHouse Keeper operations.
Change log:
Adds 5 server-side operations that replace client-side fallback loops
with single Raft entries:
ZNONODE on nonexistent path, ZNOTEMPTY if remove_nodes_limit exceeded.
Returns ZBADVERSION on mismatch. For use in Multi transactions.
Reuses StoreRequestRemove, semantics carried by opnum.
with_stat/with_data wire fields. Reuses StoreRequestList.
Respects max_entries limit with correct early-stop semantics.
Feature flags extended (REMOVE_RECURSIVE..GET_CHILDREN_RECURSIVE),
bitmask bumped to \xFF\x80. Client helpers and 4 unit + 3 integration
tests included. All 56 unit tests pass.