Antalya 26.5: Forward port of export part and partition by zvonand · Pull Request #1963 · Altinity/ClickHouse · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Cherry-pick of #1618 with unresolved conflict markers (resolution in …
…next commit)

---
Original cherry-pick message follows:

Merge pull request #1618 from Altinity/export_partition_iceberg

Export partition to apache iceberg
# Conflicts:
#	src/Core/ServerSettings.cpp
#	src/Storages/MergeTree/MergeTreeData.cpp
#	src/Storages/ObjectStorage/DataLakes/IDataLakeMetadata.h
#	src/Storages/ObjectStorage/DataLakes/Iceberg/Constant.h
#	src/Storages/ObjectStorage/DataLakes/Iceberg/IcebergMetadata.h
#	src/Storages/ObjectStorage/DataLakes/Iceberg/IcebergWrites.cpp
#	src/Storages/ObjectStorage/DataLakes/Iceberg/IcebergWrites.h
#	src/Storages/ObjectStorage/DataLakes/Iceberg/MetadataGenerator.cpp
#	src/Storages/ObjectStorage/DataLakes/Iceberg/MultipleFileWriter.cpp
#	src/Storages/ObjectStorage/DataLakes/Iceberg/MultipleFileWriter.h
#	src/Storages/ObjectStorage/DataLakes/Iceberg/Utils.cpp
#	src/Storages/ObjectStorage/StorageObjectStorage.cpp
#	src/Storages/ObjectStorage/StorageObjectStorageCluster.cpp
#	src/Storages/ObjectStorage/StorageObjectStorageCluster.h
#	tests/integration/test_export_replicated_mt_partition_to_object_storage/test.py
  • Loading branch information
zvonand committed Jun 25, 2026
commit 374ce2744f6ae4f4a8281c17d8910ae4a1055a7e
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

## Overview

The `ALTER TABLE EXPORT PARTITION` command exports entire partitions from Replicated*MergeTree tables to object storage (S3, Azure Blob Storage, etc.), typically in Parquet format. This feature coordinates export part operations across all replicas using ZooKeeper.

Each MergeTree part will become a separate file with the following name convention: `<table_directory>/<partitioning>/<data_part_name>_<merge_tree_part_checksum>.<format>`. To ensure atomicity, a commit file containing the relative paths of all exported parts is also shipped. A data file should only be considered part of the dataset if a commit file references it. The commit file will be named using the following convention: `<table_directory>/commit_<partition_id>_<transaction_id>`.
The `ALTER TABLE EXPORT PARTITION` command exports entire partitions from Replicated*MergeTree tables to object storage (S3, Azure Blob Storage, etc.) or data lakes like Apache Iceberg tables (with and without catalogs), typically in Parquet format. This feature coordinates export part operations across all replicas using ZooKeeper.

The set of parts that are exported is based on the list of parts the replica that received the export command sees. The other replicas will assist in the export process if they have those parts locally. Otherwise they will ignore it.

Expand All @@ -16,6 +14,20 @@ The export task can be killed by issuing the kill command: `KILL EXPORT PARTITIO

The task is persistent - it should be resumed after crashes, failures and etc.

### On Apache Iceberg storage exports:

Each MergeTree part will become a separate file (or more depending on `max_bytes` and `max_rows` settings) following the engine naming convention. Once all parts have been exported, new snapshots / manifest files are generated and the data is comitted using the Apache Iceberg commit mechanism.

The manifest file produced by the commit contains a summary field `clickhouse.export-partition-transaction-id` that stores the transaction id. This field is used to implement idempotency and avoid data duplication. Some Apache Iceberg storage managers employ old manifests cleanup, ClickHouse does not.

**IMPORTANT**: In case the storage is managed by a 3rd party application that cleans up old manifest files, it is important that the TTL of such files are greater than the timeout of export partition tasks. If it is not configured in such a way, it is possible to accidentally duplicate data in the extremely rare case a ClickHouse node is the only node working on a given export task, commits the data to Iceberg, crashes before marking the task as done and only boots up after the manifest cleanup has deleted the commit manifest. In such scenario, ClickHouse would attempt to commit those files again producing duplicates. The task timeout on ClickHouse side is controlled by the setting `export_merge_tree_partition_task_timeout_seconds`.

The Iceberg manifest files contain statistics about the data. Exporting a merge tree partition is a non ephemeral long running task, in which nodes can be turned off and turned on. This means the stats of individual files need to be persisted somewhere in order to produce the final manifest. This is implemented through sidecars. Each data file exported will contain a "sibling" sidecar file named `<data_file_name>_clickhouse_export_part_sidecar.avro`. ClickHouse does not clean up these files, and they can be safely deleted once the data is comitted.

### On plain object storage exports:

Each MergeTree part will become a separate file with the following name convention: `<table_directory>/<partitioning>/<data_part_name>_<merge_tree_part_checksum>.<format>`. To ensure atomicity, a commit file containing the relative paths of all exported parts is also shipped. A data file should only be considered part of the dataset if a commit file references it. The commit file will be named using the following convention: `<table_directory>/commit_<partition_id>_<transaction_id>`.

## Syntax

```sql
Expand All @@ -35,7 +47,7 @@ TO TABLE [destination_database.]destination_table

