2025-01-22
Query Engines: Torwächter des Parquet-Dateiformats
Laurens Kuiper
Das Format Apache® Parquet™
Apache Parquet ist ein beliebtes, freies, quelloffenes, spaltenorientiertes Datenspeicherformat. Während Datenbanksysteme Daten aus Formaten wie CSV und JSON typischerweise erst in Tabellen laden, bevor sie sie analysieren, ist Parquet dafür gedacht, effizient direkt abgefragt zu werden. Parquet berücksichtigt, dass Nutzer oft nur einen Teil der Daten lesen wollen, nicht alle. Deshalb lassen sich einzelne Spalten lesen, statt immer alle lesen zu müssen. Außerdem können Statistiken Teile von Dateien herausfiltern, ohne sie vollständig zu lesen (auch Zone Maps genannt). Parquet-Dateien sind außerdem typischerweise viel kleiner als CSV oder JSON – dank einer Kombination aus leichter spaltenorientierter Kompression und Allzweckkompression.
Viele Query Engines implementieren das Lesen und Schreiben von Parquet-Dateien. Deshalb eignet sich Parquet auch als Datenaustauschformat. Parquet-Dateien, die Spark in einer großen verteilten Pipeline geschrieben hat, lassen sich später mit DuckDB analysieren. Weil so viele Systeme Parquet lesen und schreiben können, ist es das Datenformat der Wahl für Data-Lake-Lösungen wie Delta Lake™ und Iceberg™. Parquet hat zweifellos Schwächen, die Forscher und Unternehmen mit neuen Formaten angehen. Mag man es oder nicht: Parquet bleibt vorerst da.
Also machen wir das Beste daraus, oder? Auch SQL hat Schwächen, und obwohl Forscher andere Abfragesprachen versucht haben, sitzen wir immer noch auf SQL fest. DuckDB nimmt das an und versucht, das Beste daraus zu machen. Die Parquet-Entwickler tun dasselbe für ihr Format: Sie aktualisieren es gelegentlich und bringen neue Features, die das Format besser machen.
Updates
Fügt DuckDB in einem Release eine neue Kompressionsmethode zum internen Dateiformat hinzu, müssen alle folgenden Releases sie lesen können. Sonst könnten Sie eine von DuckDB 1.1.0 erzeugte Datenbankdatei nach dem Update auf 1.2.0 nicht mehr lesen. Das heißt Abwärtskompatibilität und ist für Entwickler oft schwierig. Manchmal muss man Legacy-Code behalten und Konvertierungen von alt nach neu bauen. Ältere Formate weiter zu unterstützen ist wichtig, weil ein DuckDB-Update viel einfacher ist als das Umschreiben ganzer Datenbankdateien.
Abwärtskompatibilität ist auch für Parquet wertvoll: Eine vor Jahren geschriebene Parquet-Datei sollte sich heute noch lesen lassen. Glücklicherweise können die meisten gängigen Query Engines Dateien im Format Parquet 1.0 lesen, das 2013 erschien – vor über zehn Jahren. Updates bedrohen die Abwärtskompatibilität nicht, solange Engines die alten Dateien weiter lesen können. Wichtig ist aber auch, dass Query Engines das Lesen neuerer Dateien ergänzen, damit wir auch neue, verbesserte Parquet-Dateien schreiben können.
Hier wird es knifflig. Wir können nicht erwarten, dass Query Engines das brandneue Parquet-Format morgen lesen, wenn die Parquet-Entwickler heute ein Update ausrollen. Eine Weile können wir das neue Format nicht schreiben, weil viele Engines es nicht lesen können. Das Robustheitsprinzip sagt: „Sei konservativ in dem, was du sendest, und liberal in dem, was du akzeptierst.“ Auf Parquet-Dateien bezogen: Query Engines sollten neue Parquet-Dateien lesen, sie aber (zumindest standardmäßig) noch nicht schreiben.
Encodings
DuckDB mag leichte Kompression sehr.
Für die kommende Version DuckDB 1.2.0 freuen wir uns, die Encodings DELTA_BINARY_PACKED, DELTA_LENGTH_BYTE_ARRAY (in Parquet 2.2.0 2015 hinzugefügt) und BYTE_STREAM_SPLIT (in Parquet 2.8.0 2019 hinzugefügt) in unserem Parquet-Writer implementiert zu haben.
DuckDB, 2018 gestartet, kann Parquet seit 2020 lesen, DELTA_BINARY_PACKED und DELTA_LENGTH_BYTE_ARRAY seit 2022 und BYTE_STREAM_SPLIT seit 2023.
Trotz der Verfügbarkeit in 1.2.0 schreibt DuckDB diese Encodings nicht standardmäßig.
Täte DuckDB das, hätten viele Nutzer ein frustrierendes Erlebnis, weil einige gängige Query Engines diese Encodings immer noch nicht lesen können.
Ein gutes Kompressionsverhältnis nützt nichts, wenn die nachgelagerte Anwendung die Datei nicht lesen kann.
Deshalb haben wir das Schreiben dieser Encodings standardmäßig deaktiviert.
Sie kommen nur zum Einsatz, wenn in einem COPY-Befehl PARQUET_VERSION v2 gesetzt wird.
Schon DuckDB-Versionen ab 0.9.1 (Ende 2023) können Dateien lesen, die mit
PARQUET_VERSION v2serialisiert wurden.
Daten zu komprimieren ist fast immer ein Kompromiss zwischen Dateigröße und Schreibzeit. Schauen wir uns folgendes Beispiel an (auf einem MacBook Pro mit M1 Max):
-- Generate TPC-H scale factor 1INSTALL tpch;LOAD tpch;CALL dbgen(sf = 1);
-- Export to Parquet using Snappy compressionCOPY lineitem TO 'snappy_v1.parquet' (COMPRESSION snappy, PARQUET_VERSION v1); -- 244 MB, ~0.46 sCOPY lineitem TO 'snappy_v2.parquet' (COMPRESSION snappy, PARQUET_VERSION v2); -- 170 MB, ~0.39 s
-- Export to Parquet using zstd compressionCOPY lineitem TO 'zstd_v1.parquet' (COMPRESSION zstd, PARQUET_VERSION v1); -- 152 MB, ~0.58 sCOPY lineitem TO 'zstd_v2.parquet' (COMPRESSION zstd, PARQUET_VERSION v2); -- 135 MB, ~0.44 sMit Snappy, DuckDBs Standard-Seitenkompression für Parquet, die eher auf Geschwindigkeit als auf Kompressionsverhältnis setzt, ist die Datei mit aktivierten Encodings ~30 % kleiner und das Schreiben ~15 % schneller. Mit zstd, das stärker auf Kompression als auf Geschwindigkeit setzt, ist die Datei ~11 % kleiner und das Schreiben ~24 % schneller.
Das Kompressionsverhältnis hängt stark davon ab, wie gut sich die Daten komprimieren lassen. Hier ein paar extremere Beispiele:
CREATE TABLE range AS FROM range(1e9::BIGINT);COPY range TO 'v1.parquet' (PARQUET_VERSION v1); -- 3.7 GB, ~2.96 sCOPY range TO 'v2.parquet' (PARQUET_VERSION v2); -- 1.3 MB, ~1.68 sDie Integer-Folge 0, 1, 2, … komprimiert mit DELTA_BINARY_PACKED extrem gut.
Die Datei ist ~99 % kleiner, und das Schreiben fast doppelt so schnell.
Gleitkommazahlen zu komprimieren ist deutlich schwieriger. Gibt es aber ein Muster, komprimieren die Daten ziemlich gut:
CREATE TABLE range AS SELECT range / 1e9 FROM range(1e9::BIGINT);COPY range TO 'v1.parquet' (PARQUET_VERSION v1); -- 6.3 GB, ~3.83 sCOPY range TO 'v2.parquet' (PARQUET_VERSION v2); -- 610 MB, ~2.63 sDiese Folge komprimiert mit BYTE_STREAM_SPLIT sehr gut.
Sie ist ~90 % kleiner und schreibt ~31 % schneller.
Echte Daten haben selten so extrem komprimierbare Muster.
Trotzdem gibt es Muster, die diese Encodings ausnutzen.
Wenn die von Ihnen genutzten Query Engines sie lesen können, können Sie diese Encodings ab DuckDB 1.2.0 einsetzen!
Verschwendete Bits
Exakte Zahlen sind schwer zu bekommen, aber es ist sicher, dass täglich viele TB an Daten in Parquet geschrieben werden. Ein großer Teil der geschriebenen Bits wird verschwendet, weil Query Engines diese neueren Encodings nicht implementiert haben. Die Lösung ist überraschend einfach. Man muss nichts Neues erfinden, um den Platzverschwendung zu stoppen. Einfach die Spezifikation der Parquet-Encodings lesen und implementieren. Einige dieser „neueren“ Encodings sind inzwischen fast 10 Jahre alt!
Kleinere Parquet-Dateien bedeuten weniger Daten in Rechenzentren. Schon eine kleine Reduktion kann große Wirkung haben, weil irgendwann weniger neue Rechenzentren nötig sind. Rechenzentren sind nicht böse; wir werden in Zukunft sicher mehr brauchen. Das Beste aus den bestehenden zu machen, schadet aber niemandem.
Fazit
Parquet ist derzeit der Industriestandard für tabellarische Daten. Weil es auch als Austauschformat dient, hängt die Wirksamkeit von Parquet-Features von den Query Engines ab, die es nutzen. Wenn einige der gängigen Query Engines (Sie wissen, wen wir meinen) diese Features nicht implementieren, verlieren wir alle. Nicht jede Engine muss am Parquet-Bleeding-Edge sein, und DuckDB ist das auch nicht. Aber Entwickler von Query Engines tragen gemeinsam die Verantwortung, Parquet nützlicher zu machen.
Wir hoffen, dass mehr Query Engines diese neueren Encodings implementieren. Dann können mehr Engines sie standardmäßig schreiben und weniger Bits verschwenden.