2026-05-29

Neue DuckDB-Iceberg-Funktionen in v1.5.3

Tom Ebergen, Thijs Bruineman

Trotz der Arbeit an den Features für DuckLake v1.0 und Quack arbeitet das DuckLabs-Team weiter an der DuckDB-Iceberg-Erweiterung. In diesem Blogbeitrag zeigen wir einige der Funktionen, die in DuckDB v1.5.3 verfügbar sind. Viele davon waren in unserem letzten Iceberg-Beitrag „Writes in DuckDB-Iceberg“ für ein späteres Release vorgesehen – Sie können diesen Text als „Teil 2“ dazu lesen.

Einstieg

Um die neuen DuckDB-Iceberg-Funktionen auszuprobieren, verbinden Sie sich mit Ihrem bevorzugten Iceberg REST Catalog. Es gibt viele Wege dahin: Schauen Sie auf die Seite Connecting to REST Catalogs mit Anleitungen für Kataloge wie Apache Polaris und Lakekeeper. Für Amazon S3 Tables siehe Connecting to S3 Tables. Ihr ATTACH-Befehl sieht in etwa so aus:

ATTACH '⟨warehouse_name⟩' AS my_datalake (
TYPE iceberg,
⟨other options⟩
);

Unterstützung für MERGE INTO

DuckDBs Statement MERGE INTO ist der empfohlene Weg für Upserts, wenn die Zieltabelle keinen Primärschlüssel hat – das gilt für alle Lakehouse-Formate. Ab v1.5.3 ist MERGE INTO voll gegen Iceberg-Tabellen unterstützt. Sie wenden ein Changeset in einem Statement an und entscheiden pro Zeile, ob eingefügt, aktualisiert oder gelöscht wird.

Nehmen wir diese Tabelle:

CREATE TABLE my_datalake.default.people (
id INTEGER,
name VARCHAR,
salary FLOAT
);
INSERT INTO my_datalake.default.people
VALUES (1, 'John', 92_000.0), (2, 'Anna', 100_000.0);
┌───────┬─────────┬──────────┐
│ id │ name │ salary │
│ int32 │ varchar │ float │
├───────┼─────────┼──────────┤
│ 1 │ John │ 92000.0 │
│ 2 │ Anna │ 100000.0 │
└───────┴─────────┴──────────┘

Wir führen ein Update mit zwei Records aus: Person 1 bekommt eine Gehaltserhöhung, und eine neue Person mit id 3 kommt hinzu.

MERGE INTO my_datalake.default.people AS target
USING (
FROM (VALUES
(1, 'John', 105_000.0),
(3, 'Sarah', 95_000.0)
) t(id, name, salary)
) AS upserts
ON (upserts.id = target.id)
WHEN MATCHED THEN UPDATE
WHEN NOT MATCHED THEN INSERT;

Die Abfrage des Ergebnisses liefert:

SELECT *
FROM my_datalake.default.people
ORDER BY id;
┌───────┬─────────┬──────────┐
│ id │ name │ salary │
│ int32 │ varchar │ float │
├───────┼─────────┼──────────┤
│ 1 │ John │ 105000.0 │
│ 2 │ Anna │ 100000.0 │
│ 3 │ Sarah │ 95000.0 │
└───────┴─────────┴──────────┘

Sie können matched- und unmatched-Zweige auch mit WHEN MATCHED THEN DELETE kombinieren, um ein Deleteset im selben Statement auszudrücken. Wie bei UPDATE und DELETE nutzt MERGE INTO Merge-on-Read-Semantik und schreibt positionale Deletes in die Iceberg-Tabelle.

Unterstützung für ALTER TABLE

In der Iceberg-Erweiterung von DuckDB v1.4 war fehlende Schemaevolution von Iceberg-Tabellen eine dokumentierte Einschränkung. In v1.5.3 wird ALTER TABLE gegen Iceberg-Tabellen unterstützt und deckt die gängigsten Schemaevolutions-Operationen ab.

-- Create the table
CREATE TABLE my_datalake.default.simple_table AS
FROM (VALUES
(1, 'Andy'),
(2, 'Bob'),
(3, 'Claire'),
(4, 'Mr. Duck')) t(col1, col2);
-- Rename the table
ALTER TABLE my_datalake.default.simple_table
RENAME TO renamed_table;
-- Add a column
ALTER TABLE my_datalake.default.renamed_table
ADD COLUMN col3 DOUBLE;
-- Rename a column
ALTER TABLE my_datalake.default.renamed_table
RENAME COLUMN col2 TO name;
-- Drop a column
ALTER TABLE my_datalake.default.renamed_table
DROP COLUMN col3;
-- Set the format-version
ALTER TABLE my_datalake.default.renamed_table
SET ('format-version' = 3);

Fragen wir die Tabelle nach den Schemaänderungen ab, erhalten wir:

SELECT *
FROM my_datalake.default.renamed_table
ORDER BY col1;
┌───────┬──────────┐
│ col1 │ name │
│ int32 │ varchar │
├───────┼──────────┤
│ 1 │ Andy │
│ 2 │ Bob │
│ 3 │ Claire │
│ 4 │ Mr. Duck │
└───────┴──────────┘

Im Hintergrund aktualisiert jedes ALTER TABLE die current-schema-id der Iceberg-Tabelle. Die Änderungen sehen andere Iceberg-fähige Engines beim nächsten Aufruf des Endpoints LoadTableInformation. Iceberg-Schemaevolution ist rein metadatenbasiert, Datenfiles werden nicht umgeschrieben.

Unterstützung für truncate und bucket

