Export partition - allow non matching partition expressions in case we can prove the destination expression does not split the data - #2074
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3f3af7c4fc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Consider mentioning 'export' in the title and/or description |
Done, I'll soon add the description |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ca43cf1f6d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Hey @arthurpassos - separate from the positional/name column-matching issue. I want to confirm the intended behaviour here before I pin it in a test. What happensA destination with an extra column is rejected on column count: CREATE TABLE src (id Int64, a Int32)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/shard0/src', '{replica}')
ORDER BY tuple() PARTITION BY a;
CREATE TABLE dst (id Int64, a Int32, b Int32)
ENGINE = S3(..., format='Parquet', partition_strategy='hive') PARTITION BY a;
INSERT INTO src VALUES (1, 42);
ALTER TABLE src EXPORT PARTITION ID '42' TO TABLE dst;
-- Code: 20. Number of columns doesn't match (source: 2 and result: 3).That one seems right to me. But giving the extra column a CREATE TABLE dst (id Int64, a Int32, b Int32 DEFAULT 42)
ENGINE = S3(..., format='Parquet', partition_strategy='hive') PARTITION BY a;
ALTER TABLE src EXPORT PARTITION ID '42' TO TABLE dst;
-- Code: 20. Number of columns doesn't match (source: 2 and result: 3).Same error, same counts - the What I'd expectI'd expect the second case to succeed, with SELECT id, a, b FROM dst;
-- 1 42 42which is what INSERT INTO dst (id, a)
SELECT id, a
FROM src;would produce. A Is the current behaviour intended? There's no data-loss risk either way since it's a loud rejection - I just want the test to assert the intended behaviour rather than whichever one I guess at. |
|
I suggest adding setting to turn on/off this functionality. |
| } | ||
| } | ||
| } | ||
| terms.push_back(std::move(term)); |
There was a problem hiding this comment.
Seems that exsits a case where term is empty. Is this expected behavior?
|
Can you check #2138 - this does seem like we should not be allowing the export here or have some other fix in this case. |
|
I think jump also reported the same thing and you agreed that this is okay, @arthurpassos but please check the issue and if all is right even with the Insert and Export behavior being different - I'll just update the expectations of our tests. |
Audit Review — PR #2074
Summary of findings
1. 🔴 High — Argument order lost, monotonicity proof evaluates the wrong expressionAnchor: Impact: A source partition that spans multiple destination partitions can pass validation. All rows of a part are then written into the single directory computed from the part's min row — rows are silently misplaced into a wrong destination partition, and readers relying on hive/wildcard partition pruning get wrong query results. Trigger (smallest realistic case):
Why it is a defect:
Iceberg destinations are unaffected by luck of signature: Fix direction: Store the full ordered argument list (or the sub-AST) in Regression test direction: Stateless test exporting from 2. 🟠 Medium — Iceberg commit permanently fails when no exported part remains locallyAnchor: Impact: An export that has already uploaded all data files retries the commit until Trigger: Exported parts are merged away and cleaned up (
Why it is a defect: The function now requires at least one part from Fix direction: Persist the folded min/max (or the derived partition source block) in the ZooKeeper manifest at schedule time and use it at commit — removing the dependency on local parts entirely. Regression test direction: Integration test: schedule an Iceberg export, let all source parts merge, drop old parts (or restart the node), then assert the commit still succeeds. 3. 🟡 Low —
|
| Destination key shape | What happens |
|---|---|
toStartOfInterval(ts, INTERVAL 1 DAY) |
nested-function argument is dropped; rebuilt call has wrong arity → NUMBER_OF_ARGUMENTS_DOESNT_MATCH |
| Float literal in the key | Field::safeGet<Int64> throws BAD_GET |
Nested expression, e.g. toYYYYMM(toDate(ts)) |
error message names column '' |
Iceberg void transform |
mapped to tuple, which has no monotonicity → always rejected, although void never repartitions anything |
Fix direction: Reject unparseable/nested terms explicitly with BAD_ARGUMENTS before building the function; special-case void as trivially single-valued.
Regression test direction: Negative stateless tests asserting BAD_ARGUMENTS for each shape; an accept-case for a void field in the Iceberg spec.
5. 🟡 Low — Force re-export deletes previous export state before validation
Anchor: src/Storages/StorageReplicatedMergeTree.cpp — exportPartitionToTable (tryRemoveRecursive runs before verifyPlainPartitionCompatibility)
Impact: EXPORT PARTITION ... SETTINGS export_merge_tree_partition_force_export = 1 to a plain destination with an incompatible partition key removes the existing export's ZooKeeper state (killing an in-progress export) and then throws — leaving no export scheduled at all.
Why it is a defect: Before this PR the plain-destination partition-key check ran at the top of the function, before any ZooKeeper mutation. The new check needs parts, which are collected after the destructive tryRemoveRecursive. (The Iceberg check already had this ordering, so for Iceberg destinations this is pre-existing.)
Fix direction: Collect parts and run both partition-compatibility checks before the force-removal of the previous export.
Regression test direction: Integration test: force re-export with an incompatible destination key; assert the previous export state survives.
Coverage summary
Scope reviewed — full PR diff (13 files):
- term parsing and structural matching; cast + transform monotonicity proof
- plain and Iceberg schedule-time gates in
MergeTreeData::exportPartToTable/StorageReplicatedMergeTree::exportPartitionToTable - Iceberg commit min/max derivation;
iceberg_partition_timezonemanifest plumbing - write paths (
ExportPartTask→StorageObjectStorage::import→computePartitionKey) - commit retry/failure classification; part pinning lifecycle; test changes
Categories failed: destination-term reconstruction (argument order); commit-time part availability (restart); Nullable-wrapper gating; error-contract consistency; force-export rollback ordering.
Categories passed:
- Iceberg transform mapping and width-first argument order
- structural match type gating — including
bucketbeing structural-only, sinceicebergBucketTransformexposes no monotonicity - cast monotonicity gate — non-monotonic casts such as
Int → Stringcorrectly fail closed becauseCASTreports no monotonicity for them - date transforms (
toYearNumSinceEpoch,toMonthNumSinceEpoch,toRelativeDayNum,toRelativeHourNum) have monotonicity viaIFunctionDateOrDateTime - unpartitioned-destination fast paths;
MinMaxIndexfold soundness, including partial part sets at commit - timezone consistency schedule → manifest → execution; manifest forward/backward JSON compatibility
- concurrency: schedule-time parts snapshot under
lockParts, commit underreadLockParts, immutableminmax_idx - no sensitive-data leakage in new messages
- dismissed candidate:
Field::safeGet<Int64>accepts UInt64 literals, so integer literals in partition keys parse fine
Assumptions / limits:
- Static reasoning only; no runtime execution.
- Pre-existing and unchanged by this PR:
verifyExportSchemaCastablemaps columns positionally while all partition checks and the hive write path match by name — coincidentally-named but positionally-different columns can validate against the wrong source column. The identical-AST fast path also does not compare column types/timezones. - Timezone monotonicity relies on the same DST assumptions as partition pruning.
I think this deserves a separate issue |
Export partition is already experimental and back by a setting the user must opt in |
|
@Selfeer I would appreciate if you could "humanize" a bit more those AI reports. For example, the following issue is very hard to understand and doesn't explain what is happening: 1. 🔴 High — Argument order lost, monotonicity proof evaluates the wrong expression. It says "anchor" pointing to some code location. Then it says the impact. Then the trigger. It is too much to read to understand the real problem. Brain energy required to process this is very high. Instead, if you could understand the issue yourself first and then give me a few SQL instructions that repro the case and a human explanation of what's going on, that would be 100 times better. |
I agree. I'd much rather do it the way you described: perform my own investigation on the findings and raise issues as needed-and we actually do that. But even in that case, I would still have to post this exact message first. The purpose of the audit review has always been to perform a quick review of the PR without running any tests first, share the findings with the developer, and let you decide whether they are actual issues. If they are, we then raise separate issues afterward. I can update the skills we use for the audit review to make the output easier to read, but overall, the audit review has always been a separate part of the verification process, separate from our actual testing. |
|
@k-morozov hi, I have made some refactorings, fixed conflicts and updated the docs. Could you please re-review it? |
|
@Selfeer regarding the 5 AI findings you posted
|
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):
Allow export partition through different partition expressions as long as the destination expression does not repartition the data. This is validated at schedule time through two mechanisms:
Documentation entry for user-facing changes
...
CI/CD Options
Exclude tests:
Regression jobs to run: