DuckDB vs SQLite auf einem Server: Wann beide passen
SQLite verwaltet Transaktionsdaten, DuckDB analysiert Parquet und CSV. Erfahren Sie, warum eine VPS meist beide nutzt, inklusive je eines praktischen Beispiels.
DuckDB vs SQLite auf einem Server: die Antwort in einem Satz
SQLite ist eine OLTP-Engine (Online-Transaktionsverarbeitung): Sie speichert Daten als Zeilen und ist darauf ausgelegt, wenige davon gleichzeitig sicher und schnell zu lesen und zu schreiben. DuckDB ist eine OLAP-Engine (Online-Analyseverarbeitung): Sie speichert Daten als Spalten und ist darauf ausgelegt, Millionen von Zeilen zu durchsuchen und ein aggregiertes Ergebnis zurückzugeben. Beide sind eingebettete Bibliotheken, beide öffnen eine normale Datei, und keiner von beiden benötigt einen Serverprozess, den Sie überwachen müssen.
Die ehrliche Antwort auf die Frage „welches?“ lautet fast immer: „beide auf derselben VPS“. Ihre Anwendung speichert ihren aktiven Zustand in SQLite. Ihre Berichte lesen Parquet- und CSV-Dateien mit DuckDB. Die beiden konkurrieren nicht, weil sie unterschiedliche Aufgaben erfüllen.
Warum Zeilenspeicherung und Spaltenspeicherung die Antwort verändern
SQLite schreibt eine Zeile als zusammenhängenden Bereich einer Seite. Das Abrufen einer Bestellung über ihren Primärschlüssel berührt eine Indexseite und eine Datenseite. Das sind zwei Lesevorgänge. Genau das macht eine Anwendung tausende Male pro Sekunde: diesen Benutzer lesen, diese Sitzung aktualisieren, diese Bestellung einfügen.
DuckDB schreibt jede Spalte separat und komprimiert sie. Beim Summieren von amount_cents über fünf Millionen Zeilen wird nur die Spalte amount_cents gelesen. Jedes andere Byte in der Datei wird übersprungen. Die Summe wird mithilfe von vektorisiertem Code über Wertegruppen berechnet. Die anderen Spalten werden nie von der Festplatte gelesen. Daher kommt der Geschwindigkeitsvorteil.
Führen Sie nun jede Engine mit der Arbeitslast der jeweils anderen aus. Beim Summieren einer Spalte muss SQLite jede Zeile durchlaufen und die gesamte Zeile von der Seite lesen, um ein Feld zu erreichen. Dadurch werden deutlich mehr Daten von der Festplatte gelesen als erforderlich. Beim Einfügen einer Bestellung muss DuckDB für einen einzelnen Wert den Speicher jeder Spalte bearbeiten. Außerdem setzt es dafür eine Schreibsperre für die gesamte Datenbankdatei. Keine der beiden Engines ist fehlerhaft. Jede beantwortet eine Frage, für die sie nicht ausgelegt ist.
Wo SQLite gewinnt: transaktionaler Anwendungszustand
Verwenden Sie SQLite, wenn Schreibvorgänge klein und häufig sind und nicht verloren gehen dürfen. Dazu gehören Sitzungen, Bestellungen, Warteschlangenzeilen, Einstellungen und alles, was eine Webanforderung erstellt.
sudo apt update && sudo apt install -y sqlite3
sudo install -d -o "$USER" -g "$USER" /srv/appErstellen Sie die Tabelle und aktivieren Sie dabei im selben Schritt das Write-Ahead-Logging.
sqlite3 /srv/app/app.db <<'SQL'
PRAGMA journal_mode = WAL;
CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY,
customer TEXT NOT NULL,
placed_at TEXT NOT NULL,
amount_cents INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS orders_customer ON orders (customer);
INSERT INTO orders (customer, placed_at, amount_cents)
VALUES ('ana', '2026-07-30T09:14:00Z', 4200);
SQLDie erste Ausgabezeile lautet wal. Das ist PRAGMA, das den aktivierten Modus meldet. Diese Einstellung ist auf einem Server besonders nützlich. Im standardmäßigen Rollback-Journal-Modus blockiert ein Schreibvorgang alle Lesevorgänge. Im WAL-Modus lesen Leser weiterhin den zuletzt festgeschriebenen Zustand, während ein Schreibvorgang Daten anhängt. Ein langsamer Bericht hält dadurch die dahinterliegende Webanforderung nicht mehr auf.
Prüfen Sie, ob die Zeile zurückgegeben wurde:
sqlite3 /srv/app/app.db "SELECT * FROM orders WHERE customer = 'ana';"Sie erhalten 1|ana|2026-07-30T09:14:00Z|4200. Neben der Datenbank sind zwei weitere Dateien erschienen: app.db-wal und app.db-shm. Beide gehören zur Datenbank. Wenn Sie während des laufenden Anwendungsbetriebs nur app.db kopieren, erhalten Sie ein inkonsistentes Backup. Das wird weiter unten behandelt.
SQLite erlaubt weiterhin nur einen Schreibvorgang gleichzeitig. Diese Begrenzung wird durch eine Sperre umgesetzt, nicht durch eine Warteschlange. Ein zweiter Schreibvorgang, der zu lange wartet, schlägt daher mit database is locked fehl, statt unbegrenzt zu blockieren. Erhöhen Sie die Wartezeit mit PRAGMA busy_timeout = 5000; für jede Verbindung, die Ihre Anwendung öffnet. Fünf Sekunden Wartezeit vermeiden die meisten dieser Fehler bei einer normalen Weblast.
Wo DuckDB überzeugt: Analysen über bereits vorhandene Dateien
Verwenden Sie DuckDB, wenn die Frage mit „wie viele“, „wie viel“ oder „welche Top Ten“ beginnt und die Eingabe aus einer Sammlung von CSV- oder Parquet-Dateien besteht. Installieren Sie den Befehlszeilenclient, Version 1.5.5 im Juli 2026:
curl https://install.duckdb.org | shDas Skript installiert die Binärdatei unter ~/.duckdb/cli/latest/duckdb und gibt die Zeile aus, mit der Sie sie in Ihren PATH aufnehmen. Überprüfen Sie, ob sie ausgeführt wird:
~/.duckdb/cli/latest/duckdb :memory: "SELECT version();"Erstellen Sie eine realistische Datei für die Abfrage. Damit werden fünf Millionen Bestellzeilen in Parquet geschrieben und mit zstd komprimiert:
mkdir -p /srv/data
duckdb :memory: "
COPY (
SELECT i AS id,
'cust_' || (i % 5000) AS customer,
TIMESTAMP '2026-01-01 00:00:00' + INTERVAL (i) MINUTE AS placed_at,
(i * 37) % 20000 AS amount_cents
FROM range(5000000) t(i)
) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd);"Stellen Sie nun die analytische Frage. Öffnen Sie die Shell, aktivieren Sie den Timer und fragen Sie die Datei direkt ab, ohne vorherigen Importschritt:
.timer on
SELECT customer,
count(*) AS orders,
sum(amount_cents)/100.0 AS revenue
FROM '/srv/data/orders.parquet'
GROUP BY customer
ORDER BY revenue DESC
LIMIT 5;Lesen Sie Ihre eigene Zahl aus .timer ab, statt einer veröffentlichten Zahl zu vertrauen, da das Ergebnis von Ihrer Festplatte und der Anzahl Ihrer Kerne abhängt. Entscheidend ist die Form des Ergebnisses. Es gab keinen CREATE TABLE, kein INSERT und keinen Ladeschritt: DuckDB las den Parquet-Footer, ermittelte, welche Spaltenblöcke die Abfrage benötigte, und las nur diese. Ein ganzes Verzeichnis funktioniert mit einem Glob genauso, FROM '/srv/data/orders-*.parquet'. So wird aus einem Monat täglicher Exporte eine einzige Abfrage.
Die Festplattengeschwindigkeit begrenzt all dies. Ein Spaltenscan ist ein langer sequenzieller Lesevorgang. Deshalb zeigt sich der Unterschied zwischen NVMe- und älterem SATA-Speicher auf einem VPS hier deutlicher als bei den kleinen zufälligen Lesevorgängen von SQLite.
Ihre SQLite-Datenbank aus DuckDB lesen
Die beiden Engines arbeiten über die DuckDB-Erweiterung sqlite zusammen. Binden Sie die Anwendungsdatenbank schreibgeschützt ein, damit eine Analyseabfrage niemals den Live-Zustand ändern kann:
INSTALL sqlite;
LOAD sqlite;
ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY);
SELECT customer, count(*) AS orders
FROM app.orders
GROUP BY customer
ORDER BY orders DESC;Dabei werden die Zeilen zum Abfragezeitpunkt direkt aus der SQLite-Datei gelesen. Es wird keine Kopie erstellt. Das ist praktisch, aber nicht schnell, weil die Daten auf dem Datenträger weiterhin als Zeilenspeicher vorliegen und DuckDB sie durchlaufen muss. Verwenden Sie dieses Verfahren für den Export, nicht für ein Dashboard, das alle dreißig Sekunden neu geladen wird:
COPY (SELECT * FROM app.orders)
TO '/srv/data/orders-2026-07.parquet' (FORMAT parquet, COMPRESSION zstd);Diese eine Anweisung bildet das vollständige Muster. SQLite verwaltet die aktuellen Live-Zeilen. Ein geplanter Export wandelt abgeschlossene Zeiträume in Parquet um. DuckDB beantwortet jede Abfrage, die sich über mehrere Monate erstreckt. Die Anwendungsdatenbank bleibt klein, wodurch Schreibvorgänge schnell bleiben.
Führen Sie den Export nach einem Zeitplan und nicht manuell aus. Ein systemd-Service und Timer-Paar hat dafür die passende Größe: eine Unit, die COPY ausführt, und ein Timer, der sie jede Nacht startet.
Beide auf einem VPS ausführen
Hierfür benötigen Sie weder einen Container noch einen Port. Beide Engines sind Bibliotheken. Daher besteht die Installation aus einem Paket und einem Dateipfad. Wenn der Rest Ihres Stacks bereits unter Docker Compose auf demselben VPS ausgeführt wird, binden Sie das Datenverzeichnis in den benötigten Container ein, anstatt einen Datenbankdienst hinzuzufügen. Es gibt keinen Dienst, den Sie hinzufügen könnten.
Zwei Regeln verhindern Probleme bei dieser Anordnung.
Geben Sie jeder Engine ein eigenes Verzeichnis: /srv/app für die SQLite-Datei, in die die Anwendung schreibt, und /srv/data für die Parquet-Dateien, die die Analyse liest. Wenn beide dasselbe Verzeichnis verwenden, konkurriert ein Backup-Job, der einen Snapshot erstellt, mit der jeweils anderen Engine.
Verweisen Sie nicht im Lese-/Schreibmodus von zwei Prozessen auf dieselbe DuckDB-Datenbankdatei. Nur ein Prozess darf eine DuckDB-Datei zum Schreiben geöffnet halten. Der zweite Prozess kann sie überhaupt nicht öffnen. Viele Leseprozesse sind problemlos möglich, wenn jeder Prozess access_mode = 'READ_ONLY' setzt. Das überrascht Nutzer, die von SQLite kommen, weil dort mehrere Prozesse routinemäßig dieselbe Datei verwenden. Wenn Ihre Analyse nur Parquet-Dateien liest, stellt sich diese Frage nicht. Das ist ein weiterer Grund, dauerhaften Zustand in SQLite zu speichern.
Backups unterscheiden sich, und der Unterschied verursacht Probleme
Eine laufende SQLite-Datenbank besteht aus drei Dateien. Wenn Sie diese während eines Schreibvorgangs mit cp kopieren, erhalten Sie eine Datei, die sich öffnen lässt, aber falsche Daten enthält. Verwenden Sie den eigenen Backup-Befehl der Engine. Er erstellt einen konsistenten Snapshot, während die Anwendung weiter schreibt:
sqlite3 /srv/app/app.db ".backup '/srv/backup/app-$(date -u +%Y%m%dT%H%M%SZ).db'"
sqlite3 /srv/backup/app-20260730T091400Z.db "PRAGMA integrity_check;"integrity_check gibt bei einer fehlerfreien Kopie ok aus. Jeder andere Wert bedeutet, dass Sie diesen Snapshot verwerfen und einen neuen erstellen müssen.
Parquet-Dateien ändern sich nach dem Schreiben nicht. Sie benötigen daher keine besondere Behandlung. Sichern Sie das Verzeichnis. Übertragen Sie beide Pfade mit restic-Backups von Ihrem VPS vom Server. Damit befinden sich die gesamte Datenschicht und zwei Verzeichnisse in einem Backup-Job.
Fehlerarten und die genauen angezeigten Zeichenfolgen
Error: database is locked von SQLite bedeutet, dass eine andere Verbindung die Schreibsperre länger gehalten hat, als Ihr Timeout zulässt. Es handelt sich nicht um eine Beschädigung. Setzen Sie PRAGMA busy_timeout für jede Verbindung und suchen Sie dann nach einer langen Transaktion, die aus mehreren kurzen Transaktionen hätte bestehen sollen.
Error: unable to open database file nach einer Berechtigungsänderung bedeutet normalerweise, dass der Prozess zwar in die Datei, aber nicht in deren Verzeichnis schreiben kann. SQLite erstellt app.db-wal und app.db-shm neben der Datenbank. Daher muss das Verzeichnis selbst beschreibbar sein, nicht nur die Datei .db.
IO Error: Could not set lock on file von DuckDB bedeutet, dass ein zweiter Prozess die Datenbank bereits zum Schreiben geöffnet hat. Schließen Sie die andere Shell oder öffnen Sie Ihre Datenbank schreibgeschützt.
Out of Memory Error von DuckDB auf einem kleinen VPS bedeutet, dass eine Abfrage mehr Arbeitsspeicher benötigt hat, als verfügbar war. DuckDB lagert Daten nach Möglichkeit auf die Festplatte aus. Geben Sie DuckDB dafür einen Speicherort, indem Sie eine Datenbankdatei auf der Festplatte statt :memory: öffnen, und begrenzen Sie den Speicherverbrauch mit SET memory_limit = '2GB';. Auf einem System, auf dem andere Dienste ausgeführt werden, verhindert diese Begrenzung, dass eine Ad-hoc-Abfrage Ihre Anwendung aus dem RAM verdrängt.
Binder Error: Referenced column "amount" not found beim Abfragen von Parquet bedeutet fast immer, dass das Schema der Datei nicht dem entspricht, an das Sie sich erinnern. Führen Sie DESCRIBE SELECT * FROM '/srv/data/orders.parquet'; aus und lesen Sie die tatsächlichen Spaltennamen aus.
So treffen Sie in der Praxis eine Entscheidung
Fragen Sie, wie das Schreibmuster aussieht. Viele kleine Schreibvorgänge, die einen Stromausfall überstehen müssen, sprechen für SQLite. Fragen Sie, wie das Lesemuster aussieht. Vollständige Scans mit Aggregationen über eine lange Historie sprechen für DuckDB. Die meisten realen Systeme erfüllen beide Bedingungen. Die richtige Lösung ist dann, jeder Engine den Teil zuzuweisen, für den sie geeignet ist, statt eine Engine zu zwingen, die Aufgabe der anderen zu übernehmen.
Vermeiden Sie die Migration des aktiven Anwendungszustands nach DuckDB, nur weil ein Bericht langsam war. Der Bericht war langsam, weil das Speicherlayout ungeeignet war. Die Lösung ist daher ein Export und keine Neuentwicklung Ihres Schreibpfads.
FAQ
Kann DuckDB SQLite als Anwendungsdatenbank ersetzen?
Nicht bei einer Datenbank, in die häufig geschrieben wird. DuckDB setzt eine Schreibsperre für die gesamte Datenbankdatei und lässt jeweils nur einen Lese-Schreib-Prozess zu. DuckDB ist für umfangreiche Änderungen statt für das Einfügen einzelner Zeilen optimiert. Bewahren Sie den Transaktionsstatus in SQLite auf und lassen Sie DuckDB ihn mit ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY); lesen, wenn ein Bericht ihn benötigt.
Ist DuckDB bei Analysen wirklich schneller als SQLite?
Bei Scans und Aggregationen über eine große Tabelle ja. Der Grund ist das Speicherlayout und kein Tuning-Trick. DuckDB liest nur die Spalten, die eine Abfrage nennt, und verarbeitet Werte in Stapeln. SQLite muss dagegen ganze Zeilen durchlaufen, um ein einzelnes Feld zu erreichen. Beim Abrufen einer einzelnen Zeile anhand des Primärschlüssels kehrt sich das Verhältnis um, weil SQLite zwei Seiten liest und DuckDB den Speicher jeder Spalte berührt.
Brauche ich viel RAM, um DuckDB auf einem VPS auszuführen?
Nein, aber geben Sie DuckDB ein Limit und Speicherplatz auf der Festplatte. Öffnen Sie eine Datenbankdatei statt :memory:, damit DuckDB Zwischenergebnisse auf die Festplatte auslagern kann. Setzen Sie anschließend SET memory_limit = '2GB'; auf einen Wert, den Ihr VPS bereitstellen kann. Ohne ein Limit kann ein großes GROUP BY Out of Memory Error erhöhen oder andere Dienste aus dem RAM verdrängen.
Wie übertrage ich meine SQLite-Daten nach Parquet?
Binden Sie die SQLite-Datei aus DuckDB ein und kopieren Sie eine Abfrage mit COPY (SELECT * FROM app.orders) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd); direkt heraus. Führen Sie dies regelmäßig für abgeschlossene Zeiträume aus, etwa für die Zeilen des letzten Monats. Lassen Sie aktuelle Zeilen in SQLite, wo die Anwendung sie weiterhin schreibt.
Welche Datenbank sollte ich wie sichern?
Beide, aber auf unterschiedliche Weise. Erstellen Sie SQLite-Snapshots mit sqlite3 app.db ".backup '/srv/backup/app.db'" statt mit cp, weil eine laufende Datenbank auch eine -wal- und eine -shm-Datei ist und eine einfache Kopie inkonsistent sein kann. Parquet-Dateien ändern sich nach dem Schreiben nicht mehr. Daher reicht es aus, das Verzeichnis zu kopieren.