2026-05-12

Quack: Das DuckDB-Client-Server-Protokoll

The DuckDB team

Hintergrund: Datenbankarchitekturen

Als Datenbanken aufkamen, gab es keine Unterscheidung zwischen ‚Client‘ und ‚Server‘, die ganze Datenbank lief einfach auf einem Rechner. Irgendwann in den 80ern führte Sybase als Erstes das Konzept eines Datenbank-‚Servers‘ und eines ‚Clients‘ auf verschiedenen Rechnern ein. Seither galt als selbstverständlich, dass jedes Datenbanksystem eine Client-Server-Architektur plus Kommunikationsprotokoll nutzt. Das war praktisch, weil der eine veränderliche Zustand an einem Ort unter Kontrolle eines Servers bleibt und viele Clients gleichzeitig lesen und schreiben können. Nachteile gibt es natürlich auch, vor allem können diese Protokolle erheblichen Overhead bringen. Wer mehr dazu lesen möchte: Wir haben vor einer Weile ein Forschungspaper über Datenbankprotokolle geschrieben.

Natürlich gab es immer Abweichler von der Client-Server-Architektur, allen voran das allgegenwärtige SQLite im Jahr 2000 und natürlich DuckDB, erstmals 2019 veröffentlicht. Wir haben ziemlich viel Lärm um eine In-Process-Architektur gemacht, in der es keinen Client-Server gibt, kein Protokoll, nur Low-Level-API-Aufrufe. Das hat für interaktive Fälle in der Data Science gut funktioniert, wenn Analystinnen etwa in einem Python-Notebook mit ihren Daten arbeiten und die Daten in einer DuckDB-Instanz im selben Prozess liegen. Es hat auch gut funktioniert, wenn DuckDB einfach an eine bestehende Anwendung „geklebt“ wurde, um SQL über Daten in dieser Anwendung bereitzustellen.

Als In-Process-System funktioniert das „weniger gut“, wenn dieselbe Datenbankdatei aus mehreren Prozessen gleichzeitig geändert werden soll. Dafür gibt es viele Anwendungsfälle, zum Beispiel wenn mehrere Prozesse Telemetrie in dieselbe Datenbank schreiben und dieselben Tabellen gleichzeitig ein Dashboard speisen. Es gibt sehr gute technische Gründe, warum wir das nicht hinbekommen haben, vor allem, dass DuckDB eine Menge Zustand im Hauptspeicher hält und diesen Zustand synchronisieren müsste, sobald mehrere Prozesse gleichzeitig ändern.

Und ja, es gab Workarounds. Natürlich kann man eine eigene Remote-Procedure-Call-(RPC-)Lösung zusammenschrauben, bei der ein Prozess die DuckDB-Datenbankinstanz hält und anderen Prozessen einen Dienst zum Abfragen und Einfügen anbietet. Es gibt auch mehrere Projekte, die DuckDB Client-Server-Fähigkeiten nachrüsten, zum Beispiel über das Arrow-Flight-SQL-Protokoll. MotherDuck hat ein eigenes Client-Server-Protokoll. Und natürlich kann man immer (keuch) auf ein traditionelleres Datenbanksystem mit Client-Server-Unterstützung wechseln, etwa das ebenfalls allgegenwärtige PostgreSQL. Dann kann man sogar einen sogenannten „EleDucken“ betreiben, DuckDB in besagtem PostgreSQL über eine der verschiedenen Erweiterungen, die das ermöglichen, zum Beispiel pg_duckdb.

Die schiere Zahl der Workarounds, mit denen Leute eine Client-Server-Lösung an DuckDB geschraubt haben, hat uns zumindest überzeugt, dass das den Leuten wichtig ist. Wir sehen DuckDB als universelles Data-Wrangling-Werkzeug. Wenn das ein Client-Server-Protokoll zusätzlich zu den In-Process-Fähigkeiten bedeutet – in Ordnung. Wenn das eine große neue Menge von Fällen erschließt, in denen DuckDB nützlich ist – ausgezeichnet! Am Ende liegt uns die Nutzererfahrung am Herzen, weniger das letzte Wort in der Architektur. Also haben wir irgendwann, endlich, den Schritt gemacht, und heute sind wir sehr froh, das Ergebnis vorzustellen:

Das Quack-Protokoll für DuckDB

Was tun zwei (oder mehr) Enten, wenn sie miteinander sprechen wollen? Sie quaken! Es ist also ziemlich natürlich, dass wir das Protokoll, über das zwei DuckDB-Instanzen miteinander sprechen, ebenfalls „Quack“ nennen! Wir hatten 2026 die Gelegenheit, ein Datenbankprotokoll von Grund auf zu entwerfen, ohne Legacy berücksichtigen zu müssen – ein echter Luxus. Wir konnten von bestehenden Protokollen lernen, einschließlich des jüngeren Arrow Flight SQL und anderer. Bevor wir in die interne Funktionsweise von Quack einsteigen, schauen wir uns an, wie es aus Nutzersicht funktioniert. Zuerst brauchen Sie zwei DuckDB-Instanzen. Richtig, DuckDB tritt sowohl als Client als auch als Server auf! Die beiden Instanzen können auf verschiedenen Rechnern am anderen Ende der Welt (oder im All) liegen oder einfach in zwei Terminalfenstern auf Ihrem Laptop. Zuerst installieren wir die Quack-Erweiterung in beiden DuckDB-Instanzen:

DuckDB #1

CALL quack_serve(
'quack:localhost',
token = 'super_secret'
);
CREATE TABLE hello AS
FROM VALUES ('world') v(s);
quack:

DuckDB #2

CREATE SECRET (
TYPE quack,
TOKEN 'super_secret'
);
ATTACH 'quack:localhost' AS remote;
FROM remote.hello;

Das sollte den Inhalt der entfernten Tabelle hello, world, in DuckDB #2 zeigen. Hexerei! Wir können auch Daten von der lokalen Instanz auf die entfernte kopieren:

DuckDB #1

-- Step two
FROM hello2;
quack:

DuckDB #2

-- Step one
CREATE TABLE remote.hello2 AS
FROM VALUES ('world2') v(s);

Entsprechend sollten Sie world2 in der Ausgabe von DuckDB #1 sehen. Offensichtlich sind das die einfachsten Beispiele, die uns eingefallen sind. Tabellen können deutlich komplexer sein, Abfragen deutlich komplexer, Datenmengen ziemlich groß (siehe unten). Es gibt auch einen Weg, eine komplette wörtliche Abfrage auf die Remote-Seite zu schicken, über die Funktion query, besser für sehr komplexe Abfragen auf großen Datensätzen und mit mehr Kontrolle darüber, was genau remote ausgeführt wird:

DuckDB #1

-- Waiting to serve data
quack:

DuckDB #2

FROM remote.query(
'SELECT s FROM hello'
);

Natürlich gibt es hier noch viel mehr zu sehen. Bitte schauen Sie in unsere Dokumentation für weitere Details.

Protokolldesign

HTTP-basiert

Quack baut direkt auf dem ehrwürdigen HTTP, dem Hypertext Transfer Protocol. Von seinen bescheidenen Anfängen am CERN ist HTTP zu einer De-facto-Protokollschicht über TCP und allem darunter geworden. Der gesamte Stack ist darauf optimiert, HTTP-Nachrichtenströme effizient zu übertragen. Das Protokoll hat überraschend wenig Overhead, wenn es richtig implementiert ist. Jeder und sein kleiner Bruder weiß, wie man mit HTTP in Load Balancing, Authentifizierung, Firewalls, Intrusion Detection usw. umgeht. Ein Datenbankprotokoll 2026 nicht auf HTTP zu bauen wäre ziemlich verfehlt. HTTP erlaubt außerdem der DuckDB-Wasm-Distribution, nativ Quack zu sprechen! DuckDB im Browser kann sich also z. B. direkt mit einer DuckDB-Instanz auf einem EC2-Server über Quack verbinden.

