Skip to content

Antalya 26.6: Iceberg: support external paths in tables - #2154

Open
zvonand wants to merge 8 commits into
antalya-26.6from
feature/antalya-26.6/ClickHouse-ClickHouse-pr-90740
Open

Antalya 26.6: Iceberg: support external paths in tables#2154
zvonand wants to merge 8 commits into
antalya-26.6from
feature/antalya-26.6/ClickHouse-ClickHouse-pr-90740

Conversation

@zvonand

@zvonand zvonand commented Aug 3, 2026

Copy link
Copy Markdown
Member

Dropped from this backport: the AI dropped these surfaces rather than pulling in a missing prerequisite. Reviewers: confirm each is genuinely optional.

  • manifest-only compaction integration (compactIcebergManifests, writeConsolidatedManifestFile, isCurrentManifestListAboveThreshold, IcebergMetadata::optimizeManifestFiles) — that feature is not on antalya-26.6, only the PR's edits to it were dropped
  • query-condition-cache keying by the Iceberg metadata path (makeQueryConditionCacheKey, ObjectInfo::getIdentifier(bool)/getIdentifierForPath(path, bool) overloads) — depends on upstream query-condition-cache/ETag work not on antalya-26.6
  • gtest_storage_object_storage_archive.cpp update — that test file does not exist on antalya-26.6

Changelog category (leave one):

  • Improvement

Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):

