Batch ZooKeeper multi-requests in clearBlocksInPartition#105991
Batch ZooKeeper multi-requests in clearBlocksInPartition#105991alexey-milovidov merged 9 commits into
Conversation
clearBlocksInPartition sends Remove requests for all deduplication block entries in a partition as a single ZooKeeper multi-request. For tables with large deduplication windows this can cause multi-requests exceeding ZooKeeper's jute.maxbuffer limit (default 1MB), causing TRUNCATE TABLE and DROP PARTITION to fail. The fix batches the Remove operations using zkutil::MULTI_BATCH_SIZE (100 ops per batch), consistent with other bulk-removal paths in the codebase such as removeChildren and `ryRemoveChildrenRecursive.
…ally Two pieces of review feedback on the deduplication-block batching change: 1. The previous test inserted only 200 single-row blocks and asserted just the row counts, so it passed both before and after the fix (the old single `tryMulti` request stayed under the default `jute.maxbuffer`). The test now adds a focused assertion that batching actually happened: it groups the `Remove` sub-requests for the table's deduplication nodes in `system.zookeeper_log` by `(session_id, xid)` (each multi-request shares one `xid`) and checks that no multi-request exceeds `MULTI_BATCH_SIZE` and that more than one batch was sent. The pre-fix code emits a single 200-op multi-request and fails this assertion (`0 0` instead of `1 1`), independent of `jute.maxbuffer`. 2. `clearBlocksInPartition` sent the batches strictly sequentially. It now keeps up to 16 batches in flight at once using `asyncTryMultiNoThrow`, waiting on the oldest batch before issuing a new one, so partitions with many deduplication blocks are cleared without a long chain of round-trips.
The batched rewrite of `clearBlocksInPartition` switched from `tryMulti` to `asyncTryMultiNoThrow`, which changed the failure contract. `tryMulti` throws on non-user (hardware) errors such as `ZCONNECTIONLOSS`, `ZSESSIONEXPIRED` and `ZOPERATIONTIMEOUT`, but the new code only inspected per-op errors. On session finalization the sub-responses default to `ZOK`, so a top-level hardware error was logged as nothing and the caller proceeded to truncate the caches and enqueue the `DROP_RANGE`/`TRUNCATE` as if all dedup nodes had been removed. That can leave block IDs in ZooKeeper after a successful `TRUNCATE`/`DROP PARTITION`, making later reinserts of the same blocks look like duplicates. Restore the original semantics: re-throw on non-user top-level errors and only log/ignore expected per-op user errors (e.g. `ZNONODE` for an already-removed block). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The test created its deduplication blocks with a single `INSERT ... SELECT FROM numbers(200)`. Despite `max_insert_block_size = 1`, server-side squashing collapses that into a single part and therefore a single deduplication block node in ZooKeeper. The number of dedup block nodes equals the number of separate `INSERT` statements, not the number of rows or parts, so `TRUNCATE` only had one node to remove: it used a single multi-request and the batching assertion printed `1 0` instead of `1 1`, failing deterministically. Convert the test to a shell test that issues 120 separate `INSERT` statements (more than `zkutil::MULTI_BATCH_SIZE` = 100), which creates 120 `blocks` and 120 `deduplication_hashes` nodes. `TRUNCATE` must then split the removals into several multi-requests, and the assertion distinguishes the pre-fix behaviour (one oversized multi-request) from the fixed behaviour. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@Michicosun thanks for the review — both points are addressed:
I also restored the original failure contract after the async switch (re-throw on non-user/hardware Keeper errors) in Could you take another look when you have a chance? On the failing CI
|
Apply review feedback on `clearBlocksInPartition`: - In a failed ZooKeeper multi-request, only the first failed operation carries a valid error code; the remaining sub-responses are filled with `ZRUNTIMEINCONSISTENCY`. Iterating over every sub-response therefore logged misleading errors. Use `zkutil::getFailedOpIndex` to find and log only the first genuinely failed operation, consistent with other multi-request error handling in this file. - Trim the verbose batching and failure-contract comments down to a single line as requested. The failure contract is unchanged: non-user (hardware) errors still propagate via `KeeperException`, while expected per-op user errors (e.g. `ZNONODE` for an already-removed block) are logged and ignored. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@Michicosun thanks for the review — all three points addressed in
The failure contract is unchanged: non-user (hardware) errors still propagate via |
The Upgrade check (amd_release) server.log scan fails on benign background-mutation errors left behind by 02597_column_update_tricky_expression_and_replication: an `ALTER TABLE test UPDATE d = d + throwIf(1)` with mutations_sync=0 leaves an intentionally-failing mutation that the upgraded server retries on restart before the test's later KILL MUTATION cleans it up, logging FUNCTION_THROW_IF_VALUE_IS_NON_ZERO at <Error>. This is the issue ClickHouse#39174 class (bad mutation, not a backward incompatibility). The CANNOT_PARSE_TEXT sibling ('x' as UInt64) is already in the allow-list on master; this adds the throwIf counterpart. The narrow inner-text 'Value passed to throwIf function is non-zero' matches all three emitted line shapes (MutatePlainMergeTreeTask, its executeStep(), and the MergeTreeBackgroundExecutor wrapper) without masking a genuinely different background-mutation error. Validated against real leaked upgrade_error_messages.txt (159 lines from PR ClickHouse#106386, 255 from PR ClickHouse#105991): without the entry every line leaks and the gate fails; with it 0 lines leak; a control LOGICAL_ERROR in the same MutatePlainMergeTreeTask still surfaces. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LLVM Coverage Report
Changed lines: Changed C/C++ lines covered: 27/33 (81.82%) · Uncovered code |
|
The test does not reproduce the bug with ClickHouse Keeper. |

clearBlocksInPartitionremoved all deduplication block entries for a partition in a single ZooKeepermultirequest. For tables with a largereplicated_deduplication_window, that request could exceed ZooKeeper'sjute.maxbufferlimit (1MB by default), causingTRUNCATE TABLEandDROP PARTITIONto fail.The removals are now split into batches of
zkutil::MULTI_BATCH_SIZE(100) operations, with up to 16 batches in flight at once, consistent with other bulk-removal paths in the codebase such asremoveChildrenandtryRemoveChildrenRecursive. Non-user (hardware) ZooKeeper errors from a batch are propagated instead of being silently ignored, preserving the failure semantics of the originaltryMulti-based implementation.Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
Only if you used Apache ZooKeeper (not recommended) instead of ClickHouse Keeper. Fix
TRUNCATE TABLEandDROP PARTITIONfailing on tables with many deduplication blocks, where removing them in a single ZooKeeper request could exceed the default 1MBjute.maxbufferlimit.Documentation entry for user-facing changes
Version info
26.7.1.485