2025-11-28
Writes in DuckDB-Iceberg
Tom Ebergen
In den letzten Monaten hat das DuckLabs-Team intensiv an der DuckDB-Iceberg-Erweiterung gearbeitet, mit voller Leseunterstützung und erster Schreibunterstützung in v1.4.0. Heute freuen wir uns, bekannt zu geben, dass Delete- und Update-Unterstützung für Iceberg-v2-Tabellen in v1.4.2 verfügbar ist!
Das offene Iceberg-Tabellenformat ist in den letzten zwei Jahren extrem populär geworden, und viele Datenbanken haben Unterstützung für das ursprünglich bei Netflix entwickelte offene Tabellenformat angekündigt. Im vergangenen Jahr hat das DuckDB-Team Iceberg-Integration zur Priorität gemacht, und heute freuen wir uns über einen weiteren Schritt in diese Richtung. In diesem Blogbeitrag beschreiben wir den aktuellen Funktionsumfang von DuckDB-Iceberg in DuckDB v1.4.2.
Einstieg
Um die neuen DuckDB-Iceberg-Features auszuprobieren, müssen Sie sich mit Ihrem bevorzugten Iceberg REST Catalog verbinden. Es gibt viele Wege, sich mit einem Iceberg REST Catalog zu verbinden: Schauen Sie sich Connecting to REST Catalogs für Catalogs wie Apache Polaris oder Lakekeeper an und die Seite Connecting to S3 Tables, wenn Sie sich mit Amazon S3 Tables verbinden möchten.
ATTACH '⟨warehouse_name⟩' AS iceberg_catalog ( TYPE iceberg, ⟨other options⟩);Inserts, Deletes und Updates
Unterstützung für das Anlegen von Tabellen und das Einfügen in Tabellen gab es bereits in DuckDB v1.4.0: Sie können die Standard-DuckDB-SQL-Syntax nutzen, um Daten in Ihre Iceberg-Tabelle einzufügen.
CREATE TABLE iceberg_catalog.default.simple_table ( col1 INTEGER, col2 VARCHAR);INSERT INTO iceberg_catalog.default.simple_table VALUES (1, 'hello'), (2, 'world'), (3, 'duckdb is great');Sie können auch jede DuckDB-Table-Scan-Funktion nutzen, um Daten in eine Iceberg-Tabelle einzufügen:
INSERT INTO iceberg_catalog.default.more_data SELECT * FROM read_parquet('path/to/parquet');Ab v1.4.2 funktioniert die Standard-SQL-Syntax auch für Deletes und Updates:
DELETE FROM iceberg_catalog.default.simple_tableWHERE col1 = 2;
UPDATE iceberg_catalog.default.simple_tableSET col1 = col1 + 5WHERE col1 = 1;
SELECT *FROM iceberg_catalog.default.simple_table;┌───────┬─────────────────┐│ col1 │ col2 ││ int32 │ varchar │├───────┼─────────────────┤│ 3 │ duckdb is great ││ 6 │ hello │└───────┴─────────────────┘Die Iceberg-Schreibunterstützung hat derzeit zwei Einschränkungen:
Die Update-Unterstützung ist auf Tabellen beschränkt, die weder partitioniert noch sortiert sind. Der Versuch, Update-, Insert- oder Delete-Operationen auf partitionierten oder sortierten Tabellen mit DuckDB-Iceberg auszuführen, führt zu einem Fehler.
DuckDB-Iceberg schreibt nur Position Deletes für
DELETE- undUPDATE-Anweisungen. Copy-on-Write wird noch nicht unterstützt.
Funktionen für Tabelleneigenschaften
Derzeit unterstützt DuckDB-Iceberg nur Merge-on-Read-Semantik. Innerhalb der Iceberg-Tabellenmetadaten können Tabelleneigenschaften beschreiben, welche Form von Deletes oder Updates erlaubt ist. DuckDB-Iceberg respektiert die Tabelleneigenschaften write.update.mode und write.delete.mode für Updates und Deletes. Wenn eine Tabelle diese Eigenschaften hat und sie nicht merge-on-read sind, wirft DuckDB einen Fehler und das UPDATE oder DELETE wird nicht committed. Version v1.4.2 führt drei neue Funktionen ein, um Tabelleneigenschaften einer Iceberg-Tabelle hinzuzufügen, zu entfernen und anzuzeigen:
set_iceberg_table_propertiesiceberg_table_propertiesremove_iceberg_table_properties
Sie können sie so nutzen:
-- to set table propertiesCALL set_iceberg_table_properties(iceberg_catalog.default.simple_table, { 'write.update.mode': 'merge-on-read', 'write.file.size': '100000kb'});-- to read table propertiesSELECT * FROM iceberg_table_properties(iceberg_catalog.default.simple_table);┌───────────────────┬───────────────┐│ key │ value ││ varchar │ varchar │├───────────────────┼───────────────┤│ write.update.mode │ merge-on-read ││ write.file.size │ 100000kb │└───────────────────┴───────────────┘-- to remove table propertiesCALL remove_iceberg_table_properties( iceberg_catalog.default.simple_table, ['some.other.property']);Iceberg-Tabellenmetadaten
DuckDB-Iceberg erlaubt Ihnen auch, die Metadaten Ihrer Iceberg-Tabellen mit den Funktionen iceberg_metadata() und iceberg_snapshots() anzusehen.
SELECT * FROM iceberg_metadata(iceberg_catalog.default.table_1);┌──────────────────────┬──────────────────────┬──────────────────┬─────────┬──────────────────┬─────────────────────────────────────────────────────────────┬─────────────┬──────────────┐│ manifest_path │ manifest_sequence_… │ manifest_content │ status │ content │ file_path │ file_format │ record_count ││ varchar │ int64 │ varchar │ varchar │ varchar │ varchar │ varchar │ int64 │├──────────────────────┼──────────────────────┼──────────────────┼─────────┼──────────────────┼─────────────────────────────────────────────────────────────┼─────────────┼──────────────┤│ s3://warehouse/def… │ 1 │ DATA │ ADDED │ EXISTING │ s3://<storage_location>/simple_table/data/019a6ecc-9e9e-7… │ parquet │ 3 ││ s3://warehouse/def… │ 2 │ DELETE │ ADDED │ POSITION_DELETES │ s3://<storage_location>/simple_table/data/d65b1db8-9fa8-4… │ parquet │ 1 ││ s3://warehouse/def… │ 3 │ DELETE │ ADDED │ POSITION_DELETES │ s3://<storage_location>/simple_table/data/8d1b92dc-5f6e-4… │ parquet │ 1 ││ s3://warehouse/def… │ 3 │ DATA │ ADDED │ EXISTING │ s3://<storage_location>/simple_table/data/019a6ecf-5261-7… │ parquet │ 1 │└──────────────────────┴──────────────────────┴──────────────────┴─────────┴──────────────────┴─────────────────────────────────────────────────────────────┴─────────────┴──────────────┘SELECT * FROM iceberg_snapshots(iceberg_catalog.default.simple_table);┌─────────────────┬─────────────────────┬─────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐│ sequence_number │ snapshot_id │ timestamp_ms │ manifest_list ││ uint64 │ uint64 │ timestamp │ varchar │├─────────────────┼─────────────────────┼─────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────┤│ 1 │ 1790528822676766947 │ 2025-11-10 17:24:55.075 │ s3://<storage_location>/simple_table/data/snap-1790528822676766947-f09658c4-ca52-4305-943f-6a8073529fef.avro ││ 2 │ 6333537230056014119 │ 2025-11-10 17:27:35.602 │ s3://<storage_location>/simple_table/data/snap-6333537230056014119-316d09bc-549d-46bc-ae13-a9fab5cbf09b.avro ││ 3 │ 7452040077415501383 │ 2025-11-10 17:27:52.169 │ s3://<storage_location>/simple_table/data/snap-7452040077415501383-93dee94e-9ec1-45fa-aec2-13ef434e50eb.avro │└─────────────────┴─────────────────────┴─────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────┘Time Travel
Time Travel ist ebenfalls über Snapshot-IDs oder Zeitstempel mit der Syntax AT (VERSION => ...) oder AT (TIMESTAMP => ...) möglich.
-- via snapshot idSELECT *FROM iceberg_catalog.default.simple_table AT ( VERSION => ⟨snapshot_id⟩);┌───────┬─────────────────┐│ col1 │ col2 ││ int32 │ varchar │├───────┼─────────────────┤│ 1 │ hello ││ 3 │ duckdb is great │└───────┴─────────────────┘-- via timestampSELECT *FROM iceberg_catalog.default.simple_table AT ( TIMESTAMP => '2025-11-10 17:27:45.602');┌───────┬─────────────────┐│ col1 │ col2 ││ int32 │ varchar │├───────┼─────────────────┤│ 1 │ hello ││ 3 │ duckdb is great │└───────┴─────────────────┘Requests an den Iceberg REST Catalog ansehen
Sie sind vielleicht auch neugierig, welche Requests DuckDB an den Iceberg REST Catalog schickt.
Aktivieren Sie dazu HTTP-Logging, führen Sie Ihre Workload aus und selektieren Sie dann aus den HTTP-Logs.
CALL enable_logging('HTTP');SELECT * FROM iceberg_catalog.default.simple_table;SELECT request.type, request.url, response.statusFROM duckdb_logs_parsed('HTTP');┌─────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────┬────────────────────┐│ type │ url │ status ││ varchar │ varchar │ varchar │├─────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────┼────────────────────┤│ GET │ https://<catalog_endpoint>/iceberg/v1/<warehouse>/iceberg-testing/namespaces/default │ NULL ││ HEAD │ https://<catalog_endpoint>/iceberg/v1/<warehouse>/iceberg-testing/namespaces/default/tables/simple_table │ NULL ││ GET │ https://<catalog_endpoint>/iceberg/v1/<warehouse>/iceberg-testing/namespaces/default/tables/simple_table │ NULL ││ GET │ https://<storage_endpoint>/data/snap-5943683398986255948-c2217dde-6036-4e07-88f2-… │ OK_200 ││ GET │ https://<storage_endpoint>/data/f8c95b93-7b6b-4a24-8557-b98b553723d4-m0.avro │ OK_200 ││ GET │ https://<storage_endpoint>/data/214a7988-da39-4dac-aa3a-4a73d3ead405-m0.avro │ OK_200 ││ GET │ https://<storage_endpoint>/data/019a7244-c6e8-7bc9-9dd4-7249fcb04959.parquet │ PartialContent_206 ││ GET │ https://<storage_endpoint>/data/019a7244-fcb5-7308-96ec-1c9e32509eab.parquet │ PartialContent_206 ││ GET │ https://<storage_endpoint>/data/7f14bb06-f57a-42b4-ba7f-053a65152759-m0.avro │ OK_200 ││ GET │ https://<storage_endpoint>/data/71f8b43d-51e7-40e7-be88-e8d869836ecd-deletes.parq… │ PartialContent_206 ││ GET │ https://<storage_endpoint>/data/64f6c6e2-2f54-470e-b990-b201bc615042-m0.avro │ OK_200 ││ GET │ https://<storage_endpoint>/data/4e54afed-6dd8-4ba0-88fb-16f972ac1d91-deletes.parq… │ PartialContent_206 │├─────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────┴────────────────────┤│ 12 rows 3 columns │└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘Hier sehen wir Aufrufe an den Iceberg REST Catalog, gefolgt von Aufrufen an den Storage-Endpunkt. Die ersten drei Aufrufe an den Iceberg REST Catalog prüfen, ob das Schema noch existiert, und holen die neueste metadata.json der DuckDB-Iceberg-Tabelle. Als Nächstes werden Manifest-Liste, Manifest-Dateien und schließlich die Dateien mit Daten und Deletes abgefragt. Die Daten- und Delete-Dateien werden lokal in einem Cache gehalten, um Folgeläufe zu beschleunigen.
Transaktionen
DuckDB ist eine ACID-konforme Datenbank, die Transaktionen unterstützt. Die Arbeit an DuckDB-Iceberg wurde mit dem im Hinterkopf gemacht. Innerhalb einer Transaktion gelten für Iceberg-Tabellen die folgenden Bedingungen.
- Beim ersten Lesen einer Tabelle in einer Transaktion wird ihre Snapshot-Information in der Transaktion gespeichert und bleibt innerhalb dieser Transaktion konsistent.
- Updates, Inserts und Deletes werden erst beim Commit der Transaktion in eine Iceberg-Tabelle geschrieben (also
COMMIT);
Punkt 1 ist wichtig für die Leseperformance. Wenn Sie Analytics auf einer Iceberg-Tabelle machen und nicht jedes Mal die neueste Version der Tabelle brauchen, verhindert das Ausführen Ihrer Analytics in einer Transaktion, dass für jede Abfrage die neueste Version geholt wird.
-- truncate the logsCALL truncate_duckdb_logs();CALL enable_logging('HTTP')BEGIN;-- first read gets latest snapshot informationSELECT * FROM iceberg_catalog.default.simple_table;-- subsequent read reads from local cached dataSELECT * FROM iceberg_catalog.default.simple_table;-- get logsSELECT request.type, request.url, response.statusFROM duckdb_logs_parsed('HTTP');┌─────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────┬────────────────────┐│ type │ url │ status ││ varchar │ varchar │ varchar │├─────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────┼────────────────────┤│ GET │ https://<catalog_endpoint>/iceberg/v1/<warehouse>/iceberg-testing/namespaces/default │ NULL ││ HEAD │ https://<catalog_endpoint>/iceberg/v1/<warehouse>/iceberg-testing/namespaces/default/tables/simple_table │ NULL ││ GET │ https://<catalog_endpoint>/iceberg/v1/<warehouse>/iceberg-testing/namespaces/default/tables/simple_table │ NULL ││ GET │ https://<storage_endpoint>/data/snap-5943683398986255948-c2217dde-6036-4e07-88f2-1… │ OK_200 ││ GET │ https://<storage_endpoint>/data/f8c95b93-7b6b-4a24-8557-b98b553723d4-m0.avro │ OK_200 ││ GET │ https://<storage_endpoint>/data/214a7988-da39-4dac-aa3a-4a73d3ead405-m0.avro │ OK_200 ││ GET │ https://<storage_endpoint>/data/019a7244-c6e8-7bc9-9dd4-7249fcb04959.parquet │ PartialContent_206 ││ GET │ https://<storage_endpoint>/data/019a7244-fcb5-7308-96ec-1c9e32509eab.parquet │ PartialContent_206 ││ GET │ https://<storage_endpoint>/data/7f14bb06-f57a-42b4-ba7f-053a65152759-m0.avro │ OK_200 ││ GET │ https://<storage_endpoint>/data/71f8b43d-51e7-40e7-be88-e8d869836ecd-deletes.parquet │ PartialContent_206 ││ GET │ https://<storage_endpoint>/data/64f6c6e2-2f54-470e-b990-b201bc615042-m0.avro │ OK_200 ││ GET │ https://<storage_endpoint>/data/4e54afed-6dd8-4ba0-88fb-16f972ac1d91-deletes.parquet │ PartialContent_206 │├─────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────┴────────────────────┤│ 12 rows 3 columns │└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘Hier sehen wir alle Requests aus dem vorherigen Abschnitt. Jetzt sind wir aber in einer Transaktion, das heißt beim zweiten Lesen von iceberg_catalog.default.simple_table müssen wir den REST Catalog nicht nach Tabellen-Updates fragen. DuckDB-Iceberg macht also beim zweiten Lesen einer Tabelle keine Extra-Requests, was die Performance deutlich verbessert.
Fazit und Ausblick
Mit diesen Features hat DuckDB-Iceberg jetzt eine starke Basisunterstützung für Iceberg-Tabellen, die Nutzerinnen und Nutzern die analytische Kraft von DuckDB auf ihren Iceberg-Tabellen erschließt. Es kommt noch mehr Arbeit, und die Iceberg-Tabellenspezifikation hat viele weitere Features, die das DuckDB-Team in DuckDB-Iceberg unterstützen möchte. Wenn Sie ein Feature für Ihre analytischen Workloads priorisieren, melden Sie sich im DuckDB-Iceberg-GitHub-Repository oder nehmen Sie Kontakt mit unseren Engineers auf.
Unten eine Liste geplanter Verbesserungen für die nahe Zukunft (ohne besondere Reihenfolge):
- Performance-Verbesserungen
- Updates / Deletes / Inserts auf partitionierten Tabellen
- Updates / Deletes / Inserts auf sortierten Tabellen
- Schema-Evolution
- Unterstützung für Iceberg-v3-Tabellen, mit Fokus auf binäre Deletion Vectors und Row-Lineage-Tracking