Request-Response-Muster

Interaktionen auf Quack werden immer vom Client in einem Request-Response-Muster getrieben. Quack-Nachrichten sind zum Beispiel Verbindungsanfragen, um sich wie oben mit einem Token zu authentifizieren. Wie Authentifizierung und Autorisierung im Detail laufen, folgt unten. Nachfolgende Nachrichten sind Requests, eine Abfrage auszuführen und den ersten Teil der Antwort zurückzugeben, sowie Folge-Fetch-Nachrichten, um große Ergebnisse zu holen, möglicherweise parallel aus mehreren Threads.

Serialisierung

Requests und Responses werden mit dem neuen MIME-Typ application/duckdb kodiert. Diese Kodierung nutzt DuckDBs interne effiziente Serialisierungsprimitive für komplexe Strukturen wie Datentypen und Result Sets. Dieselben Primitive nutzen wir seit Jahren etwa in unseren Write-Ahead-Log-(WAL-)Dateien, sie sind also ziemlich gut optimiert und kampferprobt.

Verschlüsselung

Wir wollen, dass Quack „einfach funktioniert“, sind uns aber der Sicherheitsalbträume bewusst, wenn man eine Datenbank direkt ans böse Internet hängt, wie es schon vorgekommen ist. Deshalb erzeugt Quack beim Serverstart standardmäßig ein zufälliges Authentifizierungstoken, das dann dem Client mitgegeben werden muss. Zusätzlich bindet der Quack-Server standardmäßig nur an localhost (was sich natürlich überschreiben lässt). Quack nutzt standardmäßig kein SSL, weil es etwas albern wäre, die ganze Infrastruktur und Abhängigkeiten nur für Localhost-Kommunikation mitzuschleppen. Wir raten davon ab, einen DuckDB-Quack-Endpoint direkt ins Internet zu öffnen. Stattdessen empfehlen wir nachdrücklich, einen üblichen HTTP-Endpoint wie nginx vorzuschalten, wenn Sie Quack ins World Wide Web stellen, und diesen Proxy SSL terminieren zu lassen (z. B. mit Let’s Encrypt). Der Quack-Client geht bei nicht-lokalen Verbindungen von aktiviertem SSL aus, das lässt sich überschreiben. Wir haben eine Anleitung dazu in unserer Dokumentation.

Roundtrips

Wir haben darauf geachtet, die Zahl der Protokoll-Roundtrips bzw. Request/Response-Paare für Abfragen zu optimieren. Einmal verbunden, lässt sich eine Abfrage vollständig mit einem einzigen Roundtrip abwickeln. Das ist eine kritische Optimierung für latenzsensible Umgebungen. Gleichzeitig haben wir Quack ernsthaft für effizienten Bulk-Antworttransfer optimiert. Soweit wir wissen, ist Quack derzeit der schnellste Weg, Tabellen durch einen Socket zu schieben, und Millionen Zeilen lassen sich in wenigen Sekunden übertragen. Unten ein paar Benchmark-Ergebnisse.

Authentifizierung und Autorisierung

Authentifizierung und Autorisierung von Datenbankabfragen sind eine unendliche Quelle von Freude und Komplexität. Wir werden kaum jeden Anwendungsfall abdecken, schon gar nicht in einem ersten Release. Das Kluge ist deshalb, es gar nicht erst zu versuchen. Für Quack haben wir ein Auth-Modell gewählt, das an DuckDBs Philosophie der Erweiterbarkeit anschließt. Es gibt schon Hunderte DuckDB-Erweiterungen. Quack liefert eine Default-Authentifizierungsmethode und keine Autorisierungseinschränkungen, beides lässt sich aber durch nutzereigenen Code überschreiben. Wie oben gesehen, erzeugt der Quack-Server beim Start ein zufälliges Default-Authentifizierungstoken. Wenn ein Client verbindet, liefert er einen Authentifizierungsstring. Die Server-Seite ruft einen Authentifizierungs-Callback auf. Standardmäßig vergleicht er das vom Client gelieferte Token mit dem zuvor zufällig erzeugten. Dieser Callback lässt sich aber über Konfiguration ändern! Sie können Ihre eigene Authentifizierungsfunktion mitbringen, die zum Beispiel ein LDAP-Verzeichnis abfragt, eine Textdatei liest oder einfach würfelt. Ihnen überlassen. Ebenso lässt sich die Autorisierungsfunktion ändern. Die Default-Autorisierungsfunktion sagt zu allem „ja“, Sie können aber jede Abfrage prüfen, die ein Client ausführen will, die Abfrage mit dem zuvor genutzten Authentifizierungsstring korrelieren usw. Diese Callbacks können sogar schlichte SQL-Makros sein! Details stehen in unserer Dokumentation.

