Unser letzter Vergleich von DuckDB, Polars und Pandas konzentrierte sich in erster Linie auf moderate Datensatzgrößen mit 9-GB-CSV-Benchmarks. Der vorliegende Artikel erweitert diese Untersuchung auf deutlich größere Skalierungen und fokussiert sich auf DuckDB und Polars unter extremen analytischen Workloads, die aus dem TPC-H-Benchmark abgeleitet sind.
Anstatt idealisierte Speicherlayouts oder moderate Datenmengen zu betonen, untersucht diese Arbeit bewusst adversariale Szenarien, darunter sehr große Parquet-Dateien, große Sammlungen kleiner Dateien sowie Kombinationen aus beidem. Ziel ist es nicht, ein universell überlegenes System zu identifizieren, sondern zu analysieren, wie unterschiedliche Ausführungsmodelle auf steigende Datenvolumina, Dateianzahlen und Speicherdruck reagieren.
Durch die Konstanthaltung des logischen Abfragemusters bei gleichzeitiger Variation von Datensatzgröße und physischem Layout isoliert diese Studie die Effekte der Dateiorganisation und Skalierung auf die Performance. Dies ermöglicht einen fokussierten Vergleich von Ausführungsstrategien unter schweren, scan-intensiven Workloads.
Methodik
Benchmark-Setup
Der Benchmark verwendet die TPC-H-Tabelle lineitem, gespeichert im Parquet-Format. Die einzelnen Dateien reichen in ihrer Größe von etwa 2 GB bis 140 GB. Alle Abfragen folgen demselben logischen Muster: ein Parquet-Scan mit Projektion auf drei Spalten, selektiven Filtern und einer abschließenden Zeilenzählung.
Die Workloads sind daher stark scan-lastig sowie filter- und aggregationsorientiert. Join-lastige oder shuffle-intensive Abfragen werden nicht evaluiert. Beide Systeme durften ihre standardmäßigen Multi-Thread-Einstellungen verwenden. Die Filterselektivität wurde in allen Experimenten konstant gehalten, um die Vergleichbarkeit zu gewährleisten.
Setup
Alle Tests wurden auf einem MacBook Pro aus dem Jahr 2021 (M1 Max, 32 GB RAM) unter Verwendung von Python durchgeführt. Ein eigens entwickeltes Benchmarking-Tool stellte eine konsistente und reproduzierbare Ausführung über die Kommandozeile sicher und erlaubte die Auswahl sowohl der Engine als auch der getesteten Operation. Die Ergebnisse wurden mit matplotlib visualisiert, und zentrale statistische Kennzahlen wurden berichtet.
Speichermessung
Erste Experimente maßen den Speicherverbrauch als Differenz der Resident Set Size (RSS) zwischen Beginn und Ende jeder Abfrage. Dieser Ansatz erwies sich als irreführend, da der maximale Speicherverbrauch häufig während der Ausführung auftritt, temporäre Puffer vor Abschluss freigegeben werden und Betriebssystem-Caching die finalen RSS-Messungen verzerren kann.
In diesem Benchmark wird der Speicherverbrauch daher als die maximale während der Abfrage beobachtete RSS definiert, relativ zur Start-RSS. Dies erfasst den maximalen inkrementellen Arbeitsspeicherbedarf der Abfrage anstelle des gesamten Prozess-Footprints.
Der Prozessspeicher wurde in kurzen Intervallen von 0,05 Sekunden gemessen, und der höchste beobachtete Wert wurde aufgezeichnet. Jeder Test wurde in einem frischen Prozess ausgeführt, um eine konsistente Ausgangsbasis zu gewährleisten. Obwohl sich das Page-Caching des Betriebssystems nicht vollständig eliminieren ließ, reduziert dieses Setup persistente In-Process-Caching-Effekte. Jede Konfiguration wurde zehnmal ausgeführt, und die berichteten Ergebnisse stellen Durchschnittswerte dar.
Datensatzgenerierung
Der in diesem Benchmark verwendete Datensatz wurde mit DuckDBs tpch-Erweiterung erzeugt, die sowohl den Datengenerator als auch die Referenzabfragen für den TPC-H-Benchmark implementiert. Die Erweiterung ist standardmäßig in den meisten DuckDB-Builds enthalten und unterstützt eine einfache Datengenerierung über Skalierungsfaktoren. Aus dem generierten Datensatz wurde die Tabelle lineitem extrahiert und mit DuckDB als Parquet-Datei exportiert.
Testdesign
Es wurden drei Experimente durchgeführt, um unterschiedliche Skalierungsverhalten abzubilden:
Single-Table-Skalierung: Eine einzelne
lineitem-Datei wächst von etwa 2 GB auf 140 GB.Viele kleine Dateien: Bis zu 72 Dateien mit jeweils etwa 2 GB werden kombiniert, um das Gesamtvolumen des Single-Table-Tests zu erreichen, jedoch mit deutlich höherer Dateianzahl.
Wenige sehr große Dateien: Mehrere 140-GB-Dateien werden kombiniert und auf insgesamt etwa 1,97 TB skaliert.
In den beiden letzteren Tests stellt UNION ALL sicher, dass alle Eingabedaten ohne Deduplikation verarbeitet werden und verhindert Metadaten-basierte Zeilenzähl-Optimierungen.
Ergebnisse
Über alle Experimente hinweg blieb die logische Abfrage identisch, während das physische Layout und die Gesamtgröße des Datensatzes von wenigen Gigabytes bis fast 2 TB variierten. Dadurch werden die Effekte von Dateigröße, Dateianzahl und Ausführungsstrategie auf Laufzeit und inkrementellen Spitzen-Speicher isoliert.
1. Single-Table-Skalierung
Wenn eine einzelne Parquet-Datei von etwa 2 GB auf 140 GB anwächst, zeigen Polars und DuckDB sehr ähnliche Ausführungszeiten, wobei Polars auf der größten Skala einen leichten Vorteil von etwa 1 Sekunde behält.
DuckDB zeigt einen langsamen, stetigen Anstieg des Speicherverbrauchs mit wachsendem Datenvolumen und erreicht etwa 1,3 GB. Polars hingegen steigert seinen Speicherverbrauch deutlich aggressiver, überschreitet bereits bei rund 8 GB die 1-GB-Marke und erreicht für den ~140-GB-Datensatz etwa 17 GB.
2. Viele kleine Dateien
Die Kombination von bis zu 72 Parquet-Dateien mit jeweils etwa 2 GB führt zu ähnlichen Ausführungszeiten wie im ersten Test, mit einem etwas deutlicheren Vorteil für DuckDB.
Der Speicherverbrauch von DuckDB bleibt konstant unter 70 MB und steigt nur geringfügig mit wachsendem Datenvolumen. Polars hingegen zeigt einen starken Anstieg – von etwa 400 MB bei 2 GB auf 4,3 GB bei 40 GB – und erreicht danach ein Plateau.
3. Wenige sehr große Dateien
Der Ausführungszeit-Trend ähnelt dem zweiten Test. Für den 2-TB-Datensatz benötigt Polars etwa 1 Minute, während DuckDB den Workload in rund 45 Sekunden abschließt.
Der Speicherverbrauch von Polars skaliert von etwa 1,7 GB auf 23 GB. DuckDB steigt dagegen nur von ungefähr 500 MB auf 2,4 GB und behält durchgehend einen deutlich niedrigeren Spitzenwert.
4. Vergleich der Dateilayouts
Zusätzlich wurden das Single-Table-Szenario und das Many-Small-Tables-Szenario direkt verglichen, da beide ähnliche Datenvolumina repräsentieren, sich aber im Dateilayout unterscheiden. Keines der beiden Systeme zeigt eine signifikante Laufzeitverbesserung durch die Partitionierung in kleinere Dateien.
Hinsichtlich des Speicherverbrauchs profitieren jedoch beide Engines deutlich. Bei partitionierten Workloads reduziert DuckDB seinen Spitzenverbrauch um bis zu den Faktor 8, während Polars Reduktionen von bis zu Faktor 4 erreicht.
Diskussion
Speicherverhalten
In allen experimentellen Konfigurationen blieb DuckDBs inkrementeller Spitzen-Speicherverbrauch unter etwa 2,5 GB – selbst bei der Verarbeitung von fast 2 TB Parquet-Daten. Polars hingegen erreichte bei sehr großen Eingabedateien deutlich höhere Spitzenwerte von bis zu rund 20 GB. Wurde derselbe Datensatz in viele kleinere Dateien partitioniert, sank der Speicherverbrauch beider Systeme deutlich. Diese Ergebnisse deuten darauf hin, dass beide Engines erheblich von partitionierten Workloads gegenüber einzelnen massiven Datensätzen profitieren. Dies entspricht auch der DuckDB-Dokumentation, die Parquet-Dateigrößen zwischen 100 MB und 10 GB empfiehlt.
Polars und große Dateien
Obwohl Polars wettbewerbsfähige Ausführungszeiten erzielt, steigt sein Spitzen-Speicherverbrauch bei großen Dateien stark an. Derselbe Workload wird deutlich speichereffizienter, wenn er in viele kleinere Dateien aufgeteilt wird. Dies deutet darauf hin, dass der Engpass von Polars in der Pufferung auf Row-Group- und Page-Ebene in Kombination mit Multi-Thread-Scanning und Dekompression liegt. Bei vielen Threads können mehrere Row Groups gleichzeitig dekomprimiert werden, wobei jede eigene Scratch-Buffer benötigt.
Beim Arrow-basierten Parquet-Lesen erfolgt die Dekompression auf Seiten- und Column-Chunk-Ebene, und die Puffer können beträchtlich groß sein. Werden Workloads in kleinere Dateien aufgeteilt, reduziert sich die Anzahl der gleichzeitig verarbeiteten Row Groups, was die maximale Summe der Dekodier-, Maskierungs- und Materialisierungspuffer senkt.
DuckDBs Architektur erzwingt dagegen strikte Speicherlimits über seinen Buffer Manager. Obwohl auch DuckDB Parquet-Seiten dekodiert, ist sein Ausführungsmodell darauf ausgelegt, den Arbeitssatz begrenzt zu halten und Daten aggressiv auszulagern oder zu streamen, um große transiente Speicherpeaks zu vermeiden.
Spitzen- vs. Endspeicherverbrauch
Polars allokiert während der Abfrageausführung erhebliche transiente Speicherressourcen – vor allem für Scans, Dekompression und Zwischenpuffer –, gibt diese aber anschließend größtenteils wieder frei, sodass der Speicherverbrauch nach der Ausführung nahe am Ausgangsniveau liegt. DuckDB hält den Speicherverbrauch während der gesamten Ausführung enger begrenzt. Für speicherbeschränkte Umgebungen ist daher der Spitzenverbrauch entscheidender als der Endverbrauch, da er das Risiko von Out-of-Memory-Fehlern während der Abfrage bestimmt.
Row-Group-Größe
Die Row-Group-Größe spielt eine entscheidende Rolle für DuckDBs Performance. DuckDB parallelisiert Parquet-Scans auf Row-Group-Ebene; Dateien mit nur einer großen Row Group können daher nur von einem Thread verarbeitet werden. Die DuckDB-Dokumentation empfiehlt Row Groups mit 100 K–1 M Zeilen und warnt, dass Größen unter 5.000 Zeilen die Performance um das 5- bis 10-Fache verschlechtern können. Oberhalb von etwa 100.000 Zeilen werden Performance-Unterschiede vernachlässigbar.
Zum Vergleich: Polars’ Standard-Row-Group-Größe liegt bei etwa 250 K Zeilen. Die Row-Group-Konfiguration kann sowohl die Parallelität als auch das Speicherverhalten beider Engines beeinflussen, insbesondere bei großen Dateien. Daher wurden die Parquet-Dateien explizit mit einer Row-Group-Größe von 300.000 erstellt.
Die ersten Tests wurden mit etwa der Hälfte von Polars’ idealer Row-Group-Größe durchgeführt (dem Standardwert für Parquet-Dateien, die von DuckDB erzeugt werden). Dabei stellten wir fest, dass DuckDBs Speicherverbrauch mit größeren Row Groups deutlich sank – z. B. von 1,3 GB auf 500 MB beim 140-GB-Datensatz –, während Polars’ Speicherverbrauch relativ stabil blieb.
Beide Engines erzielten hingegen deutliche Verbesserungen bei den Ausführungszeiten. Der 2-TB-Datensatz wurde mit Polars in 60 s (zuvor 170 s) und mit DuckDB in 44 s (zuvor 70 s) abgeschlossen. Obwohl Parquet ein offenes, engine-agnostisches Format ist, zeigen diese Ergebnisse, dass physische Layout-Entscheidungen wie die Row-Group-Größe implizit bestimmte Ausführungs-Engines begünstigen können. In der Praxis sind Speicher und Rechenebene daher nicht vollständig unabhängig, selbst bei offenen Formaten.
Fazit
Sowohl DuckDB als auch Polars zeigen starke Performance bei großskaligen analytischen Workloads, unterscheiden sich jedoch deutlich hinsichtlich Speicherverbrauch, Skalierbarkeit und Sensitivität gegenüber dem Parquet-Dateilayout. Für Datensätze im Bereich von Hunderten Gigabytes bis hin zu Terabytes weist DuckDB konsistent einen geringeren inkrementellen Spitzen-Speicherverbrauch, stabilere Skalierungseigenschaften und einen robusten Durchsatz auf – selbst bei sehr großen, monolithischen Parquet-Dateien. Sein vektorbasiertes, speicherbegrenztes Ausführungsmodell ermöglicht effiziente Parallelverarbeitung bei gleichzeitig streng kontrolliertem Arbeitssatz.
Polars erzielt hingegen wettbewerbsfähige Ausführungszeiten, zeigt jedoch bei großen Dateien einen deutlich höheren transienten Speicherverbrauch. Seine Performance und Speichereffizienz verbessern sich erheblich, wenn Daten in viele kleinere Parquet-Dateien partitioniert werden, was darauf hindeutet, dass seine Streaming- und Puffermechanismen von feineren Dateilayouts profitieren. Polars bleibt damit eine starke Wahl für DataFrame-orientierte Workflows und Szenarien, in denen Dateipartitionierung möglich ist und ausreichend Speicherreserven zur Verfügung stehen.
Insgesamt verdeutlichen diese Ergebnisse, dass analytische Performance primär durch Ausführungsstrategie, Dateilayout und Workload-Charakteristika geprägt wird – und weniger durch die reine Leistungsfähigkeit der Engine allein. Es gibt kein universell optimales System: DuckDB eignet sich besonders für speicherbeschränkte, großskalige analytische Scans über massive Parquet-Dateien, während Polars in flexiblen, DataFrame-zentrierten Umgebungen mit gut partitionierten Daten überzeugt.
Auch wenn keine der beiden Engines in allen Szenarien bei der Ausführungszeit dominiert, zeigen beide außergewöhnliche Performance. Nur wenige bestehende analytische Systeme erreichen die Geschwindigkeit von DuckDB und Polars bei analytischen Workloads, wie die von Polars veröffentlichten Benchmarks zeigen.
Abschließend unterstreichen unsere Ergebnisse, dass physische Layout-Entscheidungen – wie die Row-Group-Größe – selbst bei offenen, engine-agnostischen Formaten wie Parquet bestimmte Engines implizit begünstigen. In der Praxis sind Speicher und Rechenebene daher enger gekoppelt, als oft angenommen.
Wenn Sie DuckDBs demonstrierte Fähigkeiten in der Cloud nutzen möchten, melden Sie sich für unseren On-Demand-Workshop Hands-on Workshop: Einführung in MotherDuck an und erhalten Sie eine vollständige praxisnahe Einführung!
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.
Blog-Autor*in
Niklas Niggemann
Werkstudent Data & AI
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.