### Server Settings

#### `enable_experimental_export_merge_tree_partition_feature` (Required)
#### `allow_experimental_export_merge_tree_partition` (Required)

- **Type**: `Bool`
- **Default**: `false`
Expand All @@ -47,7 +59,7 @@ TO TABLE [destination_database.]destination_table

- **Type**: `Bool`
- **Default**: `false`
- **Description**: Ignore existing partition export and overwrite the ZooKeeper entry. Allows re-exporting a partition to the same destination before the manifest expires.
- **Description**: Ignore existing partition export and overwrite the ZooKeeper entry. Allows re-exporting a partition to the same destination before the manifest expires. **IMPORTANT:** this is dangerous because it can lead to duplicated data, use it with caution.

#### `export_merge_tree_partition_max_retries` (Optional)

Expand All @@ -70,24 +82,35 @@ TO TABLE [destination_database.]destination_table
- `error` - Throw an error if the file already exists
- `overwrite` - Overwrite the file

### export_merge_tree_part_throw_on_pending_mutations
### `export_merge_tree_part_throw_on_pending_mutations` (Optional)

- **Type**: `bool`
- **Default**: `true`
- **Description**: If set to true, throws if pending mutations exists for a given part. Note that by default mutations are applied to all parts, which means that if a mutation in practice would only affetct part/partition x, all the other parts/partition will throw upon export. The exception is when the `IN PARTITION` clause was used in the mutation command. Note the `IN PARTITION` clause is not properly implemented for plain MergeTree tables.

### export_merge_tree_part_throw_on_pending_patch_parts
### `export_merge_tree_part_throw_on_pending_patch_parts` (Optional)

- **Type**: `bool`
- **Default**: `true`
- **Description**: If set to true, throws if pending patch parts exists for a given part. Note that by default mutations are applied to all parts, which means that if a mutation in practice would only affetct part/partition x, all the other parts/partition will throw upon export. The exception is when the `IN PARTITION` clause was used in the mutation command. Note the `IN PARTITION` clause is not properly implemented for plain MergeTree tables.

### export_merge_tree_part_filename_pattern
### `export_merge_tree_part_filename_pattern` (Optional)

- **Type**: `String`
- **Default**: `{part_name}_{checksum}`
- **Description**: Pattern for the filename of the exported merge tree part. The `part_name` and `checksum` are calculated and replaced on the fly. Additional macros are supported.

### `export_merge_tree_partition_task_timeout_seconds` (Optional)

- **Type**: `UInt64`
- **Default**: `3600`
- **Description**: The timeout is measured from the manifest's create_time. Set to 0 to disable the timeout.
When the timeout is exceeded the task transitions to KILLED (same terminal state as `KILL QUERY ... EXPORT PARTITION`), and `last_exception` is populated with a timeout reason.

Notes:
- Enforcement is best-effort: actual kill latency is bounded by one manifest-updater poll cycle (~30s) plus ZooKeeper watch propagation.
- Since both this timeout and `export_merge_tree_partition_manifest_ttl` are measured from `create_time`, keep `export_merge_tree_partition_manifest_ttl` greater than `export_merge_tree_partition_task_timeout_seconds` if you want the KILLED entry to remain visible in `system.replicated_partition_exports` after the timeout fires.

## Examples