Default-Port

Standardmäßig hört ein Quack-Server auf Port 9494, die Zahl 94 ist leicht zu merken als das Jahr, in dem Netscape Navigator erschien.

Benchmarks

Wir haben zwei Benchmarks aufgesetzt, um das Quack-Protokoll zu zeigen. Die Benchmarks liefen auf AWS-VMs mit Ubuntu auf Arm. Wir haben den Instanztyp m8g.2xlarge gewählt, 8 vCPUs und 32 GB RAM und, wichtig, „bis zu 15 Gbps“ Netzbandbreite. Wir haben ein reales Szenario nachgestellt, in dem Client und Server im selben Rechenzentrum, aber auf verschiedenen Maschinen liegen. Beide Instanzen lagen in derselben „Availability Zone“. Die Ping-Zeit zwischen den Instanzen lag im Mittel bei etwa 0,280 ms.

Bulk-Transfer

Der erste Benchmark testet Bulk-Transfer, den Fall, in dem eine ziemlich große Zahl von Zeilen über das Datenbankprotokoll übertragen werden soll. Wer das oben verlinkte Paper gelesen hat, weiß, dass traditionelle Datenbankprotokolle hier zu kämpfen hatten. Wir vergleichen Quack mit zwei Systemen: dem weit verbreiteten PostgreSQL-Protokoll und dem neueren Arrow-Flight-SQL-Protokoll. Arrow Flight kommt vom GizmoSQL-Server, der intern ebenfalls DuckDB nutzt. Wir übertragen eine steigende Zahl von Zeilen der TPC-H-Tabelle lineitem, bis zu stattlichen 60 Millionen Zeilen (76 GB im CSV-Format!) und berichten die mediane Wall-Clock-Zeit über 5 Läufe. Wir erwarten, dass die modernen, bulk-orientierten Protokolle das PostgreSQL-Protokoll deutlich hinter sich lassen. Die Ergebnisse:

Laufzeiten von Bulk-Transfer-Operationen (niedriger ist besser)
Bulk transfer performance Bulk transfer performance
Möchten Sie die Ergebnisse als Tabelle sehen? Hier klicken.
Zeilen DuckDB Quack Arrow Flight PostgreSQL
100k 0.07 s 0.07 s 0.20 s
1M 0.24 s 0.38 s 2.20 s
10M 0.89 s 2.90 s 25.64 s
60M 4.94 s 17.40 s 158.37 s

Man sieht, dass Quack beim Bulk-Result-Set-Transfer gut dasteht und die 60 Millionen Zeilen in unter 5 Sekunden überträgt! Selbst das zweckgebaute Arrow-Flight-SQL-Protokoll kann hier nicht mithalten, und Postgres’ zeilenbasiertes Protokoll ist generell ziemlich chancenlos.

Der Fairness halber: Die Standard-PostgreSQL-Clients parallelisieren Reads nicht über mehrere Threads, Quack und Arrow schon. Unverhohlene Eigenwerbung: DuckDBs PostgreSQL-Client kann das in manchen Fällen ebenfalls!

Kleine Writes

