Zum Inhalt springen

Dateiformate

Umgang mit Parquet-Dateien

DuckDB bietet umfassende Unterstützung für Parquet-Dateien, darunter das direkte Abfragen von Parquet-Dateien. Bei der Entscheidung, ob Sie diese Dateien direkt abfragen oder zuerst in die Datenbank laden, sollten Sie mehrere Faktoren berücksichtigen.

Gründe für das direkte Abfragen von Parquet-Dateien

Verfügbarkeit grundlegender Statistiken: Parquet-Dateien nutzen ein spaltenorientiertes Speicherformat und enthalten grundlegende Statistiken wie Zonemaps. Dank dieser Eigenschaften kann DuckDB Optimierungen wie Projektions- und Filter-Pushdown auf Parquet-Dateien anwenden. Workloads, die Projektion, Filterung und Aggregation kombinieren, laufen daher in der Regel recht gut auf Parquet-Dateien.

Speicherplatz: Das Laden der Daten aus Parquet-Dateien benötigt ungefähr denselben Speicherplatz für die DuckDB-Datenbankdatei. Ist der verfügbare Festplattenspeicher knapp, lohnt es sich daher, die Abfragen direkt auf den Parquet-Dateien auszuführen.

Gründe gegen das direkte Abfragen von Parquet-Dateien

Fehlen erweiterter Statistiken: Das DuckDB-Datenbankformat verfügt über HyperLogLog-Statistiken, die Parquet-Dateien nicht haben. Diese verbessern die Genauigkeit der Kardinalitätsschätzungen und sind besonders wichtig, wenn die Abfragen viele Join-Operatoren enthalten.

Tipp. Wenn DuckDB auf Parquet-Dateien eine unvorteilhafte Join-Reihenfolge wählt, laden Sie die Parquet-Dateien in DuckDB-Tabellen. Die besseren Statistiken führen in der Regel zu einer besseren Join-Reihenfolge.

Wiederholte Abfragen: Wenn Sie mehrere Abfragen auf demselben Datensatz ausführen möchten, lohnt es sich, die Daten in DuckDB zu laden. Die Abfragen sind dann durchgängig etwas schneller, wodurch sich die anfängliche Ladezeit mit der Zeit amortisiert.

Hohe Dekompressionszeiten: Einige Parquet-Dateien sind mit aufwendigen Kompressionsalgorithmen wie gzip komprimiert. In diesen Fällen erfordert das Abfragen der Parquet-Dateien bei jedem Zugriff eine teure Dekompression. Leichtgewichtige Verfahren wie Snappy, LZ4 und zstd dekomprimieren dagegen schneller. Mit der Funktion parquet_metadata können Sie den verwendeten Kompressionsalgorithmus ermitteln.

Mikrobenchmark: TPC-H auf einer DuckDB-Datenbank vs. Parquet

Die Abfragen des TPC-H-Benchmarks laufen auf Parquet-Dateien etwa 1.1–5.0× langsamer als auf einer DuckDB-Datenbank.

Bestpractice Wenn ausreichend Speicherplatz vorhanden ist und Sie einen join-intensiven Workload haben und/oder viele Abfragen auf demselben Datensatz planen, laden Sie die Parquet-Dateien zuerst in die Datenbank. Der Kompressionsalgorithmus und die Zeilengruppengrößen in den Parquet-Dateien haben großen Einfluss auf die Leistung: Untersuchen Sie diese mit der Funktion parquet_metadata.

Der Einfluss der Zeilengruppengröße

DuckDB arbeitet am besten mit Parquet-Dateien, deren Zeilengruppen jeweils 100K–1M Zeilen umfassen. Der Grund: DuckDB kann nur über Zeilengruppen parallelisieren – eine Parquet-Datei mit einer einzigen riesigen Zeilengruppe kann daher nur von einem Thread verarbeitet werden. Mit der Funktion parquet_metadata können Sie feststellen, wie viele Zeilengruppen eine Parquet-Datei hat. Beim Schreiben von Parquet-Dateien verwenden Sie die Option row_group_size.

Mikrobenchmark: Aggregationsabfrage bei unterschiedlichen Zeilengruppengrößen

