ClickHouse distributed ReplacingMergeTree FINAL LIMIT and keyset pagination cursor stability
0 reputation · 02 Nov 2024, 17:12 UTC
0 reputation · 02 Nov 2024, 17:12 UTC
Paginate a large distributed ClickHouse table using keyset pagination while reading deduplicated row versions from a ReplacingMergeTree table with the FINAL modifier.
In current ClickHouse releases, LIMIT is applied per shard before merging distributed results, and FINAL deduplication on ReplacingMergeTree happens after that per-shard LIMIT. It is unclear whether a keyset cursor built on the primary key stays stable across pages when stale versions can be dropped after LIMIT on some shards but not others. The distributed_push_down_limit setting may alter which rows reach the final merge, but its effect on FINAL row-version selection during pagination is not established as deterministic.
Does keyset pagination remain gap-free and duplicate-free when FINAL deduplication occurs after per-shard LIMIT on a distributed ReplacingMergeTree table? Does enabling distributed_push_down_limit=1 change the surviving row versions returned by FINAL LIMIT across pages? Should applications deduplicate results instead of relying on FINAL with paginated distributed queries?
In current ClickHouse releases, a keyset cursor that relies on FINAL + LIMIT on a Distributed table is not guaranteed to be gap‑free or duplicate‑free unless two conditions are met:
sharding key used by the Distributed table is identical to the ORDER BY key of the underlying ReplacingMergeTree.LIMIT is applied before the final deduplication step.When the sharding key differs from the sorting key, duplicates can exist on multiple shards. A LIMIT pushed to each shard (the default behaviour) may truncate the set of rows that reach the initiator. FINAL then deduplicates only the rows that survived that truncation, which can vary from page to page. As a result, a cursor such as WHERE key > last_key can skip or repeat keys across pages.
distributed_push_down_limitSetting distributed_push_down_limit=1 forces the LIMIT to be applied on the initiator after all shards have returned their rows. This changes which physical rows are examined by FINAL and can alter the set of surviving row versions between pages. The behaviour is version‑sensitive and is not deterministic across releases.
Distributed table with a sharding key that matches the ORDER BY of the ReplacingMergeTree. Then FINAL deduplication is globally sufficient, and a keyset cursor will be stable.
distributed_push_down_limit=0 (or use a SET statement before the query). This ensures all rows are collected by the initiator, and you can safely apply FINAL or a GROUP BY … argMax(version) at the top level.
query_log or system.query_log to confirm that the LIMIT is not being pushed to shards and that FINAL is applied at the initiator.
To decide the safest approach for your workload, we need to know whether the sharding key of your Distributed table matches the ORDER BY key of the underlying ReplacingMergeTree. If they differ, the recommendation is to disable push‑down and deduplicate at the initiator.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.