Der zweite Benchmark testet kleine Appends. Das ist ein häufiger Anwendungsfall, etwa um Observability-Daten in einer zentralen DuckDB-Instanz zu bündeln. Das stresst das Datenbankprotokoll anders: Mehrere Roundtrips zwischen Client und Server für eine einzige Transaktion sind zum Beispiel ein Nachteil. Wir testen das, indem wir eine leere Tabelle mit derselben Struktur wie die TPC-H-Tabelle lineitem anlegen und dann randomisierte Werte einfügen, jede Zeile in einer eigenen INSERT-Transaktion. Die eingefügten Werte folgen grob der Verteilung des üblichen Benchmark-Generators. Wir haben eine steigende Zahl paralleler Threads für fünf Sekunden laufen lassen. Das Experiment haben wir fünfmal wiederholt und die medianen Transaktionen pro Sekunde berichtet.

Wir erwarten, dass ein stark transaktionsoptimiertes System wie PostgreSQL diesen Benchmark dominiert. Wir erwarten außerdem, dass das bulk-optimierte Arrow Flight hier nicht besonders gut abschneidet.

Durchsatz kleiner Writes (höher ist besser)
Small writes performance Small writes performance
Möchten Sie die Ergebnisse als Tabelle sehen? Hier klicken.
Threads DuckDB Quack Arrow Flight PostgreSQL
1 1,038 tx/s 469 tx/s 839 tx/s
2 1,956 tx/s 799 tx/s 1,094 tx/s
4 3,504 tx/s 1,224 tx/s 2,180 tx/s
8 5,434 tx/s 1,358 tx/s 4,320 tx/s

Überraschenderweise schlägt Quack PostgreSQL bis zu 8 parallelen Threads und erreicht eine maximale Transaktionsrate von etwa 5.500 Transaktionen pro Sekunde. Darüber hinaus treffen wir auf eine aktuelle Grenze von DuckDB selbst bei parallelen Inserts pro Sekunde in dieselbe Tabelle. PostgreSQL skaliert hier besser, das wollen wir in nächster Zeit anschauen. Arrow Flight schlägt sich nicht besonders, etwa halb so schnell wie Postgres, wie erwartet.

Die Benchmark-Skripte sind online verfügbar.

Fazit

Heute haben wir Quack veröffentlicht, ein Client-Server-Protokoll für DuckDB zusammen mit einer ersten Implementierung als DuckDB-Erweiterung. Quack schaltet ein volles Multiplayer-Erlebnis mit DuckDB frei: Mehrere getrennte Prozesse – lokal oder remote – können Tabelleninhalte jetzt parallel ändern, ohne sich gegenseitig auszusperren. Und obwohl ein Teil davon schon mit DuckLake möglich war, macht Quack das deutlich einfacher und liefert deutlich höhere Performance.

Anwendungsfälle

Mit Quack kann DuckDB in einer ganzen Reihe neuer Anwendungsfälle nützlich sein, in denen das Zentralisieren von Zustand wichtiger ist als hyperlokales Abfragen. Wir haben schon lernen müssen, dass Daten mit dem Aufkommen von Data Lakes nicht immer lokal sind. Apropos Lakes: Quack wird auch in DuckLake integriert, sodass DuckDB selbst ein remote erreichbarer Catalog-Server sein kann. Das erschließt neue Fähigkeiten, z. B. für Data Inlining. Weitere Fragen beantwortet die Quack-FAQ.

Insgesamt bewegt sich DuckDB weiter aus seiner anfänglichen Nische einer In-Process-Datenbank für interaktive Analytics hinaus, hin zu einem Kernbaustein moderner Datenarchitektur. Wir spielen schon eine Weile mit Quack und sind gespannt, was Sie damit bauen. Vorschläge, wie Quack besser werden könnte, gerne her damit! Und hey, die MythBusters haben schon bewiesen, dass das Quaken einer Ente ein Echo hat, schauen wir mal, welcher Lärm daraus wird.

Nächste Schritte

