Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 12 additions & 2 deletions docs/reference/engines/table-engines/integrations/iceberg.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,19 @@ ClickHouse supports reading Iceberg tables that use the following deletion metho

- [Position deletes](https://iceberg.apache.org/spec/#position-delete-files)
- [Equality deletes](https://iceberg.apache.org/spec/#equality-delete-files) (supported from version 25.8+)
- [Deletion vectors](https://iceberg.apache.org/spec/#deletion-vectors) (Iceberg v3, read-only), stored either in Puffin files or in Delta-style `deletion_vector_*.bin` files using the same `deletion-vector-v1` envelope at the manifest `content_offset` / `content_size_in_bytes`

The following deletion method is **not supported**:
- [Deletion vectors](https://iceberg.apache.org/spec/#deletion-vectors) (introduced in v3)
The following limitations apply to deletion vectors:

- Only `deletion-vector-v1` blobs are supported (Puffin container or Delta `.bin` slice)
- Data files must be in Parquet format
- Column-scoped deletion vectors (user column ids in puffin `fields`) are not supported. Writers may set `fields` to `[]` or to the Iceberg reserved `_pos` id (`2147483645`) for file-scoped deletion vectors.
- Writing deletion vectors is not supported
- `DELETE` / `UPDATE` mutations on Iceberg format version 3+ tables are rejected (writers must not add position-delete files)

Parsed deletion vectors can be cached in memory when `use_puffin_files_cache` is enabled and the deletion-vector object has a non-empty `etag`. Empty deletion vectors are cached as well, so repeated reads do not re-fetch the object. Parsed footers for coalesced multi-DV Puffin files are memoized with that cache (same identity: storage, path, `etag`) so slices share one footer parse; Delta `.bin` files have no Puffin footer and skip that memo. The memo shares `puffin_files_cache_size` / max-entry limits and is dropped when the cache is disabled (`puffin_files_cache_size=0`) or cleared. The cache can be cleared with `SYSTEM DROP PUFFIN FILES CACHE`.

For [`icebergCluster`](/sql-reference/table-functions/icebergCluster.md) (and `object_storage_cluster`), the initiator loads and materializes each data file's deletion vector while distributing tasks, then sends the resulting row bitmap to workers with the task. Workers apply the bitmap; they do not re-read the Puffin or `.bin` object for that path. On wide v3 tables this can make the initiator a serialization point for deletion-vector I/O and decode.

### Basic usage {#basic-usage}
```sql
Expand Down
10 changes: 7 additions & 3 deletions docs/reference/formats/Puffin/Puffin.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
---
description: 'Documentation for the Puffin format'
input_format: true
output_format: false
keywords: ['Puffin']
sidebar_label: 'Puffin'
sidebar_position: 1
slug: /interfaces/formats/Puffin
title: 'Puffin'
doc_type: 'reference'
Expand All @@ -10,16 +14,16 @@ doc_type: 'reference'

Input format for reading [Apache Iceberg Puffin](https://iceberg.apache.org/puffin-spec/) files.

The format exposes deleted row positions from `deletion-vector-v1` blobs. It is the only supported blob type: a file containing any other blob type (for example `apache-datasketches-theta-v1`) is rejected.
The format exposes deleted row positions from `deletion-vector-v1` blobs. Other blob types (for example `apache-datasketches-theta-v1`) are skipped.
If a puffin file contains multiple `deletion-vector-v1` blobs, the format outputs one row per such blob.

Fixed output columns:
- `referenced_data_file` (`String`) - location of the data file the deletion vector applies to (`referenced-data-file` blob property)
- `deleted_rows` (`Array(UInt64)`) - 64-bit row positions deleted according to the deletion vector roaring bitmap

Deletion vectors whose declared `cardinality` exceeds an absolute materialization ceiling are rejected when `deleted_rows` is requested. Footer `deletion-vector-v1` properties (including that `cardinality` parses as an unsigned integer) are always validated. Selecting only `referenced_data_file` skips on-disk payload I/O and therefore also skips envelope, CRC, roaring deserialize, and the materialization ceiling — intentionally, so a path-only projection does not read up to the blob-size cap.
Deletion vectors whose declared `cardinality` exceeds an absolute materialization ceiling are rejected when `deleted_rows` is requested, **before** envelope peek or full blob allocation (same fail-closed order as the Iceberg deletion-vector reader). Footer `deletion-vector-v1` properties are always validated: `cardinality` must parse as an unsigned integer, `snapshot-id` / `sequence-number` must be `-1`, and `fields` must be either empty or the singleton Iceberg reserved `_pos` id (`2147483645`) that Spark writes for file-scoped DVs — other `fields` lists (column-scoped DVs) are rejected. Selecting only `referenced_data_file` skips on-disk payload I/O and therefore also skips envelope, CRC, roaring deserialize, and the materialization ceiling — intentionally, so a path-only projection does not read up to the blob-size cap.

On-disk `deletion-vector-v1` blob length is bounded by an absolute ceiling (aligned with Iceberg's 2 GiB content-size check). When `deleted_rows` is requested, the reader peeks the envelope header (combined length and magic) before allocating the full payload; CRC is verified after the bounded read.
On-disk `deletion-vector-v1` blob length is bounded by an absolute ceiling (aligned with Iceberg's 2 GiB content-size check). When `deleted_rows` is requested and cardinality is within the materialization ceiling, the reader peeks the envelope header (combined length and magic) before allocating the full payload; CRC is verified after the bounded read.

LZ4-compressed and uncompressed puffin footers are supported. Footer payload size (and declared LZ4 content size) is bounded by a compression ratio where applicable and an absolute ceiling; oversized footers are rejected before allocation.

Expand Down
8 changes: 5 additions & 3 deletions docs/reference/formats/Puffin/PuffinMetadata.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
---
description: 'Documentation for the PuffinMetadata format'
input_format: true
output_format: false
keywords: ['PuffinMetadata']
sidebar_label: 'PuffinMetadata'
sidebar_position: 2
slug: /interfaces/formats/PuffinMetadata
title: 'PuffinMetadata'
doc_type: 'reference'
Expand All @@ -9,9 +13,7 @@ doc_type: 'reference'
## Description {#description}

Special input format for reading [Apache Iceberg Puffin](https://iceberg.apache.org/puffin-spec/) file footer metadata.
It outputs one row per blob entry from the footer `BlobMetadata` list.

`deletion-vector-v1` is the only supported blob type: a file containing any other blob type (for example `apache-datasketches-theta-v1`) is rejected.
It outputs one row per blob entry from the footer `BlobMetadata` list, including non-deletion-vector types (for example `apache-datasketches-theta-v1`). Full deletion-vector property validation applies only to `deletion-vector-v1` entries.

Fixed output columns:
- `blob_type` (`String`) - blob type, for example `deletion-vector-v1`
Expand Down
23 changes: 18 additions & 5 deletions docs/reference/functions/table-functions/iceberg.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,23 @@ ClickHouse supports time travel for Iceberg tables, allowing you to query histor

## Processing of tables with deleted rows {#deleted-rows}

Currently, only Iceberg tables with [position deletes](https://iceberg.apache.org/spec/#position-delete-files) are supported.
ClickHouse supports reading Iceberg tables that use the following deletion methods:

The following deletion methods are **not supported**:
- [Equality deletes](https://iceberg.apache.org/spec/#equality-delete-files)
- [Deletion vectors](https://iceberg.apache.org/spec/#deletion-vectors) (introduced in v3)
- [Position deletes](https://iceberg.apache.org/spec/#position-delete-files)
- [Equality deletes](https://iceberg.apache.org/spec/#equality-delete-files) (supported from version 25.8+)
- [Deletion vectors](https://iceberg.apache.org/spec/#deletion-vectors) (Iceberg v3, read-only), stored either in Puffin files or in Delta-style `deletion_vector_*.bin` files using the same `deletion-vector-v1` envelope at the manifest `content_offset` / `content_size_in_bytes`

The following limitations apply to deletion vectors:

- Only `deletion-vector-v1` blobs are supported (Puffin container or Delta `.bin` slice)
- Data files must be in Parquet format
- Column-scoped deletion vectors (user column ids in puffin `fields`) are not supported. Writers may set `fields` to `[]` or to the Iceberg reserved `_pos` id (`2147483645`) for file-scoped deletion vectors.
- Writing deletion vectors is not supported
- `DELETE` / `UPDATE` mutations on Iceberg format version 3+ tables are rejected (writers must not add position-delete files)

Parsed deletion vectors can be cached in memory when `use_puffin_files_cache` is enabled and the deletion-vector object has a non-empty `etag`. Empty deletion vectors are cached as well, so repeated reads do not re-fetch the object. Parsed footers for coalesced multi-DV Puffin files are memoized with that cache (same identity: storage, path, `etag`) so slices share one footer parse; Delta `.bin` files have no Puffin footer and skip that memo. The memo shares `puffin_files_cache_size` / max-entry limits and is dropped when the cache is disabled (`puffin_files_cache_size=0`) or cleared. The cache can be cleared with `SYSTEM DROP PUFFIN FILES CACHE`.

For [`icebergCluster`](/sql-reference/table-functions/icebergCluster.md) (and `object_storage_cluster`), the initiator loads and materializes each data file's deletion vector while distributing tasks, then sends the resulting row bitmap to workers with the task. Workers apply the bitmap; they do not re-read the Puffin or `.bin` object for that path. On wide v3 tables this can make the initiator a serialization point for deletion-vector I/O and decode.

### Basic usage {#basic-usage}

Expand Down Expand Up @@ -386,8 +398,9 @@ y: 993

### DELETE {#iceberg-writes-delete}

Deleting extra rows in the merge-on-read format is also supported in ClickHouse.
Deleting extra rows in the merge-on-read format is also supported in ClickHouse for Iceberg format version 2.
This query will create a new snapshot with position delete files.
Mutations on format version 3+ tables are rejected until ClickHouse can write deletion vectors (Iceberg v3 writers must not add new position-delete files).

### Example {#example-iceberg-writes-delete}

Expand Down
6 changes: 6 additions & 0 deletions docs/reference/functions/table-functions/icebergCluster.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ This is an extension to the [iceberg](/reference/functions/table-functions/icebe

Allows processing files from Apache [Iceberg](https://iceberg.apache.org/) in parallel from many nodes in a specified cluster. On initiator it creates a connection to all nodes in the cluster and dispatches each file dynamically. On the worker node it asks the initiator about the next task to process and processes it. This is repeated until all tasks are finished.

## Deletion vectors on cluster reads {#deletion-vectors-cluster}

Iceberg v3 [deletion vectors](https://iceberg.apache.org/spec/#deletion-vectors) are loaded on the **initiator** while it distributes tasks: for each data file the initiator reads the Puffin or Delta `.bin` object, validates the `deletion-vector-v1` envelope, materializes deleted row positions, and attaches the bitmap to the task sent to workers. Workers apply that bitmap when reading Parquet; they do not fetch or parse the deletion-vector file again for that path. See [Processing of tables with deleted rows](/sql-reference/table-functions/iceberg.md#deleted-rows) for format limits and caching.

On wide tables with many deletion vectors, initiator-side decode and per-task bitmap serialization can become a bottleneck even when Parquet reads are well parallelized across the cluster.

## Syntax {#syntax}

```sql
Expand Down
4 changes: 4 additions & 0 deletions docs/reference/statements/system.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ Clears the in-memory cache of parsed Paimon metadata files (manifest lists and m

Clears the cache of preprocessed constant polygons used by the function [`pointInPolygon`](/reference/functions/regular-functions/geo/coordinates#pointinpolygon). The configured size limit (the `point_in_polygon_cache_size` server setting) is left unchanged, so the cache keeps accepting entries afterwards. To disable the cache instead, set `point_in_polygon_cache_size` to `0`.

## SYSTEM DROP PUFFIN FILES CACHE {#drop-puffin-files-cache}

Clears the Puffin files cache used for parsed Iceberg puffin file content such as deletion vectors.

## SYSTEM CLEAR|DROP TEXT INDEX CACHES {#drop-text-index-caches}

Clears the text index's tokens, header and postings caches.
Expand Down
15 changes: 15 additions & 0 deletions programs/local/LocalServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ namespace ServerSetting
extern const ServerSettingsUInt64 parquet_metadata_cache_size;
extern const ServerSettingsUInt64 parquet_metadata_cache_max_entries;
extern const ServerSettingsDouble parquet_metadata_cache_size_ratio;
extern const ServerSettingsString puffin_files_cache_policy;
extern const ServerSettingsUInt64 puffin_files_cache_size;
extern const ServerSettingsUInt64 puffin_files_cache_max_entries;
extern const ServerSettingsDouble puffin_files_cache_size_ratio;
extern const ServerSettingsUInt64 max_active_parts_loading_thread_pool_size;
extern const ServerSettingsUInt64 max_io_thread_pool_free_size;
extern const ServerSettingsUInt64 max_io_thread_pool_size;
Expand Down Expand Up @@ -1635,6 +1639,17 @@ void LocalServer::processConfig()
global_context->setParquetMetadataCache(parquet_metadata_cache_policy, parquet_metadata_cache_size, parquet_metadata_cache_max_entries, parquet_metadata_cache_size_ratio);
#endif

String puffin_files_cache_policy = server_settings[ServerSetting::puffin_files_cache_policy];
size_t puffin_files_cache_size = server_settings[ServerSetting::puffin_files_cache_size];
size_t puffin_files_cache_max_entries = server_settings[ServerSetting::puffin_files_cache_max_entries];
double puffin_files_cache_size_ratio = server_settings[ServerSetting::puffin_files_cache_size_ratio];
if (puffin_files_cache_size > max_cache_size)
{
puffin_files_cache_size = max_cache_size;
LOG_INFO(log, "Lowered Puffin files cache size to {} because the system has limited RAM", formatReadableSizeWithBinarySuffix(puffin_files_cache_size));
}
global_context->setPuffinFilesCache(puffin_files_cache_policy, puffin_files_cache_size, puffin_files_cache_max_entries, puffin_files_cache_size_ratio);

Names allowed_disks_table_engines;
splitInto<','>(allowed_disks_table_engines, server_settings[ServerSetting::allowed_disks_for_table_engines].value);
global_context->setAllowedDisksForTableEngines(std::unordered_set<String>(allowed_disks_table_engines.begin(), allowed_disks_table_engines.end()));
Expand Down
15 changes: 15 additions & 0 deletions programs/server/Server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,10 @@ namespace ServerSetting
extern const ServerSettingsUInt64 parquet_metadata_cache_size;
extern const ServerSettingsUInt64 parquet_metadata_cache_max_entries;
extern const ServerSettingsDouble parquet_metadata_cache_size_ratio;
extern const ServerSettingsString puffin_files_cache_policy;
extern const ServerSettingsUInt64 puffin_files_cache_size;
extern const ServerSettingsUInt64 puffin_files_cache_max_entries;
extern const ServerSettingsDouble puffin_files_cache_size_ratio;
extern const ServerSettingsUInt64 io_thread_pool_queue_size;
extern const ServerSettingsBool jemalloc_enable_global_profiler;
extern const ServerSettingsBool jemalloc_collect_global_profile_samples_in_trace_log;
Expand Down Expand Up @@ -2586,6 +2590,16 @@ try
}
global_context->setParquetMetadataCache(parquet_metadata_cache_policy, parquet_metadata_cache_size, parquet_metadata_cache_max_entries, parquet_metadata_cache_size_ratio);
#endif
String puffin_files_cache_policy = server_settings[ServerSetting::puffin_files_cache_policy];
size_t puffin_files_cache_size = server_settings[ServerSetting::puffin_files_cache_size];
size_t puffin_files_cache_max_entries = server_settings[ServerSetting::puffin_files_cache_max_entries];
double puffin_files_cache_size_ratio = server_settings[ServerSetting::puffin_files_cache_size_ratio];
if (puffin_files_cache_size > max_cache_size)
{
puffin_files_cache_size = max_cache_size;
LOG_INFO(log, "Lowered Puffin files cache size to {} because the system has limited RAM", formatReadableSizeWithBinarySuffix(puffin_files_cache_size));
}
global_context->setPuffinFilesCache(puffin_files_cache_policy, puffin_files_cache_size, puffin_files_cache_max_entries, puffin_files_cache_size_ratio);

Names allowed_disks_table_engines;
splitInto<','>(allowed_disks_table_engines, server_settings[ServerSetting::allowed_disks_for_table_engines].value);
Expand Down Expand Up @@ -3104,6 +3118,7 @@ try
#if USE_PARQUET
global_context->updateParquetMetadataCacheConfiguration(config(), max_cache_size_in_bytes);
#endif
global_context->updatePuffinFilesCacheConfiguration(config(), max_cache_size_in_bytes);
}

#if USE_SSL
Expand Down
1 change: 1 addition & 0 deletions src/Access/Common/AccessType.h
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ enum class AccessType : uint8_t
M(SYSTEM_DROP_AVRO_SCHEMA_CACHE, "SYSTEM CLEAR AVRO SCHEMA CACHE, SYSTEM DROP AVRO SCHEMA CACHE, DROP AVRO SCHEMA CACHE", GLOBAL, SYSTEM_DROP_CACHE) \
M(SYSTEM_DROP_PARQUET_METADATA_CACHE, "SYSTEM DROP PARQUET_METADATA_CACHE", GLOBAL, SYSTEM_DROP_CACHE) \
M(SYSTEM_DROP_POINT_IN_POLYGON_CACHE, "SYSTEM CLEAR POINT IN POLYGON CACHE, SYSTEM DROP POINT IN POLYGON CACHE, DROP POINT IN POLYGON CACHE", GLOBAL, SYSTEM_DROP_CACHE) \
M(SYSTEM_DROP_PUFFIN_FILES_CACHE, "SYSTEM DROP PUFFIN_FILES_CACHE", GLOBAL, SYSTEM_DROP_CACHE) \
M(SYSTEM_PREWARM_PRIMARY_INDEX_CACHE, "SYSTEM PREWARM PRIMARY INDEX, PREWARM PRIMARY INDEX CACHE, PREWARM PRIMARY INDEX", GLOBAL, SYSTEM_DROP_CACHE) \
M(SYSTEM_DROP_PRIMARY_INDEX_CACHE, "SYSTEM CLEAR PRIMARY INDEX CACHE, SYSTEM DROP PRIMARY INDEX, DROP PRIMARY INDEX CACHE, DROP PRIMARY INDEX", GLOBAL, SYSTEM_DROP_CACHE) \
M(SYSTEM_DROP_UNCOMPRESSED_CACHE, "SYSTEM CLEAR UNCOMPRESSED CACHE, SYSTEM DROP UNCOMPRESSED, DROP UNCOMPRESSED CACHE, DROP UNCOMPRESSED", GLOBAL, SYSTEM_DROP_CACHE) \
Expand Down
Loading
Loading