Skip to content

Support aggregate function states in Parquet and Iceberg - #2680

Open
zvonand wants to merge 6 commits into
antalya-26.8from
backports/antalya-26.8/2301
Open

zvonand wants to merge 6 commits into
antalya-26.8from
backports/antalya-26.8/2301

Conversation

@zvonand

@zvonand zvonand commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Closes #2206

Changelog category (leave one):

  • New Feature

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

Support aggregate function states in Parquet and Iceberg

CI/CD Options

Exclude tests:

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

Regression jobs to run:

  • Fast suites (mostly <1h)
  • Aggregate Functions (2h)
  • Alter (1.5h)
  • Benchmark (30m)
  • CAS (content-addressed storage; Antalya only)
  • ClickHouse Keeper (1h)
  • Iceberg (2h)
  • LDAP (1h)
  • OAuth (5m)
  • Parquet (1.5h)
  • RBAC (1.5h)
  • SSL Server (1h)
  • S3 (2h)
  • S3 Export (2h)
  • Swarms (30m)
  • Tiered Storage (2h)

Changes of unmerged #2301 at e44403c

Conflicts committed as-is; the next commit resolves them:
- src/DataTypes/DataTypeAggregateFunction.cpp (both modified)
- src/Formats/FormatSettings.h (both modified)
- src/Processors/Formats/Impl/Parquet/Decoding.cpp (both modified)
- src/Processors/Formats/Impl/Parquet/SchemaConverter.h (both modified)
- src/Processors/Formats/Impl/Parquet/Write.h (both modified)
- src/Processors/Formats/Impl/ParquetBlockOutputFormat.cpp (both modified)
- src/Processors/Formats/Impl/ParquetV3BlockInputFormat.cpp (both modified)
- src/Storages/ObjectStorage/DataLakes/Iceberg/DataFileStatistics.cpp (both modified)
- src/Storages/ObjectStorage/DataLakes/Iceberg/DataFileStatistics.h (both modified)
- src/Storages/ObjectStorage/DataLakes/Iceberg/IcebergMetadata.cpp (both modified)
- src/Storages/ObjectStorage/DataLakes/Iceberg/IcebergMetadata.h (both modified)
- src/Storages/ObjectStorage/DataLakes/Iceberg/MetadataGenerator.cpp (both modified)
- src/Storages/ObjectStorage/DataLakes/Iceberg/SchemaProcessor.cpp (both modified)
- src/Storages/ObjectStorage/DataLakes/Iceberg/tests/gtest_iceberg_metadata_generator.cpp (both modified)
Merged the PR's aggregate-state support with antalya-26.8's newer code (column reordering, manifest-schema tracking, excluded statistics columns, public `getState`); dropped `updateVersionFromRevision`, which doesn't exist on this branch.
@zvonand zvonand added antalya antalya-26.8 Session label (releasy session config) backport Backport labels Oct 25, 2026
@zvonand

zvonand commented Oct 25, 2026

Copy link
Copy Markdown
Member Author

Backporter: cherry-pick conflicts resolved by AI

Conflicted files:

  • src/DataTypes/DataTypeAggregateFunction.cpp (both modified)
  • src/Formats/FormatSettings.h (both modified)
  • src/Processors/Formats/Impl/Parquet/Decoding.cpp (both modified)
  • src/Processors/Formats/Impl/Parquet/SchemaConverter.h (both modified)
  • src/Processors/Formats/Impl/Parquet/Write.h (both modified)
  • src/Processors/Formats/Impl/ParquetBlockOutputFormat.cpp (both modified)
  • src/Processors/Formats/Impl/ParquetV3BlockInputFormat.cpp (both modified)
  • src/Storages/ObjectStorage/DataLakes/Iceberg/DataFileStatistics.cpp (both modified)
  • src/Storages/ObjectStorage/DataLakes/Iceberg/DataFileStatistics.h (both modified)
  • src/Storages/ObjectStorage/DataLakes/Iceberg/IcebergMetadata.cpp (both modified)
  • src/Storages/ObjectStorage/DataLakes/Iceberg/IcebergMetadata.h (both modified)
  • src/Storages/ObjectStorage/DataLakes/Iceberg/MetadataGenerator.cpp (both modified)
  • src/Storages/ObjectStorage/DataLakes/Iceberg/SchemaProcessor.cpp (both modified)
  • src/Storages/ObjectStorage/DataLakes/Iceberg/tests/gtest_iceberg_metadata_generator.cpp (both modified)