Es gibt natürlich noch viel zu tun. Zuerst integrieren wir Quack in DuckLake, sodass ein entfernter DuckDB-Server als DuckLake-Katalog dienen kann! Wir erwarten davon einen deutlichen Performance-Gewinn, besonders mit Inlining. Als Nächstes polieren wir Quack in den kommenden Monaten und veröffentlichen ein erstes Produktionsrelease zusammen mit DuckDB v2.0, wenn es im Herbst dieses Jahres kommt. Wir planen zum Beispiel Auto-Installation und Auto-Loading der Quack-Erweiterung, sobald sie gebraucht wird. Mit unserem neuen Parser wollen wir außerdem die Syntax für das Sprechen mit entfernten SQL-Datenbanken aus DuckDB verbessern. Auf der Core-DuckDB-Seite wollen wir die erreichbaren Transaktionen pro Sekunde deutlich erhöhen, damit wir Transaktionen weit über acht parallele Threads hinaus skalieren.

Weiter voraus denken wir darüber nach, Erweiterungen des Quack-Protokolls über Authentifizierung und Autorisierung hinaus zuzulassen, zum Beispiel indem DuckDB-Erweiterungen neue Protokollnachrichten und den Code zu ihrer Behandlung ergänzen. Und wir denken auch über ein Replikationsprotokoll auf Quack nach, sodass Änderungen an einer DuckDB-Instanz auf andere Server repliziert werden können, etwa um einen Cluster von Read Replicas aufzusetzen.

Wenn Sie mehr über Quack erfahren wollen – und über seine erste Adoption – kommen Sie zu unserer Community-Konferenz, DuckCon #7, am 24. Juni. DuckCon beginnt mit dem Vortrag „State of the Duck“ der DuckDB-Mitgründer. Sie können vor Ort dabei sein oder den Livestream auf YouTube schauen.

PS: Wir haben eine eigene Seite für das Quack-Projekt, schauen Sie unbedingt vorbei.

Danksagungen

Wir danken Boaz Leskes von MotherDuck dafür, dass er die Lessons Learned aus dem Bau des MotherDuck-Protokolls mit uns geteilt hat. Wir danken außerdem Philip Moore von GizmoSQL / GizmoData, der uns den Weg schon geebnet und gezeigt hat, dass Client-Server-DuckDB sich sehr lohnt.

Anhang: Warum nicht Arrow Flight SQL?

Wir müssen auch einen der wenigen Elefanten im Raum ansprechen: Warum um alles in der Welt haben wir nicht das bestehende Arrow-Flight-SQL-Protokoll genutzt? Es ist da. Es ist verfügbar. Es gibt bestehende Implementierungen. Wir sehen den Wert in Arrow und verwandten Projekten wie ADBC: Sie sind Austausch-APIs wie ODBC und JDBC vor ihnen, um Reibung beim Datenaustausch zwischen Systemen zu senken. Und das funktioniert ziemlich gut.

Wir sind aber auch vorsichtig damit, Austauschformate wie Arrow innerhalb von DuckDB zu nutzen. Und obwohl DuckDBs interne Strukturen für Query-Zwischenstände in mancher Hinsicht nahe an Arrow sind, sind sie in anderer Hinsicht ziemlich anders. Wir haben das Gefühl, dass wir uns, um in Datensystemen weiter innovieren zu können, nicht von Formaten einschränken lassen dürfen, die extern kontrolliert werden. Deshalb nutzen wir in Quack unsere eigene Serialisierung. Wenn wir einen neuen Datentyp oder eine neue Protokollnachricht ergänzen wollen, können wir morgen ausliefern.

Tief unten steckt außerdem eine folgenreiche Designentscheidung in Arrow Flight SQL: Jede einzelne Abfrage braucht mindestens zwei Protokoll-Roundtrips, CommandStatementQuery und DoGet. Das ist nicht ideal für kleine Updates wie in unserem zweiten Experiment, besonders in Umgebungen mit höherer Latenz. Wie erwähnt haben wir Quack so entworfen, dass kleine Abfragen Query-Ausführung und Result-Fetching in einem einzigen Roundtrip erledigen können.