### Basic Export to S3
Expand Down
5 changes: 5 additions & 0 deletions src/Common/FailPoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ static struct InitFiu
ONCE(write_file_operation_fail_on_read) \
REGULAR(slowdown_parallel_replicas_local_plan_read) \
ONCE(iceberg_writes_cleanup) \
ONCE(iceberg_writes_non_retry_cleanup) \
ONCE(iceberg_writes_post_publish_throw) \
ONCE(iceberg_export_after_commit_before_zk_completed) \
REGULAR(export_partition_commit_always_throw) \
ONCE(export_partition_status_change_throw) \
ONCE(backup_add_empty_memory_table) \
PAUSEABLE_ONCE(backup_pause_on_start) \
PAUSEABLE_ONCE(restore_pause_on_start) \
Expand Down
7 changes: 7 additions & 0 deletions src/Core/ServerSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1569,7 +1569,14 @@ The policy on how to perform a scheduling of CPU slots specified by `concurrent_
<skip_check_for_incorrect_settings>1</skip_check_for_incorrect_settings>
```
)", 0) \
<<<<<<< HEAD
DECLARE(Bool, enable_experimental_export_merge_tree_partition_feature, false, "Enable export replicated merge tree partition feature. It is experimental and not yet ready for production use.", 0)
=======
DECLARE(UInt64, object_storage_list_objects_cache_size, 500000000, "Maximum size of ObjectStorage list objects cache in bytes. Zero means disabled.", 0) \
DECLARE(UInt64, object_storage_list_objects_cache_max_entries, 1000, "Maximum size of ObjectStorage list objects cache in entries. Zero means disabled.", 0) \
DECLARE(UInt64, object_storage_list_objects_cache_ttl, 3600, "Time to live of records in ObjectStorage list objects cache in seconds. Zero means unlimited", 0) \
DECLARE(Bool, allow_experimental_export_merge_tree_partition, false, "Enable export replicated merge tree partition feature. It is experimental and not yet ready for production use.", 0) \
>>>>>>> 981a2d92cd0 (Merge pull request #1618 from Altinity/export_partition_iceberg)

/// Settings with a path are server settings with at least one layer of nesting that have a fixed structure (no lists, lists, enumerations, repetitions, ...).
#define LIST_OF_SERVER_SETTINGS_WITH_PATH(DECLARE, ALIAS) \
Expand Down
11 changes: 10 additions & 1 deletion src/Core/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7805,6 +7805,15 @@ Maximum number of retries for exporting a merge tree part in an export partition
DECLARE(UInt64, export_merge_tree_partition_manifest_ttl, 180, R"(
Determines how long the manifest will live in ZooKeeper. It prevents the same partition from being exported twice to the same destination.
This setting does not affect / delete in progress tasks. It'll only cleanup the completed ones.
)", 0) \
DECLARE(UInt64, export_merge_tree_partition_task_timeout_seconds, 3600, R"(
Maximum wall-clock duration (in seconds) an export partition task is allowed to remain in the PENDING state before it is auto-killed by the background cleanup loop.
The timeout is measured from the manifest's create_time. Set to 0 to disable the timeout.
When the timeout is exceeded the task transitions to KILLED (same terminal state as `KILL QUERY ... EXPORT PARTITION`), and `last_exception` is populated with a timeout reason.

Notes:
- Enforcement is best-effort: actual kill latency is bounded by one manifest-updater poll cycle (~30s) plus ZooKeeper watch propagation.
- Since both this timeout and `export_merge_tree_partition_manifest_ttl` are measured from `create_time`, keep `export_merge_tree_partition_manifest_ttl` greater than `export_merge_tree_partition_task_timeout_seconds` if you want the KILLED entry to remain visible in `system.replicated_partition_exports` after the timeout fires.
)", 0) \
DECLARE(MergeTreePartExportFileAlreadyExistsPolicy, export_merge_tree_part_file_already_exists_policy, MergeTreePartExportFileAlreadyExistsPolicy::skip, R"(
Possible values:
Expand All @@ -7830,7 +7839,7 @@ Throw an error if there are pending patch parts when exporting a merge tree part
Only lock a part when the task is already running. This might help with busy waiting where the scheduler locks a part, but the task ends in the pending list.
On the other hand, there is a chance once the task executes that part has already been locked by another replica and the task will simply early exit.
)", 0) \
DECLARE(Bool, export_merge_tree_partition_system_table_prefer_remote_information, true, R"(
DECLARE(Bool, export_merge_tree_partition_system_table_prefer_remote_information, false, R"(
Controls whether the system.replicated_partition_exports will prefer to query ZooKeeper to get the most up to date information or use the local information.
Querying ZooKeeper is expensive, and only available if the ZooKeeper feature flag MULTI_READ is enabled.
)", 0) \
Expand Down
5 changes: 5 additions & 0 deletions src/Core/SettingsChangesHistory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,11 @@ const VersionToSettingsChangesMap & getSettingsChangesHistory()
// {"object_storage_max_nodes", 0, 0, "Antalya: New setting"},
{"s3_propagate_credentials_to_other_storages", false, false, "New setting"},
{"export_merge_tree_part_filename_pattern", "", "{part_name}_{checksum}", "New setting"},
{"use_parquet_metadata_cache", false, true, "Enables cache of parquet file metadata."},
{"input_format_parquet_use_metadata_cache", true, false, "Obsolete. No-op"}, // https://github.com/Altinity/ClickHouse/pull/586
{"object_storage_remote_initiator_cluster", "", "", "New setting."},
{"iceberg_metadata_staleness_ms", 0, 0, "New setting allowing using cached metadata version at READ operations to prevent fetching from remote catalog"},
{"export_merge_tree_partition_task_timeout_seconds", 0, 3600, "New setting to control the timeout for export partition tasks."},
});
addSettingsChanges(settings_changes_history, "26.1",
{
Expand Down
6 changes: 3 additions & 3 deletions src/Interpreters/InterpreterKillQueryQuery.cpp
Loading