Commits:

  • ffedc5b: cherry-pick with the conflicts committed as-is

  • ba724ed: resolution

  • DataTypeAggregateFunction.cpp, FormatSettings.h, Write.h, ParquetBlockOutputFormat.cpp, Decoding.cpp, SchemaConverter.h, ParquetV3BlockInputFormat.cpp: next to the PR's additions, the branch had added fields, includes or a constructor parameter (spatial_filter_push_down, output_timestamp_adjusted_to_utc_from_timezone, precomputed_geo_columns, a longer schema-cache key). I kept both sides and added allow_aggregate_function_states to the cache key. I left out updateVersionFromRevision, which came in only as conflict context.

  • DataFileStatistics.* (adapted): the branch's excluded-column check and the PR's track_ranges are combined (if (isExcluded(i) || !track_ranges[i]) continue;). I kept the branch's Ranges type.

  • IcebergMetadata.{h,cpp} (adapted): on this branch getRelevantState and getState are public, and AlterDropPartitionExecutor calls getState. I added a SchemaParsing parameter defaulting to Parse to those public declarations instead of the PR's private copies. The branch's updateLastColumnId and format_version >= 2 handling stay, wrapped with the PR's add_schema / Skip logic. checkAggregateFunctionStatesAllowed sits after the branch's alterPartition* code.

  • SchemaProcessor.cpp and MetadataGenerator.cpp (adapted):

    • SchemaProcessor.cpp: kept the branch's flow (manifest-sourced schemas, empty-name check) and added the PR's rollback if parsing a field type fails.
    • MetadataGenerator.cpp: kept the branch's first/after_column support and getClickHouseFieldType, and added the annotation checks. The current type comes from the stored f_clickhouse_type annotation when there is one. An annotation mismatch sets type_changed, and the type-change step writes or removes the annotation.
  • gtest_iceberg_metadata_generator.cpp: kept the branch's helpers and tests (getCurrentFieldNames; findCurrentFieldType is now a thin wrapper over the PR's findCurrentFieldValue), added the PR's annotation tests, and combined both include lists.

@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Workflow [PR], commit [d2ee51d]

@zvonand zvonand changed the title Antalya 26.8 Backport of #2301 - Support aggregate function states in Parquet and Iceberg Support aggregate function states in Parquet and Iceberg Oct 25, 2026
@zvonand zvonand added port-antalya PRs to be ported to all new Antalya releases and removed backport Backport labels Oct 25, 2026
Changed the PR's new Iceberg unit tests to use antalya-26.8's API: renamed get/hasClickhouseTableSchemaById to the ClickHouse spelling and added the format_version argument to getIcebergType.
@arthurpassos

Copy link
Copy Markdown
Collaborator

Can you add a test like the below? The reason is I also need to implement some annotated types and AI is saying that 3rd party tools can overwrite your annotation

def test_clickhouse_type_annotation_survives_catalog(started_cluster):
node = started_cluster.instances["node1"]
ns, tbl = f"ann_{uuid.uuid4().hex}", "t"
create_clickhouse_iceberg_database(started_cluster, node, CATALOG_NAME)
create_clickhouse_iceberg_table(
started_cluster, node, ns, tbl,
"(id Int64, s SimpleAggregateFunction(sum, Int64))")
catalog = load_catalog_impl(started_cluster)
def annotations():
loc = catalog.load_table(f"{ns}.{tbl}").metadata_location
bucket, key = loc[len("s3://"):].split("/", 1)
meta = json.loads(get_file_contents(started_cluster.minio_client, bucket, key))
schema = next(s for s in meta["schemas"] if s["schema-id"] == meta["current-schema-id"])
return {f["name"]: f.get("clickhouse.type") for f in schema["fields"]}
# 1. Survives CREATE through the REST catalog?
assert annotations()["s"] == "SimpleAggregateFunction(sum, Int64)"
# 2. Survives a commit by another engine?
arrow_schema = pa.schema([pa.field("id", pa.int64(), nullable=False),
pa.field("s", pa.int64(), nullable=False)])
catalog.load_table(f"{ns}.{tbl}").append(
pa.Table.from_pylist([{"id": 1, "s": 2}], schema=arrow_schema))
assert annotations()["s"] == "SimpleAggregateFunction(sum, Int64)"
# 3. ClickHouse still sees the type
assert "SimpleAggregateFunction" in node.query(f"DESCRIBE {CATALOG_NAME}.{ns}.{tbl}")

@Selfeer

Selfeer commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator

Can you add a test like the below? The reason is I also need to implement some annotated types and AI is saying that 3rd party tools can overwrite your annotation

def test_clickhouse_type_annotation_survives_catalog(started_cluster):
node = started_cluster.instances["node1"]
ns, tbl = f"ann_{uuid.uuid4().hex}", "t"
create_clickhouse_iceberg_database(started_cluster, node, CATALOG_NAME)
create_clickhouse_iceberg_table(
started_cluster, node, ns, tbl,
"(id Int64, s SimpleAggregateFunction(sum, Int64))")
catalog = load_catalog_impl(started_cluster)
def annotations():
loc = catalog.load_table(f"{ns}.{tbl}").metadata_location
bucket, key = loc[len("s3://"):].split("/", 1)
meta = json.loads(get_file_contents(started_cluster.minio_client, bucket, key))
schema = next(s for s in meta["schemas"] if s["schema-id"] == meta["current-schema-id"])
return {f["name"]: f.get("clickhouse.type") for f in schema["fields"]}

1. Survives CREATE through the REST catalog?

assert annotations()["s"] == "SimpleAggregateFunction(sum, Int64)"

2. Survives a commit by another engine?

