2023-09-26
Ankündigung von DuckDB 0.9.0
Mark Raasveldt, Hannes Mühleisen

Das DuckDB-Team freut sich, das neueste DuckDB-Release (0.9.0) bekannt zu geben. Diese Version heißt Undulata nach der Gelbschnabelente, die in Afrika beheimatet ist.
Zur Installation der neuen Version siehe die Installationsanleitung. Die vollständigen Release Notes finden Sie auf GitHub.
Was ist neu in 0.9.0
Es gab zu viele Änderungen, um jede im Detail zu besprechen. Einige besonders spannende Features möchten wir aber hervorheben:
- Out-of-Core-Hash-Aggregation
- Speicherverbesserungen
- Indexverbesserungen
- DuckDB-Wasm-Erweiterungen
- Automatisches Laden von Erweiterungen
- Verbesserte AWS-Unterstützung
- Iceberg-Unterstützung
- Azure-Unterstützung
- PySpark-kompatible API
Im Folgenden eine Zusammenfassung dieser neuen Features mit Beispielen – beginnend mit einer Änderung in unserem SQL-Dialekt, die standardmäßig intuitivere Ergebnisse liefern soll.
Breaking Changes in SQL
Struct-Auto-Casting. Bisher wurden die Namen von Struct-Einträgen bei den Auto-Casting-Regeln ignoriert. Struct-Feldnamen konnten dadurch still umbenannt werden. Ab diesem Release führt das zu einem Fehler.
CREATE TABLE structs (s STRUCT(i INTEGER));INSERT INTO structs VALUES ({'k': 42});Mismatch Type Error: Type STRUCT(k INTEGER) does not match with STRUCT(i INTEGER). Cannot cast STRUCTs with different namesUnbenannte Structs, die mit der Funktion ROW erzeugt werden, lassen sich weiterhin in Struct-Felder einfügen.
INSERT INTO structs VALUES (ROW(42));Verbesserungen im Kernsystem
Out-of-Core-Hash-Aggregationen und Leistungsverbesserungen der Hash-Aggregation. Bei großen Datensätzen ist Speicherverwaltung immer ein potenzieller Schmerzpunkt. Über eine streaming Execution Engine und einen Buffer Manager unterstützt DuckDB viele Operationen auf Datensätzen, die größer als der Speicher sind. DuckDB will außerdem Abfragen unterstützen, deren Zwischenergebnisse nicht in den Speicher passen, indem auf die Platte ausgelagert wird.
In diesem Release wird das Auslagern auf die Platte durch Out-of-Core-Hash-Aggregationen weiter ausgebaut. Hash-Tabellen, die bei GROUP BY-Abfragen oder DISTINCT-Operationen wegen vieler eindeutiger Gruppen nicht in den Speicher passen, werden jetzt auf die Platte geschrieben statt eine Out-of-Memory-Exception zu werfen. Durch geschickte Radix-Partitionierung fällt der Leistungsabfall allmählich aus, Leistungssprünge werden vermieden. Nur der Teil der Tabelle, der nicht in den Speicher passt, wird ausgelagert.
Die Leistung unserer Hash-Aggregation hat sich generell verbessert, besonders bei vielen Gruppen. Zum Beispiel berechnen wir die Zahl eindeutiger Zeilen in einem Datensatz mit 30 Millionen Zeilen und 15 Spalten so:
SELECT count(*) FROM (SELECT DISTINCT * FROM tbl);Liegen alle Daten im Speicher, sollte die Abfrage etwa 6 GB brauchen. Die Abfrage lässt sich aber auch mit weniger Speicher zu Ende führen. Die Tabelle zeigt, wie die Laufzeit mit sinkendem Speicherlimit beeinflusst wird:
| memory limit | v0.8.1 | v0.9.0 |
|---|---|---|
| 10 GB | 8.52 s | 2.91 s |
| 9 GB | 8.52 s | 3.45 s |
| 8 GB | 8.52 s | 3.45 s |
| 7 GB | 8.52 s | 3.47 s |
| 6 GB | OOM | 3.41 s |
| 5 GB | OOM | 3.67 s |
| 4 GB | OOM | 3.87 s |
| 3 GB | OOM | 4.20 s |
| 2 GB | OOM | 4.39 s |
| 1 GB | OOM | 4.91 s |
Compressed Materialization. DuckDBs streaming Execution Engine hat einen geringen Speicherbedarf, für Operationen wie gruppierte Aggregation wird aber mehr Speicher gebraucht. Dieser Fußabdruck lässt sich durch Kompression verringern. DuckDB nutzt bereits viele Kompressionstechniken im Speicherformat, viele davon sind zur Laufzeit einer Abfrage aber zu teuer. Bestimmte leichte Kompressionstechniken sind so günstig, dass der Gewinn durch weniger Speicher den Aufwand für (De-)Kompression überwiegt.
In diesem Release komprimieren wir Strings und Integer-Typen unmittelbar bevor die Daten in gruppierte Aggregation und Sortierung gehen. Über Statistiken werden beide Typen auf den kleinstmöglichen Integer-Typ komprimiert. Beispielsweise bei folgender Tabelle:
┌───────┬─────────┐│ id │ name ││ int32 │ varchar │├───────┼─────────┤│ 300 │ alice ││ 301 │ bob ││ 302 │ eve ││ 303 │ mallory ││ 304 │ trent │└───────┴─────────┘Die Spalte id nutzt ein 32-Bit-Integer. Aus den Statistiken wissen wir: Minimum 300, Maximum 304. Wir können 300 abziehen und auf ein 8-Bit-Integer casten – statt 4 Bytes nur noch 1.
Die Spalte name nutzt unseren internen String-Typ mit 16 Bytes. Die Statistiken sagen aber, dass der längste String hier nur 7 Bytes hat. Das passt in ein 64-Bit-Integer:
alice -> alice005bob -> bob00003eve -> eve00003mallory -> mallory7trent -> trent005Das reduziert die Breite von 16 auf 8 Bytes. Damit komprimierte Strings sortierbar bleiben, drehen wir auf Big-Endian-Maschinen die Bytes, damit unsere Vergleichsoperatoren weiter stimmen:
alice005 -> 500ecilabob00003 -> 30000bobeve00003 -> 30000evemallory7 -> 7yrollamtrent005 -> 500tnertKleinere Abfragezwischenstände bedeuten weniger oder kein Auslagern auf die Platte, weniger teure I/O und damit schnellere Abfragen.
Leistungsverbesserungen bei Window-Funktionen (#7831, #7996, #8050, #8491). Dieses Release bringt viele Verbesserungen der Window-Funktionen: bessere Vektorisierung, mehr Wiederverwendung partieller Aggregate und bessere Parallelität durch Work Stealing. Dadurch ist die Leistung von Window-Funktionen deutlich gestiegen, besonders wenn es keine oder wenige Partitionen gibt.
SELECT sum(driver_pay) OVER ( ORDER BY dropoff_datetime ASC RANGE BETWEEN INTERVAL 3 DAYS PRECEDING AND INTERVAL 0 DAYS FOLLOWING )FROM tripdata;| Version | Run time |
|---|---|
| v0.8.0 | 33.8 s |
| v0.9.0 | 3.8 s |
Speicherverbesserungen
Vacuuming gelöschter Row Groups. Ab diesem Release werden bei DELETE ganze gelöschte Row Groups automatisch aufgeräumt. Außerdem kann die Datenbankdatei beim Checkpoint gekürzt werden, sodass die Datei nach dem Löschen von Daten kleiner wird. Das passiert nur, wenn die gelöschten Row Groups am Ende der Datei liegen. Das System verschiebt Daten noch nicht, um die Dateigröße auf der Platte zu verkleinern. Freie Blöcke weiter vorne in der Datei werden stattdessen für spätere Daten wiederverwendet.
Verbesserungen am Index-Storage (#7930, #8112, #8437, #8703). Speicherbedarf im RAM und auf der Platte von ART-Indizes wurde stark reduziert. Besonders bei Indizes für PRIMARY KEY, UNIQUE oder FOREIGN KEY ist der Fußabdruck deutlich kleiner.
CREATE TABLE integers (i INTEGER PRIMARY KEY);INSERT INTO integers FROM range(10000000);| Version | Size |
|---|---|
| v0.8.0 | 278 MB |
| v0.9.0 | 78 MB |
Außerdem können Indizes durch die geänderte Speicherung auf der Platte jetzt inkrementell geschrieben werden statt immer komplett neu. Checkpoints bei Tabellen mit Indizes werden dadurch deutlich schneller.
Erweiterungen
Automatisches Laden von Erweiterungen. Ab diesem Release kann DuckDB vertrauenswürdige Erweiterungen automatisch installieren und laden. Viele Workflows hängen von Kern-Erweiterungen ab, die nicht mitgeliefert werden, etwa httpfs; Nutzer mussten sich merken, die nötigen Erweiterungen vorab zu laden. Jetzt werden sie automatisch geladen (und optional installiert), sobald sie in einer Abfrage vorkommen.
In Python funktioniert das folgende Snippet jetzt ohne explizites Laden der Erweiterungen httpfs oder json.
import duckdb
duckdb.sql("FROM 'https://raw.githubusercontent.com/duckdb/duckdb/main/data/json/example_n.ndjson'")Der Satz automatisch ladbarer Erweiterungen ist auf offizielle Erweiterungen von DuckLabs beschränkt und steht hier. Das Verhalten lässt sich über autoinstall_known_extensions und autoload_known_extensions abschalten, oder über die allgemeinere Einstellung enable_external_access. Siehe die Konfigurationsoptionen.
DuckDB-Wasm-Erweiterungen. Dieses Release fügt ladbare Erweiterungen für DuckDB-Wasm hinzu. Bisher mussten gewünschte Erweiterungen im Wasm-Client fest eingebacken sein. Jetzt können sie dynamisch geladen werden. Beim Laden wird das Wasm-Bundle heruntergeladen und die Funktionalität der Erweiterung aktiviert. Probieren Sie es in unserer Wasm-Shell.
LOAD inet;SELECT '127.0.0.1'::INET;AWS-Erweiterung. Dieses Release markiert den Start der DuckDB-AWS-Erweiterung. Sie enthält AWS-bezogene Funktionen, die auf dem AWS SDK aufbauen. Derzeit gibt es eine Funktion, LOAD_AWS_CREDENTIALS, die die AWS Credential Provider Chain nutzt, um Credentials automatisch zu holen und zu setzen:
CALL load_aws_credentials();SELECT * FROM 's3://some-bucket/that/requires/authentication.parquet';Mehr dazu in der Dokumentation.
Experimentelle Iceberg-Erweiterung. Dieses Release markiert den Start der DuckDB-Iceberg-Erweiterung. Sie ermöglicht das Lesen von Tabellen im Iceberg-Format.
SELECT count(*)FROM iceberg_scan('data/iceberg/lineitem_iceberg', allow_moved_paths = true);Mehr dazu in der Dokumentation.
Experimentelle Azure-Erweiterung. Dieses Release markiert den Start der DuckDB-Azure-Erweiterung. Damit kann DuckDB Daten auf Azure nativ lesen, ähnlich wie Daten auf S3.
SET azure_storage_connection_string = '<your_connection_string>';SELECT * FROM 'azure://<my_container>/*.csv';Mehr dazu in der Dokumentation.
Clients
Experimentelle PySpark-API. Dieses Release fügt dem Python-Client eine experimentelle Spark-API hinzu. Sie soll vollständig kompatibel zur PySpark-API sein: Sie nutzen die vertraute Spark-API, darunter arbeitet DuckDB. Alle Statements werden über unsere Relational API in DuckDBs interne Pläne übersetzt und von DuckDBs Query-Engine ausgeführt.
from duckdb.experimental.spark.sql import SparkSession as sessionfrom duckdb.experimental.spark.sql.functions import lit, colimport pandas as pd
spark = session.builder.getOrCreate()
pandas_df = pd.DataFrame({ 'age': [34, 45, 23, 56], 'name': ['Joan', 'Peter', 'John', 'Bob']})
df = spark.createDataFrame(pandas_df)df = df.withColumn( 'location', lit('Seattle'))res = df.select( col('age'), col('location')).collect()
print(res)#[# Row(age=34, location='Seattle'),# Row(age=45, location='Seattle'),# Row(age=23, location='Seattle'),# Row(age=56, location='Seattle')#]Die API ist derzeit experimentell, Features fehlen noch. Feedback ist uns sehr willkommen. Melden Sie fehlende Funktionalität bitte über Discord oder auf GitHub.
Schlussbemerkung
Die vollständigen Release Notes finden Sie auf GitHub. Wir danken allen Beitragenden für die Arbeit an DuckDB.