Wir führen eine einfache Aggregationsabfrage über Parquet-Dateien mit unterschiedlichen Zeilengruppengrößen aus, gewählt zwischen 960 und 1.966.080. Die Ergebnisse sind wie folgt.

Zeilengruppengröße Ausführungszeit
960 8.77 s
1920 8.95 s
3840 4.33 s
7680 2.35 s
15360 1.58 s
30720 1.17 s
61440 0.94 s
122880 0.87 s
245760 0.93 s
491520 0.95 s
983040 0.97 s
1966080 0.88 s

Die Ergebnisse zeigen, dass Zeilengruppengrößen <5.000 einen stark negativen Effekt haben und die Laufzeiten mehr als 5–10× größer machen als bei ideal dimensionierten Zeilengruppen, während Größen zwischen 5.000 und 20.000 noch 1.5–2.5× vom Bestwert entfernt sind. Ab einer Zeilengruppengröße von 100.000 sind die Unterschiede gering: Die Lücke zwischen bester und schlechtester Laufzeit beträgt etwa 10 %.

Größen von Parquet-Dateien

DuckDB kann auch über mehrere Parquet-Dateien hinweg parallelisieren. Es ist ratsam, insgesamt mindestens so viele Zeilengruppen über alle Dateien zu haben, wie CPU-Threads vorhanden sind. Bei einer Maschine mit 10 Threads erreichen beispielsweise sowohl 10 Dateien mit je 1 Zeilengruppe als auch 1 Datei mit 10 Zeilengruppen volle Parallelität. Es ist außerdem sinnvoll, die Größe einzelner Parquet-Dateien moderat zu halten.

Bestpractice Der ideale Bereich liegt zwischen 100 MB und 10 GB pro einzelner Parquet-Datei.

Hive-Partitionierung für Filter-Pushdown

Beim Abfragen vieler Dateien mit Filterbedingungen lässt sich die Leistung verbessern, indem Sie eine Hive-Ordnerstruktur verwenden, um die Daten entlang der in der Filterbedingung verwendeten Spalten zu partitionieren. DuckDB muss dann nur die Ordner und Dateien lesen, die die Filterkriterien erfüllen. Das ist besonders hilfreich beim Abfragen entfernter Dateien.

Weitere Tipps zum Lesen und Schreiben von Parquet-Dateien

Tipps zum Lesen und Schreiben von Parquet-Dateien finden Sie auf der Seite Parquet-Tipps.

CSV-Dateien laden

CSV-Dateien werden oft in komprimierter Form verteilt, etwa als GZIP-Archive (.csv.gz). DuckDB kann diese Dateien on-the-fly dekomprimieren. Das ist in der Regel sogar schneller, als die Dateien zuerst zu dekomprimieren und dann zu laden, weil weniger IO anfällt.

Schema Ladezeit
Laden aus GZIP-komprimierten CSV-Dateien (.csv.gz) 107.1 s
Dekomprimieren (mit parallelem gunzip) und Laden aus dekomprimierten CSV-Dateien 121.3 s

Viele kleine CSV-Dateien laden

Der CSV-Reader führt den CSV-Sniffer auf allen Dateien aus. Bei vielen kleinen Dateien kann das unnötig hohen Overhead verursachen. Eine mögliche Optimierung ist, den Sniffer auszuschalten. Unter der Annahme, dass alle Dateien denselben CSV-Dialekt und dieselben Spaltennamen/-typen haben, erhalten Sie die Sniffer-Optionen wie folgt:

.mode line
SELECT Prompt FROM sniff_csv('part-0001.csv');
Prompt = FROM read_csv('file_path.csv', auto_detect=false, delim=',', quote='"', escape='"', new_line='\n', skip=0, header=true, columns={'hello': 'BIGINT', 'world': 'VARCHAR'});

Anschließend können Sie den read_csv-Befehl anpassen, z. B. durch Dateinamen-Expansion (Globbing), und ihn mit den übrigen vom Sniffer erkannten Optionen ausführen:

FROM read_csv('part-*.csv', auto_detect=false, delim=',', quote='"', escape='"', new_line='\n', skip=0, header=true, columns={'hello': 'BIGINT', 'world': 'VARCHAR'});