Die Iceberg-Spezifikation definiert mehrere Partition Transforms, die festlegen, wie Datenfiles auf der Platte liegen. In v1.5.3 unterstützt DuckDB-Iceberg das Anlegen, Einfügen und Aktualisieren von Tabellen mit den Partition Transforms bucket und truncate.

Der Transform bucket(N, col) hasht den Spaltenwert in N Buckets, nützlich für stabile Partitionierung auf einer hochkardinalen Spalte. truncate(W, col) gruppiert Zeilen nach den ersten W Zeichen (oder, bei numerischen Spalten, nach dem auf ein Vielfaches von W abgerundeten Wert), nützlich für präfixbasierte Partitionierung.

CREATE TABLE my_datalake.default.events (
event_id BIGINT,
user_id BIGINT,
country VARCHAR,
payload VARCHAR
)
PARTITIONED BY (bucket(16, user_id), truncate(2, country));
INSERT INTO my_datalake.default.events
VALUES
(1, 1001, 'United States', 'click'),
(2, 1002, 'United Kingdom', 'view'),
(3, 1003, 'Germany', 'click'),
(4, 1004, 'Netherlands', 'view');

Die entstandenen Datenfiles können Sie prüfen, um die Partitionierung zu verifizieren:

SELECT file_path, record_count
FROM iceberg_metadata(my_datalake.default.events)
WHERE content = 'EXISTING';

Updates und Deletes gegen bucket- und truncate-partitionierte Tabellen sind ebenfalls unterstützt, mit positionalen Deletes unter Merge-on-Read-Semantik.

Iceberg Schema Properties

Iceberg-Kataloge erlauben beliebige Key-Value-Properties auf Schema-(Namespace-)Ebene. Typisch werden sie für Ownership, Beschreibungen, Default-Storage-Locations oder andere Metadaten genutzt, die für alle Tabellen eines Schemas gelten.

Verwendung:

-- to set schema properties
CALL set_iceberg_schema_properties(my_datalake.default, {
'owner': 'analytics-team',
'description': 'Default analytics schema'
});
-- to read schema properties
SELECT * FROM iceberg_schema_properties(my_datalake.default);
┌─────────────┬──────────────────────────┐
│ key │ value │
│ varchar │ varchar │
├─────────────┼──────────────────────────┤
│ owner │ analytics-team │
│ description │ Default analytics schema │
└─────────────┴──────────────────────────┘
-- to remove schema properties
CALL remove_iceberg_schema_properties(
my_datalake.default,
['description']
);

Schema Properties werden über den Iceberg REST Catalog geschrieben, jede andere Iceberg-fähige Engine am selben Katalog sieht die Updates sofort. Der Rückgabewert ist die Zahl der verbleibenden Schema Properties.

V3-Unterstützung

Die Iceberg-v3-Spezifikation führt mehrere neue Funktionen ein, die DuckDB-Iceberg jetzt für Reads und Writes unterstützt:

Die größte praktische Änderung sind binäre Deletion Vectors. In v2-Tabellen schreibt DuckDB-Iceberg positionale Deletes als Parquet-Dateien; in v3-Tabellen wird dieselbe Information als deutlich kompakterer binärer Deletion Vector (Puffin-Datei) kodiert. DuckDB wählt das Format automatisch anhand der format-version der Tabelle.

Eine v3-Tabelle legen Sie an, indem Sie die Table Property format-version beim Anlegen setzen:

CREATE TABLE my_datalake.default.v3_table
WITH ('format-version' = 3) AS
FROM (VALUES
(1, {'kind': 'click', 'x': 10}::VARIANT, TIMESTAMP_NS '2026-05-20 12:00:00.123456789'),
(2, {'kind': 'view'}::VARIANT, TIMESTAMP_NS '2026-05-20 12:00:00.987654321')
) t(id, payload, event_time);
-- Deletes against a v3 table are written as binary deletion vectors
DELETE FROM my_datalake.default.v3_table
WHERE id = 1;
SELECT * FROM my_datalake.default.v3_table;
┌───────┬──────────────────┬───────────────────────────────┐
│ id │ payload │ event_time │
│ int32 │ variant │ timestamp_ns │
├───────┼──────────────────┼───────────────────────────────┤
│ 2 │ {"kind": "view"} │ 2026-05-20 12:00:00.987654321 │
└───────┴──────────────────┴───────────────────────────────┘

Ein Blick in die Metadaten bestätigt, dass der Delete als Deletion Vector geschrieben wurde, nicht als positional-delete-Parquet-Datei:

SELECT manifest_content, content, file_format
FROM iceberg_metadata(my_datalake.default.v3_table);
┌──────────────────┬──────────────────┬─────────────┐
│ manifest_content │ content │ file_format │
│ varchar │ varchar │ varchar │
├──────────────────┼──────────────────┼─────────────┤
│ DATA │ EXISTING │ parquet │
│ DELETE │ POSITION_DELETES │ puffin │
└──────────────────┴──────────────────┴─────────────┘

Die Typen Geography und Unknown sind in DuckDB-Iceberg noch nicht unterstützt; wir planen sie für DuckDB v2.0.0.

Fazit und Ausblick

Mit diesen Funktionen hat DuckDB-Iceberg viele der Lücken aus dem vorherigen Blogbeitrag geschlossen: partitionierte Writes, Schemaevolution, MERGE INTO und viele Iceberg-v3-Funktionen sind jetzt da. Es kommt noch mehr, und wie immer: Wenn Sie eine bestimmte Funktion priorisiert sehen möchten, schreiben Sie uns im DuckDB-Iceberg-GitHub-Repository oder melden Sie sich bei unseren Ingenieurinnen und Ingenieuren.