Support Iceberg tables that have files outside table location or on different storage (ClickHouse#90740 by @zvonand).

Cherry-picked from ClickHouse#90740.


Closes ClickHouse#84609

Current logic is that all table files (data files, manifests, manifest lists) shall be "inside" the table location, in the same storage. This PR abandons that logic: now files can be located anywhere, even on a different storage type (e.g. all metadata is on s3, data file in in local storage).

In some cases, that old logic shoots back: even incorrect (non-existing) paths, i.e. with redundant items in a prefix were parsed in such a way that files from another locations were read (/path/to/item was read when the specified location was /path/to/to/item or vice versa)

Documentation entry for user-facing changes

  • Documentation is written (mandatory for new features)

CI/CD Options

Exclude tests:

  • Fast test
  • Integration Tests
  • Stateless tests
  • Stateful tests
  • Performance tests
  • All with ASAN
  • All with TSAN
  • All with MSAN
  • All with UBSAN
  • All with Coverage
  • All with Aarch64
  • All Regression
  • Disable CI Cache

Regression jobs to run:

  • Fast suites (mostly <1h)
  • Aggregate Functions (2h)
  • Alter (1.5h)
  • Benchmark (30m)
  • ClickHouse Keeper (1h)
  • Iceberg (2h)
  • LDAP (1h)
  • Parquet (1.5h)
  • RBAC (1.5h)
  • SSL Server (1h)
  • S3 (2h)
  • S3 Export (2h)
  • Swarms (30m)
  • Tiered Storage (2h)

zvonand added 2 commits August 3, 2026 18:36
Kept the antalya-26.6 side for everything outside the source PR's scope
(older cluster-protocol version list, non-lambda iceberg_metadata_log->add,
4-arg createReadBuffer, string-path tryGetObjectMetadata/getObjectMetadata,
try/catch around generateManifestList in Mutations, no manifest-only
compaction) and applied the PR's changes on top of it.

Also removed the upstream 26.7/26.8 SettingsChangesHistory blocks that the
three-way merge re-introduced into antalya-26.6's registry, and uncommented
the existing Antalya placeholder row for the PR's new setting instead of
adding a duplicate.

Adapted: ObjectInfo::getIdentifierForPath(path) added on antalya-26.6 (getIdentifier() refactored to use it) because the PR's getSchedulingIdentifier() relies on that accessor, which only exists upstream
Adapted: createReadBuffer / tryGetObjectMetadata / getObjectMetadata call sites keep antalya-26.6's argument shapes (string path, no read_settings/headers args) and only swap the object storage for the resolved one
Adapted: generateManifestList call sites keep antalya-26.6's signature (per-call content_type, no per_entry_content_types) and only pass the new secondary_storages argument
Adapted: manifest_file_cache_keys.emplace_back keeps antalya-26.6's 5-field ManifestFileCacheKey (no partition_spec_id)
Adapted: Iceberg/Utils.h needs an explicit #include <optional> for the new always-available declarations, since antalya-26.6 had it only inside the USE_AVRO block
Dropped: manifest-only compaction integration (compactIcebergManifests, writeConsolidatedManifestFile, isCurrentManifestListAboveThreshold, IcebergMetadata::optimizeManifestFiles) — that feature is not on antalya-26.6, only the PR's edits to it were dropped
Dropped: query-condition-cache keying by the Iceberg metadata path (makeQueryConditionCacheKey, ObjectInfo::getIdentifier(bool)/getIdentifierForPath(path, bool) overloads) — depends on upstream query-condition-cache/ETag work not on antalya-26.6
Dropped: gtest_storage_object_storage_archive.cpp update — that test file does not exist on antalya-26.6
@zvonand zvonand added releasy Created/managed by RelEasy antalya-26.6 ai-resolved Port conflict auto-resolved by Claude labels Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Workflow [PR], commit [a623cd1]

…ouse-pr-90740

# Conflicts:
#	src/Storages/ObjectStorage/DataLakes/Iceberg/Utils.h
@mkmkme

This comment was marked as outdated.

…ature/antalya-26.6/ClickHouse-ClickHouse-pr-90740
…ouse-pr-90740

# Conflicts:
#	src/Storages/ObjectStorage/DataLakes/Iceberg/IcebergMetadata.cpp
…ouse-pr-90740

# Conflicts:
#	src/Storages/ObjectStorage/DataLakes/Iceberg/IcebergWrites.cpp
#	src/Storages/ObjectStorage/StorageObjectStorageSource.cpp
#	src/Storages/ObjectStorage/StorageObjectStorageStableTaskDistributor.cpp
@zvonand

zvonand commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

@blau-ai

@blau-ai

blau-ai commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

CI triage — 3 distinct red checks: 2 PR-caused, 1 flaky/unrelated

I looked at every failing check on a623cd1. They collapse into three real signals (the top-level PR check is just the rollup gate, and each failure reproduces identically on both aarch64 and x86_64, so nothing here is a runner one-off):

Check Verdict Root cause
Regression Iceberg (2) / iceberg_2 🔴 PR-caused Iceberg export/write path
Regression S3Export (part) / s3_export_part 🔴 PR-caused same write path
Stateless tests (arm_binary, parallel) 🟡 flaky / unrelated 03518_alter_logical_race

The good news first: the reader side this PR actually targets is healthy. On the base branch iceberg_2 fails 8 iceberg engine (read) scenarios; on this PR those are green (0 read failures). The intended improvement (external/relative Iceberg paths on read) appears to work. The regressions are all on the write side.


🔴 1. Iceberg (2) / iceberg_2 — PR-caused

  • PR: 242 leaf scenarios failed, and every one is under /iceberg/export partition (842 [Fail] lines incl. parent suites; the rest of the module passes). Fails on both arches identically.
  • Base branch (antalya-26.6, latest MasterCI 31713806632): only 14 iceberg_2 failures total, of which just 4 touch export partition.
  • Even the smoke test .../sanity/export single partition fails, across no catalog / ice catalog / glue catalog and both plain/replicated merge tree → the EXPORT PARTITION operation itself is broken, not a single edge case.

Classification: whole-suite regression, base→PR = 4→842. This is the PR.

🔴 2. S3Export (part) / s3_export_part — PR-caused (same root cause)

  • PR: 66 (release) / 68 (arm) [Fail], spread across export part/{concurrent alter, concurrent other, error handling, pending mutations and patch parts, rbac, table functions}.
  • Base branch: only 5 failures, all in one sub-suite (feature support validation).
  • Same story: base→PR = 5→66. Both features share the Iceberg manifest/data-file writing code this PR reworks.

🟡 3. Stateless tests (arm_binary, parallel) — flaky, not this PR

  • Exactly one test failed: 03518_alter_logical_race [ FAIL ] 182.81 sec — a concurrency/timing stress test (ALTER race) with a documented history of stabilization commits (Let 03518_alter_logical_race extend its budget…, Stabilize 03518_alter_logical_race…).
  • This PR touches nothing in that path (Iceberg/object-storage only). Safe to re-run.

Where the write regression most likely lives

This backport of upstream ClickHouse#90740 re-plumbed the Iceberg write path to support files on secondary/external storages. Every manifest/data-file path is now routed through the new resolveObjectStorageForPath(...) (src/Storages/ObjectStorage/Utils.cpp:962), and a SecondaryStorages is threaded through generateManifestList / writeMetadataFiles / IcebergStorageSink / Iceberg::mutate:

  • IcebergWrites.cpp:929-935 — previous-snapshot manifest list now resolved via resolveObjectStorageForPath instead of the old direct path_resolver.resolve(manifest_list).
  • Mutations.cppwriteMetadataFiles / mutate now require SecondaryStorages &.

For the ordinary single-storage export (no external files — which is exactly what these tests do), that resolver must fall back to the base storage. If it instead throws (e.g. resolveObjectStorageForPathtryResolveObjectStorageForPath reaches Utils.cpp:845 "Unsupported storage scheme" for a serialized relative path that doesn't start with /), or if the manifests are now written with absolute paths the test's pyiceberg readback can't resolve, that breaks every export — matching the wholesale failure.

⚠️ I couldn't fetch the exact DB::Exception text: the regression console only carries the [Fail]/AssertionError summary; the real message lives in the S3 report.html, and web fetch is disabled in my environment. The fastest next step is to open the report and grab the Code: NNN … DB::Exception from the first .../sanity/export single partition failure — that will point straight at the throwing line:

Suggested fixes

  1. Confirm the exact exception from the report (above) — that decides between the two hypotheses.
  2. Make the write path's resolveObjectStorageForPath fall back cleanly to the base storage for serialized relative metadata paths (the single-storage case), rather than throwing / emitting absolute paths. Compare the serialized form fed at IcebergWrites.cpp:932 and Mutations.cpp against what the resolver's relative-path branch (Utils.cpp:577) expects.
  3. Re-check whether any write-path caller passes an empty/half-populated SecondaryStorages where the read path populates it.

I did not push anything. Want me to open a blau/* PR with a proposed fix once we have the exact exception, or do you want to drive it? (I can't build/run ClickHouse here, so any fix I write would be validated by re-running these two regression suites on the PR.)

Evidence: PR run 31795945657 (jobs iceberg_2 94806921483/94805671736, s3_export_part 94806921454/94805672014, stateless 94777903141); base run 31713806632 (jobs 94649901976, 94649902015). I can't build/test locally — this is static + CI-log analysis.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-resolved Port conflict auto-resolved by Claude antalya-26.6 releasy Created/managed by RelEasy

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants