DuckDB vs SQLite auf einem Server: Warum beide
SQLite verwaltet Transaktionsdaten, DuckDB analysiert Parquet und CSV. Erfahren Sie, warum eine VPS meist beide nutzt, mit je einem konkreten Beispiel.
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 dafür 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 dafür ausgelegt, Millionen von Zeilen zu durchsuchen und ein Aggregat zurückzugeben. Beide sind eingebettete Bibliotheken, beide öffnen eine einfache Datei, und bei keiner von beiden müssen Sie einen Serverprozess betreuen.
Die ehrliche Antwort auf die Frage „welche?“ lautet daher fast immer: „beide auf derselben VPS“. Ihre Anwendung hält ihren aktiven Zustand in SQLite. Ihre Auswertungen lesen Parquet- und CSV-Dateien mit DuckDB. Die beiden konkurrieren nicht miteinander, weil sie nicht dieselbe Aufgabe erfüllen.
Warum Zeilen- und Spaltenspeicher die Antwort verändern
SQLite schreibt eine Zeile als zusammenhängenden Bereich auf eine Seite. Das Abrufen einer Bestellung über ihren Primärschlüssel greift auf eine Indexseite und eine Datenseite zu. Das sind zwei Lesezugriffe. Genau das macht eine Anwendung tausendfach pro Sekunde: diesen Benutzer lesen, diese Sitzung aktualisieren, diese Bestellung einfügen.
DuckDB schreibt jede Spalte separat und komprimiert sie. Beim Aufsummieren 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 vektorisierter Verarbeitung über Werteblöcke berechnet. Die übrigen Spalten werden nie vom Datenträger gelesen. Daraus entsteht der Geschwindigkeitsvorteil.
Führen Sie nun jede Engine mit der Arbeitslast der jeweils anderen aus. Beim Aufsummieren einer Spalte muss SQLite jede Zeile durchlaufen und die gesamte Zeile von der Seite lesen, um ein einzelnes Feld zu erreichen. Dadurch werden deutlich mehr Daten vom Datenträger gelesen als erforderlich. Beim Einfügen einer Bestellung muss DuckDB für einen einzelnen Wert den Speicher jeder Spalte ändern. Außerdem nimmt 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 überzeugt: transaktionaler Anwendungsstatus
Verwenden Sie SQLite, wenn Schreibvorgänge klein und häufig sind und nicht verloren gehen dürfen. Dazu gehören Sitzungen, Bestellungen, Warteschlangeneinträge, Einstellungen und alle Daten, die eine Webanfrage 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 gleichzeitig 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 am nützlichsten. Im standardmäßigen Rollback-Journal-Modus blockiert ein Schreibvorgang alle Lesevorgänge. Im WAL-Modus lesen Abfragen weiterhin den zuletzt festgeschriebenen Status, während ein Schreibvorgang Daten anhängt. Dadurch hält ein langsamer Bericht die dahinterliegende Webanfrage 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 verursacht, 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. Eine Wartezeit von fünf Sekunden verhindert 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 Eingabedaten aus einer Sammlung von CSV- oder Parquet-Dateien bestehen. Installieren Sie den Befehlszeilen-Client, Version 1.5.5 (Stand 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 PATH aufnehmen. Prüfen Sie, ob sie ausgeführt werden kann:
~/.duckdb/cli/latest/duckdb :memory: "SELECT version();"Erstellen Sie eine realistische Datei für Abfragen. Der folgende Befehl schreibt fünf Millionen Bestellzeilen in Parquet und komprimiert sie mit zstd:
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 CPU-Kerne abhängt. Entscheidend ist das Muster. Es gab kein CREATE TABLE, kein INSERT und keinen Ladeschritt: DuckDB las den Parquet-Footer, ermittelte, welche Spalten-Chunks die Abfrage benötigte, und las nur diese. Ein vollständiges Verzeichnis funktioniert mit einem Glob-Muster genauso, FROM '/srv/data/orders-*.parquet'. So wird aus den täglichen Exporten eines Monats eine einzige Abfrage.
Die Festplattengeschwindigkeit bildet die Untergrenze für all das. Ein Spaltenscan ist ein langer sequenzieller Lesevorgang. Deshalb ist der Unterschied zwischen NVMe- und älterem SATA-Speicher auf einem VPS hier deutlicher sichtbar als bei den kleinen zufälligen Lesezugriffen von SQLite.
Lesen Ihrer SQLite-Datenbank mit DuckDB
Die beiden Engines verbinden sich über die sqlite-Erweiterung von DuckDB. 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;Damit werden die Zeilen zum Abfragezeitpunkt direkt aus der SQLite-Datei gelesen. Es ist keine Kopie erforderlich. Das ist praktisch, aber nicht schnell, weil die Daten auf dem Datenträger weiterhin als Zeilenspeicher vorliegen und DuckDB sie vollständig 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 gesamte Muster ab. 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 ihre Schreibvorgänge schnell bleiben.
Führen Sie den Export nach einem Zeitplan statt manuell aus. Ein systemd-Service und Timer-Paar hat dafür die richtige Größe: eine Unit, die den COPY ausführt, und ein Timer, der ihn jede Nacht startet.
Beide auf einem VPS ausführen
Hierfür ist kein Container und kein Port erforderlich. Beide Engines sind Bibliotheken. Für die Installation wird daher nur ein Paket und ein Dateipfad benötigt. Wenn der übrige Stack bereits unter Docker Compose auf demselben VPS läuft, binden Sie das Datenverzeichnis in den Container ein, der es benötigt, 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 von der Analyse gelesen werden. Wenn beide ein Verzeichnis gemeinsam verwenden, kann ein Backup-Job, der eine Datei per Snapshot sichert, gleichzeitig mit der anderen Operation konkurrieren.
Verweisen Sie nicht zwei Prozesse im Lese-/Schreibmodus auf dieselbe DuckDB-Datenbankdatei. Nur ein Prozess darf eine DuckDB-Datei zum Schreiben geöffnet halten. Der zweite Prozess kann sie überhaupt nicht öffnen. Mehrere lesende Prozesse sind problemlos möglich, wenn jeder von ihnen access_mode = 'READ_ONLY' setzt. Das überrascht viele Nutzer, die von SQLite kommen, weil dort mehrere Prozesse routinemäßig gemeinsam auf eine Datei zugreifen. Wenn Ihre Analyse ausschließlich Parquet-Dateien liest, stellt sich diese Frage nicht. Das ist ein weiterer Grund, dauerhafte Daten in SQLite zu speichern.
Backups unterscheiden sich, und dieser Unterschied hat Folgen
Eine laufende SQLite-Datenbank besteht aus drei Dateien. Wenn Sie diese während eines Schreibvorgangs mit cp kopieren, erhalten Sie eine Datei, die sich zwar öffnen lässt, aber fehlerhaft ist. Verwenden Sie den integrierten 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. Jede andere Ausgabe bedeutet, dass Sie diesen Snapshot verwerfen und einen neuen erstellen müssen.
Parquet-Dateien ändern sich nach dem Schreiben nicht mehr. Sie benötigen daher keine spezielle Behandlung: Sichern Sie das Verzeichnis. Übertragen Sie beide Pfade mit restic-Backups von Ihrem VPS vom Server weg. Damit besteht die gesamte Datenschicht in einem Backup-Auftrag aus zwei Verzeichnissen.
Fehlerbilder und die exakt angezeigten Zeichenfolgen
Error: database is locked von SQLite bedeutet, dass eine andere Verbindung die Schreibsperre länger gehalten hat, als es Ihr Timeout zulässt. Das ist keine Beschädigung. Setzen Sie PRAGMA busy_timeout für jede Verbindung und suchen Sie anschließend nach einer langen Transaktion, die aus mehreren kurzen Transaktionen hätte bestehen sollen.
Error: unable to open database file nach einer Berechtigungsänderung bedeutet in der Regel, dass der Prozess die Datei schreiben kann, aber nicht ihr Verzeichnis. 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ötigte, als verfügbar war. DuckDB lagert Daten auf die Festplatte aus, wenn dies möglich ist. Geben Sie DuckDB daher einen Speicherort für temporäre Daten, indem Sie eine Datenbankdatei auf der Festplatte statt :memory: öffnen, und begrenzen Sie den Speicherbedarf mit SET memory_limit = '2GB';. Auf einem System, auf dem weitere Dienste laufen, verhindert diese Begrenzung, dass eine spontan ausgeführte 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.
Wie Sie in der Praxis auswählen
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 beantworten beide Fragen mit Ja. Die richtige Lösung besteht dann darin, jeder Engine den Teil zu überlassen, für den sie geeignet ist, statt eine Engine dazu zu zwingen, die andere zu ersetzen.
Vermeiden Sie es, den aktiven Anwendungsstatus nach DuckDB zu verschieben, nur weil ein Bericht langsam war. Der Bericht war wegen des Speicherlayouts langsam. Die Lösung ist daher ein Export und keine Neugestaltung Ihres Schreibpfads.
FAQ
Kann DuckDB SQLite als Anwendungsdatenbank ersetzen?
Nicht bei einer Anwendung mit häufigen Schreibvorgängen. DuckDB sperrt die gesamte Datenbankdatei für Schreibvorgänge, erlaubt jeweils nur einen Prozess mit Lese- und Schreibzugriff und ist auf umfangreiche Änderungen statt auf das Einfügen einzelner Zeilen ausgelegt. 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 dies benötigt.
Ist DuckDB für 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 benennt, und verarbeitet Werte in Batches. SQLite muss dagegen vollständige 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, während DuckDB den Speicher für jede Spalte berührt.
Benötige ich viel RAM, um DuckDB auf einem VPS auszuführen?
Nein, aber weisen Sie DuckDB ein Limit und Speicherplatz auf der Festplatte zu. Ö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 entbehren kann. Ohne ein Limit kann ein großer GROUP BY den Out of Memory Error erhöhen oder andere Dienste aus dem RAM verdrängen.
Wie übertrage ich meine SQLite-Daten nach Parquet?
Hängen Sie die SQLite-Datei in 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 nach einem Zeitplan für abgeschlossene Zeiträume aus, beispielsweise für die Zeilen des letzten Monats. Aktuelle Zeilen verbleiben in SQLite, wo die Anwendung weiterhin Schreibvorgänge ausführt.
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 zugleich 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.