arrow_schema = pa.schema([pa.field("id", pa.int64(), nullable=False),
pa.field("s", pa.int64(), nullable=False)])
catalog.load_table(f"{ns}.{tbl}").append(
pa.Table.from_pylist([{"id": 1, "s": 2}], schema=arrow_schema))
assert annotations()["s"] == "SimpleAggregateFunction(sum, Int64)"

3. ClickHouse still sees the type

assert "SimpleAggregateFunction" in node.query(f"DESCRIBE {CATALOG_NAME}.{ns}.{tbl}")

@arthurpassos Added this in clickhouse-regression and ran it against this PR's build. You were right, the annotation doesn't survive.

Here's what I tried:

  • CREATE through the REST catalog, engine-less and with an explicit engine. The catalog writes its own metadata without clickhouse.type, so DESCRIBE already shows u String, s Int64.
  • A PyIceberg append to a table ClickHouse wrote. The annotation is gone after the commit, and uniqMerge breaks.
  • A PyIceberg add_column. Same thing.
  • A Spark commit. Same thing.

Reading works fine. Spark and PyIceberg both see the states as binary, and if you CAST back to the AggregateFunction type in ClickHouse you get the right results. But anything that writes to the table drops the field key, since it isn't in the spec.

If the type needs to survive other writers, I think it has to go somewhere they keep, like table properties.

Here is the main file you would want to look at for for ths: https://github.com/Altinity/clickhouse-regression/blob/main/iceberg/tests/iceberg_engine/aggregate_states/annotation_survival.py

https://github.com/Altinity/clickhouse-regression/blob/main/iceberg/tests/iceberg_engine/aggregate_states/external_engines.py

@Selfeer

Selfeer commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator

Defects in #2680

Head 90ca4796.

Medium

An attached Iceberg table checks allow_experimental_aggregate_function_states_in_open_formats only the first time it parses a schema id, then caches the column types. Later queries on that table deserialize AggregateFunction states even when the setting is off.

Attach an Iceberg table with a binary field u annotated AggregateFunction(uniq, UInt64). Query it once with the setting enabled, then again as a user whose setting is off. The second query should throw SUPPORT_IS_DISABLED. It returns a result instead. A one-off iceberg table function is unaffected, because it does not keep the cache. The attached table keeps it until detach or restart.

Low

Iceberg accepts a clickhouse.type annotation without checking that this ClickHouse type could have been written into that Iceberg type. Parquet rejects the same mismatch. SimpleAggregateFunction is not covered by the aggregate-state setting.

On a long field s, set clickhouse.type to SimpleAggregateFunction(anyLast, DateTime64(9)) and SELECT s. The query should be rejected. The values are read as DateTime64(9) instead.

Low

Iceberg resolves clickhouse.type through the data-type factory before it checks the setting. Parquet checks the type name first.

SELECT a column whose annotation is AggregateFunction(...) with the setting left at 0. The query should fail the setting check without constructing that function. It constructs the function, then throws SUPPORT_IS_DISABLED.

@blau-ai blau-ai mentioned this pull request Oct 28, 2026
9 of 30 tasks
The field-level `clickhouse.type` key was dropped by REST catalogs and by
commits from other engines, so aggregate-state columns silently read back as
`String` or their storage type. Annotations are now table properties
`clickhouse.type.<field-id>.<schema-id>`; a field's type in a schema is the
entry with the greatest schema id not above it, so older schemas keep theirs.

- Pass annotations explicitly to `IcebergSchemaProcessor::addIcebergTableSchema`
  and store them per schema; re-parse a schema transactionally when they change.
- Validate an annotation exactly against the field's Iceberg type.
- Ignore `clickhouse.type.*` properties that are not annotation keys.
- Register metadata schemas before maintenance operations walk manifests.
- Send property changes with REST `updateSchema`, forward properties on create.
- Move the docs sections into the engine and format `Documentation` sources.

Related: #2680 (comment)
`IcebergSchemaProcessor::getFieldType` is compiled without Avro but calls
`getIcebergType` and `icebergTypesEqualIgnoringIds`, which were declared and
defined only under `USE_AVRO`. Neither depends on Avro, so move them out of
the guard.

CI report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2680&sha=bf6ff058da7117bbd3c4ff1bc9bbb499b2236d38&name_0=PR&name_1=Fast%20test
PR: #2680
@zvonand

zvonand commented Oct 30, 2026

Copy link
Copy Markdown
Member Author

@blau-ai

@blau-ai

blau-ai commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

CI triage for d2ee51d

Summary: none of the 7 red checks look caused by this PR. I don't see anything to fix in the PR itself.

