Conversation
Signed-off-by: Andrew Xie <dev@xie.is>
27e2460 to
c5a11a6
Compare
…able-drop-partition
Signed-off-by: Andrew Xie <dev@xie.is>
4591889 to
953220d
Compare
CI triage for #2361 @
|
| Check | Result |
|---|---|
GrypeScanKeeper / Grype Scan (altinityinfra/clickhouse-keeper) |
fail — 1 high/critical |
GrypeScanServer (-alpine) / Grype Scan (altinityinfra/clickhouse-server:…-alpine) |
fail — 1 high/critical |
Both images fail on the same single High vulnerability, CVE-2026-85091 (an nvd:cpe match; the only other finding, CVE-2025-60876, is Medium and passes the threshold). Evidence it is not caused by this PR:
- The diff is C++ source + one test only —
src/Storages/ObjectStorage/DataLakes/Iceberg/*,StorageObjectStorage.*,IDataLakeMetadata.h, andtests/integration/.../test_drop_partition.py. NoDockerfile, no dependency/package manifest, no base-image change. Grype scans OS/runtime packages baked into the image, which this PR does not touch. - A fresh 2026 CVE.
CVE-2026-85091was published essentially now (today is 2026-09-23), so it lights up on every image built after the grype DB picked it up — independent of source changes. - Reproduces on an unrelated PR. Sibling PR Iceberg: reuse the Puffin object metadata across deletion-vector reads #2419 (Iceberg Puffin, no shared code) shows the identical two failures with the same "1 high/critical" message. Notably its ubuntu-based
clickhouse-serverimage passes with 0 high/critical — i.e. the CVE lives in the keeper + alpine base images, not in anything either PR wrote.
Suggested action: nothing to change in this PR. This is resolved at the CI/base-image level by Altinity infra — patch/rebuild the keeper and alpine base images, or add CVE-2026-85091 to the grype ignore list once triaged. Re-running the job won't clear it until the base image is updated, and it should not block review/merge of the code change.
Everything else so far: green
Finished and passing: Fast test (0 failed / 9392 passed), all Builds (amd debug/asan_ubsan/binary/release, arm release), Unit tests (asan_ubsan: 0/14839), Stateless (amd_debug parallel 0/11130; amd_asan_ubsan distributed-plan parallel 1/2 0/5550), both AST fuzzer (targeted) jobs, Integration tests (amd_asan_ubsan, targeted), Docker server/keeper images, Source upload.
PR workflow is still running — many jobs are PENDING/RUNNING (remaining Stateless shards, Integration db disk / old analyzer 1–8, Stress tests, Compatibility check, SQLLogic/SQLStorm, the RegressionTestsRelease / Iceberg regression suite, Finish Workflow). No functional failures have appeared yet, but the run isn't complete — worth a final glance once it settles, especially the Iceberg regression + integration jobs given what this PR changes.
— @blau-ai (analysis only; CI is the source of truth since I can't build/run ClickHouse here)
CI Failures AnalysisRun 35903013423, commit Related to this PRNone. Pre-existing Flaky Tests (Unrelated)
Infrastructure Issues (Unrelated)
Already-known broken tests (jobs stayed green)
Issue/Fix References
|
|
Regression tests for On REST, every scenario passed except format version 3. The suite's REST catalog rejects v3, so Spark never creates the table. On Glue that scenario is skipped for the same reason. On both catalogs the drop left the expected rows: identity and day partitions, a tuple key, an empty partition, purge on and off, IcebergS3 with no catalog, a mixed manifest, and a file written under a finer spec. Rejections also matched: unpartitioned tables, Glue scenarios that then read The new scenarios are skipped in clickhouse-regression until this is in a released build. CI failures are unrelated. Waiting on @arthurpassos for dev review. |
|
AI audit note: This review comment was generated by AI. Audit update for PR #2361 (Iceberg Confirmed defects: High: Partial manifest rewrite turns
High: A failed catalog commit deletes the manifest list the new metadata already names
Medium:
Medium: Successful drop leaves the cached “latest metadata” pointer on the pre-drop snapshot
Coverage summary:
|
|
@DimensionWieldr @xieandrew is it ready for review tho? I see "WIP: Add support for purging data files (physically delete)" |
|
@arthurpassos Yes it is ready for review. Sorry, forgot to update the description. |
arthurpassos
left a comment
There was a problem hiding this comment.
There is one fundamental issue I would like to discuss before proceeding with the review:
AFAIK, ClickHouse has two ways to drop partitions: by id and by value. By id is very simple, you provide the partition id and that is it. The value one is interesting, and is the one supported in this PR.
In ClickHouse MergeTree tables, the "value" for the drop partition is the value of each element of a partition expression tuple after it has gone through the "transforms".
You've implemented the opposite. On yours, you are required to provide the source values present in the columns.
For example:
CREATE TABLE xie_test
(
`id` UInt32,
`event_date` DateTime64
)
ENGINE = MergeTree
PARTITION BY (id, toYYYYMM(event_date))
INSERT INTO xie_test VALUES (1, now());
In the ClickHouse idiom, to drop such a partition by value you would do:
ALTER TABLE xie_test DROP PARTITION (1, toYYYYMM(now()))
On the other hand, with your implementation, you'd have to provide the raw source values.
ALTER TABLE xie_test DROP PARTITION (1, now())
I think both approaches have its pros and cons, but I would vote for keeping it consistent with MergeTree unless this is a limitation of Iceberg (tho I don't see how it could be).
Also, it would be great if you could add docs.
|
|
||
| /// Find the partition spec object with the given spec-id inside a metadata JSON document. | ||
| /// Throws METADATA_MISMATCH if the spec is not found (indicates metadata/spec-id mismatch). | ||
| Poco::JSON::Object::Ptr lookupPartitionSpec(const Poco::JSON::Object::Ptr & meta, Int64 spec_id) |
There was a problem hiding this comment.
Interesting to see this function being reused
| auto metadata_object = getMetadataJSONObject(metadata_path, object_storage, persistent_components.metadata_cache, context, log, compression_method, persistent_components.table_uuid); | ||
|
|
||
| if (!metadata_object->has(f_current_snapshot_id)) | ||
| throw Exception(ErrorCodes::BAD_ARGUMENTS, "No snapshot exists for this Iceberg table"); |
There was a problem hiding this comment.
Is this really a bad_arguments error?
There was a problem hiding this comment.
Maybe it can just succeed with a no-op (and log info)? This doesn't mean the table is in an invalid state, there's just no data files yet. A non-empty table that doesn't match any files for drop partition already is a no-op right now.
There was a problem hiding this comment.
Hm.. I would say copy ClickHouse MergeTree behavior. What happens if we try to drop a partition that does not exist on MergeTree tables? If it is no-op, make this one no-op as well.
There was a problem hiding this comment.
Yes, MergeTree is a no-op as well
|
|
||
| const Int64 current_snapshot_id = metadata_object->getValue<Int64>(f_current_snapshot_id); | ||
| if (current_snapshot_id < 0) | ||
| throw Exception(ErrorCodes::BAD_ARGUMENTS, "No snapshot exists for this Iceberg table"); |
There was a problem hiding this comment.
I can add support for dropping partition by id, but is the partition id for Iceberg tables actually exposed anywhere that the user would be able to know? It looks like
I agree that it should be consistent if possible. I'll change it and make sure the correct function for each transform from source -> iceberg partition format is documented somewhere. |
No need to, I was just putting context in the message. The real issue to be tackled is the below:
|
|
Is there a simpler way to pass a function in a single value partition? It looks like a bare function only works in a partition tuple, so I found |
Signed-off-by: Andrew Xie <dev@xie.is> - DELETED entries in partial manifests are now omitted, so they can't be turned back into live files - Failed catalog commit doesn't delete manifest list or leave orphaned files - Fix stale cache for latest metadata - Iceberg table with no snapshot now no-ops instead of throwing an exception to match MergeTree - A failure when purging data files now logs all the paths that could still exist
Signed-off-by: Andrew Xie <dev@xie.is> - Also remove a failed test that pyiceberg can't produce the right conditions for
d0cf3cf to
1407255
Compare
|
@DimensionWieldr Addressed most of the AI comments besides "Medium: iceberg_delete_data_on_drop deletes files that older snapshots still reference". This is an intentional part of drop partition, because most of the time there will be at least 1 older snapshot that references the partition. The case of no older snapshots would only be if expire snapshots was run. The rest of the defects are addressed:
|
|
@arthurpassos I made the changes from your comments and changed the partition value to accept the transformed type to be consistent with MergeTree. Feel free to review the code changes. Before I write the docs, do you have a suggestion for this?
|
|
|
||
| def months_since_epoch(value): | ||
| moment = datetime.fromisoformat(value) | ||
| return (moment.year - EPOCH.year) * 12 + moment.month - 1 |
There was a problem hiding this comment.
why do you need these?
There was a problem hiding this comment.
Removed those helpers to use plain SQL functions
I assumed |
@arthurpassos No, it doesn't work on MergeTree, you also have to wrap tuple(). I just found the existing alter partition docs which state to wrap with tuple. For this PR's docs, should I create a new file in |
So wrapping is fine. Embed it into the existing docs file. Will you ship this PR to upstream as well? |
Signed-off-by: Andrew Xie <dev@xie.is>
Signed-off-by: Andrew Xie <dev@xie.is>
|
Yes, I will submit it to upstream but haven't started the process yet. |
|
So AI found 2 other "high" potential issues: A partial manifest rewrite drops Iceberg field ids, so other engines cannot read the surviving files. Impact: DROP PARTITION that rewrites a mixed manifest replaces a valid manifest with one whose avro.schema has no field-id or element-id. Spark, Trino, and PyIceberg project manifest columns by those ids and fail the scan, so rows that should remain become unreadable outside ClickHouse. Anchor: rewriteManifestFileExcludingFiles in src/Storages/ObjectStorage/DataLakes/Iceberg/IcebergWrites.cpp, called from IcebergMetadata::tryDropPartitionOnce. Trigger: Drop one partition from a manifest that also contains other partitions (the test_drop_partition_rewrites_mixed_manifest case). Why defect: The Avro C++ compiler never loads field-id (Compiler.cc makeField). DataFileWriter then stores schema.toJson(), which omits ids (fieldIdAt is -1). The rewrite skips every avro.* metadata key, so the source manifest's id-bearing schema is not copied. ClickHouse still reads these files by name. Fix direction (short): Write the original avro.schema bytes (the id-bearing JSON) into the rewritten manifest, the same way the empty manifest-list path already does. With Impact: On a table that uses version-hint.text and has no catalog, DROP PARTITION can report success and then delete Parquet files that the visible snapshot still references. Readers with iceberg_use_version_hint keep using the old snapshot and hit missing objects. Anchor: writeMetadataFileAndVersionHint in src/Storages/ObjectStorage/DataLakes/Iceberg/Utils.cpp, then the purge block in IcebergMetadata::tryDropPartitionOnce. Trigger: iceberg_delete_data_on_drop = 1 on a version-hint table, and either a concurrent commit moves the hint to a newer version or the conditional hint write fails for every retry. Why defect: The hint loop breaks when old_version >= metadata_file_info.version, and it also falls out of the loop if every hint write throws. Both paths return true. tryDropPartitionOnce treats that as a published commit and purges files_to_be_purged. There is no check that the hint actually names this metadata file. The catalog path does Fix direction (short): Treat a hint that does not point at this metadata file as a lost commit: do not purge, return false, and let the retry loop run again. |
Signed-off-by: Andrew Xie <dev@xie.is>
|
@DimensionWieldr Addressed the dropped field ids by copying the source avro.schema for DROP PARTITION. However, it looks like other Iceberg write paths still drop field-ids, but that is a separate problem out of this PR's scope. The version hint problem was addressed by skipping the purging of data files if the version-hint.text doesn't match up with the just written metadata file. There is no retry loop or commit rollback because in this case the snapshot is already committed which another writer might have already used for the next snapshot. The main difference now is that when the hint is lost it just creates stale reads instead of deleting the files being read. Let me know if these fixes are okay |
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):
Adds support for
ALTER TABLE <table> DROP PARTITION <id>for Iceberg tables.Documentation entry for user-facing changes
Adds support for
ALTER TABLE <table> DROP PARTITION <id>for Iceberg tables. This resolves the correct partition to remove and writes a new Iceberg snapshot with the matching data files excluded.Data files are physically deleted if the option iceberg_delete_data_on_drop is enabled.
CI/CD Options
Exclude tests:
Regression jobs to run:
Closes #1046