Workloads abstimmen
Die Option preserve_insertion_order
Beim Import oder Export von Datensätzen (von/nach Parquet oder CSV), die deutlich größer sind als der verfügbare Speicher, kann ein Out-of-Memory-Fehler auftreten:
Out of Memory Error: failed to allocate data of size ... (.../... used)In diesen Fällen sollten Sie die Konfigurationsoption preserve_insertion_order auf false setzen:
SET preserve_insertion_order = false;Dadurch darf das System Ergebnisse ohne ORDER BY-Klausel umordnen, was den Speicherverbrauch potenziell senkt.
Parallelität (Mehrkernverarbeitung)
Der Einfluss von Zeilengruppen auf die Parallelität
DuckDB parallelisiert den Workload anhand von Zeilengruppen, also Gruppen von Zeilen, die auf Speicherebene zusammen abgelegt sind. Die Standard-Zeilengruppengröße im DuckDB-Datenbankformat beträgt 122.880 Zeilen. Die Parallelität beginnt auf Ebene der Zeilengruppen; damit eine Abfrage auf k Threads läuft, müssen daher mindestens k * 122.880 Zeilen gescannt werden.
Die Zeilengruppengröße kann als Option der Anweisung ATTACH angegeben werden:
ATTACH '/tmp/somefile.db' AS db (ROW_GROUP_SIZE 16384);Die Leistungsüberlegungen zur Wahl von ROW_GROUP_SIZE für Parquet-Dateien gelten unverändert auch für DuckDBs eigenes Datenbankformat.
Zu viele Threads
In bestimmten Fällen kann DuckDB zu viele Threads starten (z. B. durch HyperThreading), was zu Verlangsamungen führen kann. Dann lohnt es sich, die Thread-Anzahl manuell mit SET threads = X zu begrenzen.
Workloads größer als der Speicher (Out-of-Core-Verarbeitung)
Eine zentrale Stärke von DuckDB ist die Unterstützung von Workloads, die größer sind als der verfügbare Speicher, d. h. DuckDB kann Datensätze verarbeiten, die den Systemspeicher übersteigen (auch Out-of-Core-Verarbeitung genannt). DuckDB kann außerdem Abfragen ausführen, deren Zwischenergebnisse nicht in den Speicher passen. Dieser Abschnitt erläutert Voraussetzungen, Umfang und bekannte Einschränkungen der Out-of-Core-Verarbeitung in DuckDB.
Auslagerung auf die Festplatte
Workloads größer als der Speicher werden durch Auslagerung auf die Festplatte unterstützt.
In der Standardkonfiguration legt DuckDB das temporäre Verzeichnis ⟨database_file_name⟩.tmp{:.language-sql .highlight} (im persistenten Modus) bzw. das Verzeichnis .tmp{:.language-sql .highlight} (im In-Memory-Modus) an. Dieses Verzeichnis lässt sich mit der Konfigurationsoption temp_directory ändern, z. B.:
SET temp_directory = '/path/to/temp_dir.tmp/';Blockierende Operatoren
Einige Operatoren können keine einzige Zeile ausgeben, bevor sie die letzte Zeile ihrer Eingabe gesehen haben. Diese heißen blockierende Operatoren, weil sie ihre gesamte Eingabe puffern müssen, und sind die speicherintensivsten Operatoren in relationalen Datenbanksystemen. Die wichtigsten blockierenden Operatoren sind:
- Gruppierung:
GROUP BY - Join:
JOIN - Sortierung:
ORDER BY - Windowing:
OVER ... (PARTITION BY ... ORDER BY ...)
DuckDB unterstützt Out-of-Core-Verarbeitung für alle diese Operatoren.
Einschränkungen
DuckDB ist bestrebt, Workloads immer abzuschließen, auch wenn sie größer sind als der Speicher. Derzeit gibt es jedoch einige Einschränkungen:
- Treten mehrere blockierende Operatoren in derselben Abfrage auf, kann DuckDB aufgrund des komplexen Zusammenspiels dieser Operatoren dennoch eine Out-of-Memory-Exception werfen.
- Einige Aggregatfunktionen, etwa
list()undstring_agg(), unterstützen keine Auslagerung auf die Festplatte. - Aggregatfunktionen, die sortieren, sind holistisch, d. h. sie benötigen alle Eingaben, bevor die Aggregation beginnen kann. Da DuckDB einige komplexe Zwischenzustände von Aggregaten noch nicht auf die Festplatte auslagern kann, können diese Funktionen bei großen Datensätzen eine Out-of-Memory-Exception verursachen.
- Die Operation
PIVOTverwendet intern die Funktionlist()und unterliegt daher derselben Einschränkung.
Profiling
Wenn Ihre Abfragen nicht so performant sind wie erwartet, lohnt es sich, ihre Abfragepläne zu untersuchen:
- Mit
EXPLAINgeben Sie den physischen Abfrageplan aus, ohne die Abfrage auszuführen. - Mit
EXPLAIN ANALYZEführen Sie die Abfrage aus und profilieren sie. Dabei wird die CPU-Zeit jedes Schritts angezeigt. Aufgrund von Multithreading ist die Summe der Einzelzeiten größer als die Gesamtlaufzeit der Abfrage.
Abfragepläne können auf die Ursache von Leistungsproblemen hinweisen. Einige allgemeine Hinweise:
- Vermeiden Sie Nested-Loop-Joins zugunsten von Hash-Joins.
- Ein Scan, der für eine später angewendete Filterbedingung keinen Filter-Pushdown enthält, verursacht unnötige IO. Versuchen Sie, die Abfrage so umzuschreiben, dass ein Pushdown greift.
- Ungünstige Join-Reihenfolgen, bei denen die Kardinalität eines Operators auf Milliarden von Tupeln explodiert, sollten unbedingt vermieden werden.
Prepared Statements
Prepared Statements können die Leistung verbessern, wenn dieselbe Abfrage viele Male mit unterschiedlichen Parametern ausgeführt wird. Beim Vorbereiten einer Anweisung werden mehrere der ersten Phasen der Abfrageausführung (Parsing, Planung usw.) abgeschlossen und deren Ausgabe zwischengespeichert. Bei der Ausführung können diese Schritte übersprungen werden, was die Leistung verbessert. Das lohnt sich vor allem beim wiederholten Ausführen kleiner Abfragen (Laufzeit < 100 ms) mit unterschiedlichen Parametersätzen.
Beachten Sie, dass es kein primäres Entwurfsziel von DuckDB ist, viele kleine Abfragen schnell nebenläufig auszuführen. DuckDB ist vielmehr auf größere, seltener ausgeführte Abfragen optimiert.
Entfernte Dateien abfragen
DuckDB verwendet synchrones IO beim Lesen entfernter Dateien. Jeder DuckDB-Thread kann daher höchstens eine HTTP-Anfrage gleichzeitig stellen. Muss eine Abfrage viele kleine Netzwerkanfragen stellen, kann das Erhöhen der threads-Einstellung über die Anzahl der CPU-Kerne hinaus (etwa das 2- bis 5-fache der CPU-Kerne) Parallelität und Leistung verbessern.
Unnötiges Lesen von Daten vermeiden
Der Hauptengpass bei Workloads, die entfernte Dateien lesen, ist in der Regel die IO. Unnötig gelesene Daten zu minimieren, kann daher sehr vorteilhaft sein.
Einige grundlegende SQL-Tricks helfen dabei:
- Vermeiden Sie
SELECT *. Wählen Sie stattdessen nur Spalten aus, die tatsächlich verwendet werden. DuckDB versucht, nur die tatsächlich benötigten Daten herunterzuladen. - Wenden Sie nach Möglichkeit Filter auf entfernte Parquet-Dateien an. DuckDB kann diese Filter nutzen, um die Menge der gescannten Daten zu reduzieren.
- Sortieren oder partitionieren Sie Daten nach Spalten, die regelmäßig für Filter verwendet werden: Das erhöht die Wirksamkeit der Filter bei der Reduzierung der IO.
Um zu prüfen, wie viel entfernte Daten für eine Abfrage übertragen werden, kann EXPLAIN ANALYZE die Gesamtzahl der Anfragen und die insgesamt übertragenen Daten für Abfragen auf entfernten Dateien ausgeben.
Caching
Ab Version 1.3.0 unterstützt DuckDB das Caching entfernter Daten. Um den Inhalt des Caches für externe Dateien zu prüfen, führen Sie aus:
FROM duckdb_external_file_cache();Best Practices für Verbindungen
DuckDB arbeitet am besten, wenn dieselbe Datenbankverbindung viele Male wiederverwendet wird. Trennen und erneut Verbinden bei jeder Abfrage verursacht Overhead, der die Leistung beim Ausführen vieler kleiner Abfragen mindern kann. DuckDB speichert außerdem einige Daten und Metadaten im Speicher; dieser Cache geht verloren, wenn die letzte offene Verbindung geschlossen wird. Häufig ist eine einzelne Verbindung am besten, ein Connection-Pool kann aber ebenfalls verwendet werden.
Mehrere Verbindungen können einige Operationen parallelisieren, obwohl das typischerweise nicht nötig ist. DuckDB versucht, innerhalb jeder einzelnen Abfrage so weit wie möglich zu parallelisieren, das ist aber nicht in allen Fällen möglich. Mehrere Verbindungen können mehr Operationen gleichzeitig verarbeiten. Das hilft eher, wenn DuckDB nicht CPU-begrenzt ist, sondern durch eine andere Ressource wie die Netzwerkübertragungsgeschwindigkeit ausgebremst wird.
Persistente vs. In-Memory-Tabellen
DuckDB unterstützt leichtgewichtige Kompressionsverfahren. Standardmäßig wird Kompression nur auf persistenten (auf der Festplatte gespeicherten) Datenbanken angewendet, nicht auf In-Memory-Tabellen.
In manchen Fällen führt das zu kontraintuitiven Leistungsunterschieden, bei denen Abfragen auf Festplatten-Tabellen schneller sind als auf In-Memory-Tabellen. Betrachten wir Q1 des TPC-H-Workloads auf dem SF30-Datensatz:
CALL dbgen(sf = 30);.timer onPRAGMA tpch(1);Wir führen dieses Skript mit drei DuckDB-Prompts aus:
| Datenbankeinrichtung | DuckDB-Prompt | Ausführungszeit |
|---|---|---|
| In-Memory-DB (unkomprimiert) | duckdb |
4.22 s |
| In-Memory-DB (komprimiert) | duckdb -cmd "ATTACH ':memory:' AS db (COMPRESS); USE db;" |
0.55 s |
| Persistente DB (komprimiert) | duckdb tpch-sf30.db |
0.56 s |
Die komprimierten Datenbanken sind etwa 8× schneller als die unkomprimierte In-Memory-Datenbank.