Check Failing tests Verdict
Integration (amd_asan_ubsan, db disk, old analyzer, 5/8) all 21 of test_storage_iceberg_with_spark/test_export_partition_iceberg.py Already failing on the base branch
Integration (… 6/8) 17× test_database_backup/test.py Already failing on the base branch
Integration (… 7/8) 2× test_backward_compatibility/test_custom_s3_disk_remote_url_allow_hosts.py Already failing on the base branch
Stateless (amd_debug, parallel) 05387_cas_publish_counts_replicated Flaky (CAS test, passed 16/16 reruns)
Regression release oauth /oauth/access control, /oauth/groups/* Already failing on the base branch
Regression release swarms cluster discovery/…wrong cluster name, node failure/cpu overload* Already failing on the base branch
Regression release s3_azure_1 /s3/azure/part 1/disk/remote host filter, …/disk/syntax Already failing on the base branch

How I checked: I compared against the praktika result_pr.json reports of four unrelated open PRs on antalya-26.8: #2703, #2715, #2729, #2735. All four have the same 21 test_export_partition_iceberg failures, plus the test_database_backup and test_custom_s3_disk_remote_url_allow_hosts failures in the same shards. On their GitHub checks, oauth and swarms regression fail on #2703, #2715, #2729 and #2735, and s3_azure_1 fails on #2735.

The checks that cover this feature passed: Regression iceberg_1 / iceberg_2, integration shards 1–4 and 8/8, and the rest of stateless amd_debug parallel (13638 passed). That includes the new 04673_parquet_aggregate_function_state and 05428_iceberg_… tests; the report lists only non-OK tests, and these aren't in it.


1. test_export_partition_iceberg.py (21/21): already failing on the base branch

Every test fails while setting up the table, on the CREATE TABLE … ENGINE=IcebergS3(s3, …) statement. It never gets to any export or aggregate-state code:

Code: 497. DB::Exception: default: Not enough privileges. To execute this query, it's necessary to have the grant SHOW NAMED COLLECTIONS SECRETS ON s3.
10. src/Storages/NamedCollectionsHelpers.cpp:228:18: DB::checkNamedCollectionOverride(...)
12. src/Storages/ObjectStorage/StorageObjectStorageConfiguration.cpp:114:38: DB::StorageObjectStorageConfiguration::initialize(...)

This is the named-collection override access check against the test's default user. The same 21 tests fail with the same shard layout on #2703, #2715, #2729 and #2735. To fix it on the base branch, the test user needs SHOW NAMED COLLECTIONS SECRETS on s3, or the test config needs to allow overriding the collection without it. That belongs in a separate fix on antalya-26.8, not in this PR.

2. test_database_backup/test.py (17): already failing on the base branch

The first failure is a server abort. All the others are Connection refused because the instance is dead:

<Fatal> : Logical error: 'Chosen multipart upload for an empty file. This must not happen'.

It is a CAS backup to backup_disk_s3_plain_rustfs (test_database_backup_database[cas_policy-Disk('backup_disk_s3_plain_rustfs', …)]). The same tests fail on all four comparison PRs. None of that code is in this PR.

3. test_custom_s3_disk_remote_url_allow_hosts.py (2): already failing on the base branch

Exception: Timed out while waiting for instance `node' with ip address 172.16.5.6 to start.

The same 2 tests fail on #2703, #2715, #2729 and #2735.

4. 05387_cas_publish_counts_replicated: flaky

-replicated_merge	1	0	3
+replicated_merge	0	0	3

The runner's own diagnosis: "Runs: 16, Failed: 0, Passed: 16 … The failure is not reproducible." The test checks CAS publish counters for replicated merges, which this PR doesn't touch. Safe to re-run.

5. Regression oauth / swarms / s3_azure_1: already failing on the base branch

The failing scenarios are OAuth groups/access control, swarm cluster discovery and CPU overload, and the Azure disk remote host filter / syntax. They have nothing to do with Parquet or Iceberg type mapping, and the same suites are red on #2735 (all three) and #2703, #2715, #2729 (oauth and swarms).


Other notes

  • Several jobs show as DROPPED in the praktika report and skipping on GitHub, so they didn't run for this commit: stateless amd_debug sequential, distributed plan, CAS storage suites, Stress (amd_debug/asan), AST fuzzer, BuzzHouse, SQLLogic/SQLStorm, Install packages and Compatibility check. The report gives no reason. If you want that coverage before merging, a re-run is worth trying.
  • Suggested next step: nothing to change in this PR. The test_export_partition_iceberg access error is red on every antalya-26.8 PR, so it deserves its own fix on the base branch.

Sources: PRs/2680/d2ee51d…/result_pr.json, plus the same reports for #2703, #2715, #2729 and #2735, and the gh run view --log-failed output of the regression jobs.

@zvonand

zvonand commented Oct 30, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T09:53:27.426362Z d2ee51d Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

if (old_required && !required && !old_node->result_type->equals(*type))
{
node = &dag->addCast(*old_node, type, name, nullptr);
}
else if (old_json->getValue<String>(f_name) != name)
{
node = &dag->addAlias(*old_node, name);
}

P1 Badge Retype columns when schema annotations change

When a schema evolution both changes a SimpleAggregateFunction annotation and creates a new schema ID—for example, changing sum to max while repositioning the column—the Iceberg primitive type remains identical and this branch passes the old DAG input through unchanged. Because IDataType::equals ignores the custom SimpleAggregateFunction name, old data files retain the old function/type while the new schema expects the new annotation, producing an incorrect transformed header or wrong aggregate semantics. Compare the resolved annotated types here and add a cast/retyping node whenever their custom names differ.

ℹ️ 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".

Comment on lines +928 to +932
Iceberg::setClickHouseTypeAnnotation(
metadata_object,
target_field_id,
metadata_object->getValue<Int32>(Iceberg::f_current_schema_id),
getClickHouseTypeAnnotation(type));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allocate a new schema ID for annotation-only changes

When MODIFY COLUMN changes only a SimpleAggregateFunction annotation, this overwrites the property for the existing schema ID. Historical snapshots using that same ID are then parsed with the new annotation from the latest metadata, so time-travel reads can reinterpret an old SimpleAggregateFunction(sum, T) column as max or as plain T. Preserve the old annotation by creating a new schema ID for this logical type change, or reject the alteration when the catalog cannot retain an equivalent schema under a new ID.

Useful? React with 👍 / 👎.

@Selfeer

Selfeer commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

Segfault reading an AggregateFunction Iceberg table after Spark appends to it

Version: 26.8.34.20001.altinityantalya (git hash 1c76c4f9e3eda32a942cc04b57e61c06b7cc76d4)

What happens

  1. ClickHouse writes an Iceberg table that has an AggregateFunction(uniq, UInt64) column.
  2. Spark appends rows to the same table.
  3. ClickHouse reads the table and the server crashes with Segmentation fault (11) (NULL pointer read).

The crash happens before any data is read, while ClickHouse is processing the
min/max statistics of the data files listed in the manifest
(IcebergIterator::next -> DataFileMetaInfo -> deserializeFieldFromBinaryRepr).

Steps to reproduce

You need ClickHouse, MinIO, and Spark connected to an Iceberg REST catalog.

1. In ClickHouse, create the table and insert aggregate states for 200 rows

SET allow_experimental_aggregate_function_states_in_open_formats = 1;
SET allow_insert_into_iceberg = 1;
SET write_full_path_in_iceberg_metadata = 1;

CREATE TABLE agg_t
(
    k Int32,
    u AggregateFunction(uniq, UInt64),
    s SimpleAggregateFunction(sum, Int64)
)
ENGINE = IcebergS3('http://minio:9000/warehouse/data/agg_t/', '<user>', '<password>')
SETTINGS allow_experimental_aggregate_function_states_in_open_formats = 1;

INSERT INTO agg_t (k, u, s)
SELECT
    toInt32(number % 2) AS k,
    uniqState(toUInt64(number % 23)) AS u,
    sumSimpleState(toInt64(number)) AS s
FROM numbers(0, 200)
GROUP BY k;

2. In Spark, register the table and append two more rows

The binary values are hex(uniqState(...)) for rows 200–300, computed in ClickHouse.

CREATE NAMESPACE IF NOT EXISTS demo.agg_ns;

CALL demo.system.register_table(
    table => 'agg_ns.agg_t',
    metadata_file => 's3://warehouse/data/agg_t/metadata/v2.metadata.json'
);

INSERT INTO demo.agg_ns.agg_t VALUES
    (0, X'0017000000002CCBC23487C6A393341466634489C6D3E7830665DDD168E40DD3482535B0A84475554CD51C774C0A99528EC92DE2CE187518F0A8735292BB36FBF31FD5F1F4F7C472D5A1CED8F80973457CC7E68A1C8445313DD837E77E0D', 12450),
    (1, X'0017000000002CCBC23487C6A39334146663E78306654489C6D30DD3482535B0A844DDD168E41C774C0A75554CD599528EC92DE2CE187518F0A8735292BB36FBF31FD5F1F4F7C472D5A1CED8F80973457CC7E68A1C8445313DD837E77E0D', 12500);

-- metadata file Spark just committed, used in step 3
SELECT file FROM demo.agg_ns.agg_t.metadata_log_entries ORDER BY timestamp DESC LIMIT 1;

3. In ClickHouse, read the table at Spark's metadata file

DESCRIBE works and reports the correct aggregate types:

DESCRIBE icebergS3('http://minio:9000/warehouse/data/agg_t/', '<user>', '<password>',
    SETTINGS iceberg_metadata_file_path = 'metadata/00000-<uuid>.metadata.json');
k   Int32
u   AggregateFunction(uniq, UInt64)
s   SimpleAggregateFunction(sum, Int64)

Merging the states crashes the server:

SELECT k, uniqMerge(u), sum(s)
FROM icebergS3('http://minio:9000/warehouse/data/agg_t/', '<user>', '<password>',
    SETTINGS iceberg_metadata_file_path = 'metadata/00000-<uuid>.metadata.json')
GROUP BY k
ORDER BY k
SETTINGS allow_experimental_aggregate_function_states_in_open_formats = 1;
Code: 32. DB::Exception: Attempt to read after eof: while receiving packet from localhost:9000. (ATTEMPT_TO_READ_AFTER_EOF)

Expected: two rows, one per k, with the states from both ClickHouse and Spark merged.

Important

Check the details below

Full stack trace (two pipeline threads, identical stacks)
2026.10.30 11:12:41.817236 [ 41 ] <Fatal> BaseDaemon: ########################################
2026.10.30 11:12:41.817298 [ 41 ] <Fatal> BaseDaemon: (version 26.8.34.20001.altinityantalya, build id: CA2FE9C385C6C122EB2BCA5C3D19F257387C9C23, git hash: 1c76c4f9e3eda32a942cc04b57e61c06b7cc76d4) (from thread 316) (query_id: 8b0fd3f6-a13b-43cc-b8f3-e071377b939e) (query: SELECT k, uniqMerge(u), sum(s) FROM icebergS3('http://minio:9000/warehouse/data/agg_65b08973_d44a_11f1_a617_9cb1507b8e87/', '[masked]:Secret(name='minio_root_user')', '[HIDDEN]', SETTINGS '[HIDDEN]') GROUP BY k ORDER BY k ASC FORMAT TabSeparated) Received signal Segmentation fault (11)
2026.10.30 11:12:41.817321 [ 41 ] <Fatal> BaseDaemon: Address: NULL pointer. Access: read. Sent by the kernel.
2026.10.30 11:12:41.817332 [ 41 ] <Fatal> BaseDaemon: Stack trace: 0x0000000018efb797 0x000000001dc105c3 0x000000001dc1065b 0x000000001ada2049 0x000000001ad7b027 0x000000001ae72ba6 0x000000001ac4f2f8 0x000000001ac484a1 0x000000001f55a5a9 0x000000001f55a101 0x000000001f58246c 0x000000001f571c1a 0x000000001f573f9a 0x00000000176881e3 0x0000000017691a8c 0x0000000017685a97 0x000000001768ebda 0x00007c569eef8ac3 0x00007c569ef8a850
2026.10.30 11:12:41.817408 [ 41 ] <Fatal> BaseDaemon: 2. UniquesHashSet<DefaultHash<unsigned long>>::write(DB::WriteBuffer&) const @ 0x0000000018efb797
2026.10.30 11:12:41.817457 [ 41 ] <Fatal> BaseDaemon: 3. DB::ColumnAggregateFunction::operator[](unsigned long) const @ 0x000000001dc105c3
2026.10.30 11:12:41.817511 [ 41 ] <Fatal> BaseDaemon: 4. DB::ColumnAggregateFunction::get(unsigned long, DB::Field&) const @ 0x000000001dc1065b
2026.10.30 11:12:41.817552 [ 41 ] <Fatal> BaseDaemon: 5. DB::Iceberg::deserializeFieldFromBinaryRepr(String const&, std::shared_ptr<DB::IDataType const>, bool, bool) @ 0x000000001ada2049
2026.10.30 11:12:41.817594 [ 41 ] <Fatal> BaseDaemon: 6. DB::DataFileMetaInfo::DataFileMetaInfo(DB::Iceberg::IcebergSchemaProcessor const&, int, int, std::unordered_map<int, DB::Iceberg::ColumnInfo, std::hash<int>, std::equal_to<int>, std::allocator<std::pair<int const, DB::Iceberg::ColumnInfo>>> const&, std::unordered_map<int, std::pair<DB::Field, DB::Field>, std::hash<int>, std::equal_to<int>, std::allocator<std::pair<int const, std::pair<DB::Field, DB::Field>>>> const&, String const&) @ 0x000000001ad7b027
2026.10.30 11:12:41.817637 [ 41 ] <Fatal> BaseDaemon: 7. DB::IcebergIterator::next(unsigned long) @ 0x000000001ae72ba6
2026.10.30 11:12:41.817688 [ 41 ] <Fatal> BaseDaemon: 8. DB::StorageObjectStorageSource::createReader(unsigned long, DB::StorageID const&, std::shared_ptr<DB::IObjectIterator> const&, std::shared_ptr<DB::StorageObjectStorageConfiguration> const&, std::shared_ptr<DB::IObjectStorage> const&, DB::ReadFromFormatInfo&, std::optional<DB::FormatSettings> const&, std::shared_ptr<DB::Context const> const&, DB::SchemaCache*, std::shared_ptr<Poco::Logger> const&, unsigned long, std::shared_ptr<DB::FormatParserSharedResources>, std::shared_ptr<DB::FormatFilterInfo>, bool) @ 0x000000001ac4f2f8
2026.10.30 11:12:41.817721 [ 41 ] <Fatal> BaseDaemon: 9. DB::StorageObjectStorageSource::generate() @ 0x000000001ac484a1
2026.10.30 11:12:41.817755 [ 41 ] <Fatal> BaseDaemon: 10. DB::ISource::tryGenerate() @ 0x000000001f55a5a9
2026.10.30 11:12:41.817783 [ 41 ] <Fatal> BaseDaemon: 11. DB::ISource::work() @ 0x000000001f55a101
2026.10.30 11:12:41.817817 [ 41 ] <Fatal> BaseDaemon: 12. DB::ExecutionThreadContext::executeTask() @ 0x000000001f58246c
2026.10.30 11:12:41.817856 [ 41 ] <Fatal> BaseDaemon: 13. DB::PipelineExecutor::executeStepImpl(unsigned long, DB::WorkloadResources&&, std::atomic<bool>*) @ 0x000000001f571c1a
2026.10.30 11:12:41.817891 [ 41 ] <Fatal> BaseDaemon: 14. void std::__function::__policy_func<void ()>::__call_func[abi:fqe220101]<DB::PipelineExecutor::spawnThreads(std::shared_ptr<DB::IAcquiredSlot>)::$_0>(std::__function::__policy_storage const*) @ 0x000000001f573f9a
2026.10.30 11:12:41.817929 [ 41 ] <Fatal> BaseDaemon: 15. ThreadPoolImpl<ThreadFromGlobalPoolImpl<false, true>>::ThreadFromThreadPool::worker() @ 0x00000000176881e3
2026.10.30 11:12:41.817976 [ 41 ] <Fatal> BaseDaemon: 16. void std::__function::__policy_func<void ()>::__call_func[abi:fqe220101]<startThreadFromGlobalPool(std::shared_ptr<ThreadFromGlobalPoolState>, std::function<void ()>, unsigned long, unsigned long, bool, bool)::$_0>(std::__function::__policy_storage const*) @ 0x0000000017691a8c
2026.10.30 11:12:41.818008 [ 41 ] <Fatal> BaseDaemon: 17. ThreadPoolImpl<std::thread>::ThreadFromThreadPool::worker() @ 0x0000000017685a97
2026.10.30 11:12:41.818058 [ 41 ] <Fatal> BaseDaemon: 18. void* std::__thread_proxy[abi:fqe220101]<std::tuple<std::unique_ptr<std::__thread_struct, std::default_delete<std::__thread_struct>>, void (ThreadPoolImpl<std::thread>::ThreadFromThreadPool::*)(), ThreadPoolImpl<std::thread>::ThreadFromThreadPool*>>(void*) @ 0x000000001768ebda
2026.10.30 11:12:41.818128 [ 41 ] <Fatal> BaseDaemon: 19. ? @ 0x0000000000094ac3
2026.10.30 11:12:41.818162 [ 41 ] <Fatal> BaseDaemon: 20. ? @ 0x0000000000126850
2026.10.30 11:12:41.818197 [ 41 ] <Fatal> BaseDaemon: Integrity check of the executable skipped because the reference checksum could not be read.
2026.10.30 11:12:41.818305 [ 41 ] <Fatal> BaseDaemon: Changed settings: use_uncompressed_cache = false, load_balancing = 'random', max_memory_usage = 10000000000, allow_experimental_aggregate_function_states_in_open_formats = true, allow_experimental_analyzer = false
2026.10.30 11:12:41.818629 [ 41 ] <Fatal> BaseDaemon: ########################################
2026.10.30 11:12:41.818637 [ 41 ] <Fatal> BaseDaemon: (version 26.8.34.20001.altinityantalya, build id: CA2FE9C385C6C122EB2BCA5C3D19F257387C9C23, git hash: 1c76c4f9e3eda32a942cc04b57e61c06b7cc76d4) (from thread 333) (query_id: 8b0fd3f6-a13b-43cc-b8f3-e071377b939e) (query: SELECT k, uniqMerge(u), sum(s) FROM icebergS3('http://minio:9000/warehouse/data/agg_65b08973_d44a_11f1_a617_9cb1507b8e87/', '[masked]:Secret(name='minio_root_user')', '[HIDDEN]', SETTINGS '[HIDDEN]') GROUP BY k ORDER BY k ASC FORMAT TabSeparated) Received signal Segmentation fault (11)
2026.10.30 11:12:41.818644 [ 41 ] <Fatal> BaseDaemon: Address: NULL pointer. Access: read. Sent by the kernel.
2026.10.30 11:12:41.818648 [ 41 ] <Fatal> BaseDaemon: Stack trace: 0x0000000018efb797 0x000000001dc105c3 0x000000001dc1065b 0x000000001ada2049 0x000000001ad7b027 0x000000001ae72ba6 0x000000001ac4f2f8 0x000000001ac484a1 0x000000001f55a5a9 0x000000001f55a101 0x000000001f58246c 0x000000001f571c1a 0x000000001f573f9a 0x00000000176881e3 0x0000000017691a8c 0x0000000017685a97 0x000000001768ebda 0x00007c569eef8ac3 0x00007c569ef8a850
2026.10.30 11:12:41.818693 [ 41 ] <Fatal> BaseDaemon: 2. UniquesHashSet<DefaultHash<unsigned long>>::write(DB::WriteBuffer&) const @ 0x0000000018efb797
2026.10.30 11:12:41.818703 [ 41 ] <Fatal> BaseDaemon: 3. DB::ColumnAggregateFunction::operator[](unsigned long) const @ 0x000000001dc105c3
2026.10.30 11:12:41.818711 [ 41 ] <Fatal> BaseDaemon: 4. DB::ColumnAggregateFunction::get(unsigned long, DB::Field&) const @ 0x000000001dc1065b
2026.10.30 11:12:41.818724 [ 41 ] <Fatal> BaseDaemon: 5. DB::Iceberg::deserializeFieldFromBinaryRepr(String const&, std::shared_ptr<DB::IDataType const>, bool, bool) @ 0x000000001ada2049
2026.10.30 11:12:41.818742 [ 41 ] <Fatal> BaseDaemon: 6. DB::DataFileMetaInfo::DataFileMetaInfo(DB::Iceberg::IcebergSchemaProcessor const&, int, int, std::unordered_map<int, DB::Iceberg::ColumnInfo, std::hash<int>, std::equal_to<int>, std::allocator<std::pair<int const, DB::Iceberg::ColumnInfo>>> const&, std::unordered_map<int, std::pair<DB::Field, DB::Field>, std::hash<int>, std::equal_to<int>, std::allocator<std::pair<int const, std::pair<DB::Field, DB::Field>>>> const&, String const&) @ 0x000000001ad7b027
2026.10.30 11:12:41.818749 [ 41 ] <Fatal> BaseDaemon: 7. DB::IcebergIterator::next(unsigned long) @ 0x000000001ae72ba6
2026.10.30 11:12:41.818766 [ 41 ] <Fatal> BaseDaemon: 8. DB::StorageObjectStorageSource::createReader(unsigned long, DB::StorageID const&, std::shared_ptr<DB::IObjectIterator> const&, std::shared_ptr<DB::StorageObjectStorageConfiguration> const&, std::shared_ptr<DB::IObjectStorage> const&, DB::ReadFromFormatInfo&, std::optional<DB::FormatSettings> const&, std::shared_ptr<DB::Context const> const&, DB::SchemaCache*, std::shared_ptr<Poco::Logger> const&, unsigned long, std::shared_ptr<DB::FormatParserSharedResources>, std::shared_ptr<DB::FormatFilterInfo>, bool) @ 0x000000001ac4f2f8
2026.10.30 11:12:41.818776 [ 41 ] <Fatal> BaseDaemon: 9. DB::StorageObjectStorageSource::generate() @ 0x000000001ac484a1
2026.10.30 11:12:41.818784 [ 41 ] <Fatal> BaseDaemon: 10. DB::ISource::tryGenerate() @ 0x000000001f55a5a9
2026.10.30 11:12:41.818793 [ 41 ] <Fatal> BaseDaemon: 11. DB::ISource::work() @ 0x000000001f55a101
2026.10.30 11:12:41.818800 [ 41 ] <Fatal> BaseDaemon: 12. DB::ExecutionThreadContext::executeTask() @ 0x000000001f58246c
2026.10.30 11:12:41.818809 [ 41 ] <Fatal> BaseDaemon: 13. DB::PipelineExecutor::executeStepImpl(unsigned long, DB::WorkloadResources&&, std::atomic<bool>*) @ 0x000000001f571c1a
2026.10.30 11:12:41.818822 [ 41 ] <Fatal> BaseDaemon: 14. void std::__function::__policy_func<void ()>::__call_func[abi:fqe220101]<DB::PipelineExecutor::spawnThreads(std::shared_ptr<DB::IAcquiredSlot>)::$_0>(std::__function::__policy_storage const*) @ 0x000000001f573f9a
2026.10.30 11:12:41.818833 [ 41 ] <Fatal> BaseDaemon: 15. ThreadPoolImpl<ThreadFromGlobalPoolImpl<false, true>>::ThreadFromThreadPool::worker() @ 0x00000000176881e3
2026.10.30 11:12:41.818845 [ 41 ] <Fatal> BaseDaemon: 16. void std::__function::__policy_func<void ()>::__call_func[abi:fqe220101]<startThreadFromGlobalPool(std::shared_ptr<ThreadFromGlobalPoolState>, std::function<void ()>, unsigned long, unsigned long, bool, bool)::$_0>(std::__function::__policy_storage const*) @ 0x0000000017691a8c
2026.10.30 11:12:41.818854 [ 41 ] <Fatal> BaseDaemon: 17. ThreadPoolImpl<std::thread>::ThreadFromThreadPool::worker() @ 0x0000000017685a97
2026.10.30 11:12:41.818866 [ 41 ] <Fatal> BaseDaemon: 18. void* std::__thread_proxy[abi:fqe220101]<std::tuple<std::unique_ptr<std::__thread_struct, std::default_delete<std::__thread_struct>>, void (ThreadPoolImpl<std::thread>::ThreadFromThreadPool::*)(), ThreadPoolImpl<std::thread>::ThreadFromThreadPool*>>(void*) @ 0x000000001768ebda
2026.10.30 11:12:41.818887 [ 41 ] <Fatal> BaseDaemon: 19. ? @ 0x0000000000094ac3
2026.10.30 11:12:41.818899 [ 41 ] <Fatal> BaseDaemon: 20. ? @ 0x0000000000126850
2026.10.30 11:12:41.818911 [ 41 ] <Fatal> BaseDaemon: Integrity check of the executable skipped because the reference checksum could not be read.
2026.10.30 11:12:41.818956 [ 41 ] <Fatal> BaseDaemon: Changed settings: use_uncompressed_cache = false, load_balancing = 'random', max_memory_usage = 10000000000, allow_experimental_aggregate_function_states_in_open_formats = true, allow_experimental_analyzer = false
Where it was found
  • Regression test: /iceberg/aggregate states/rest catalog/external engines/clickhouse merges after spark commit
  • Source: iceberg/tests/iceberg_engine/aggregate_states/external_engines.py, clickhouse_merges_after_spark_commit

This branch has not been deployed

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

Labels

antalya antalya-26.8 Session label (releasy session config) port-antalya PRs to be ported to all new Antalya releases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants