Skip to content
Published on

DuckDB Gets a Client-Server Protocol — What Quack Changes and What It Doesn't

Share
Authors

Introduction — What It Means for an In-Process DB to Get a Server

DuckDB's identity has long fit in one sentence — no client, no server, no protocol, just low-level API calls. Since its first release in 2019, the DuckDB team has been fairly loud about this in-process architecture, and it has genuinely paid off. For workloads where an analyst pokes at their own data inside a Python notebook, or where SQL capability gets bolted onto an existing application, zero protocol overhead is an overwhelming advantage.

But on May 12, 2026, that same team announced Quack: The DuckDB Client-Server Protocol — a protocol for two DuckDB instances to talk to each other across a network. One side becomes the server, the other the client.

What makes this interesting isn't the performance numbers. It's the fact that a project which spent seven years holding "we are not client-server" as an architectural conviction has now bolted that on itself. The announcement's own wording is fairly candid about the shift — "In the end we care deeply about user experience and perhaps less about having the last word on architecture."

So instead of repeating "DuckDB is a server now!", this post looks at what actually became possible and what still isn't. The latter list is much longer.

Getting the Facts Straight First

Before wading through the hype, it helps to pin down the dates and versions.

  • May 12, 2026 — Quack announced. At this point it lived in the core_nightly repository, usable on the then-current release, DuckDB v1.5.2.
  • May 20, 2026 — In DuckDB v1.5.3, Quack was promoted to a core extension. It's now installed and loaded automatically and transparently on first use. The same release let DuckLake use Quack as its catalog database.
  • Status: beta. In the exact wording of the 1.5.3 release post — "Quack is still in beta state and breaking changes may happen in the protocol, in function names, etc."
  • Stabilization target: DuckDB v2.0. The Quack FAQ writes "as part of DuckDB v2.0 in September 2026," while the release calendar lists 2.0.0 as "Fall 2026" and explicitly notes these dates are tentative. The two pages phrase it slightly differently, so don't read September as fixed.

In other words, at the time you're reading this (July 2026), Quack is for prototyping. The FAQ's answer to "Is Quack production-ready?" is simply "Not yet."

What It Looks Like

From a user's perspective, the shape is surprisingly simple. Spin up two DuckDB instances, and start a server on one.

-- DuckDB #1 (server)
CALL quack_serve('quack:localhost', token = 'super_secret');
CREATE TABLE hello AS FROM VALUES ('world') v (s);
-- DuckDB #2 (client)
CREATE SECRET (TYPE quack, TOKEN 'super_secret');
ATTACH 'quack:localhost' AS remote;
FROM remote.hello;

Once attached, the remote table looks like a local one. DDL, writes, and transactions all pass through unchanged.

CREATE TABLE remote_db.t AS FROM range(10) r (i);  -- remote DDL
INSERT INTO remote_db.t VALUES (42);               -- remote write
FROM remote_db.t WHERE i = 42;                     -- filter also runs remotely
BEGIN; ...; COMMIT;                                -- transaction passthrough

There's also a way to fire a query in one shot without attaching.

FROM quack_query('quack:localhost', 'SELECT 42', token = 'MY_TOKEN');

Protocol Design — Why HTTP

A handful of design choices stand out here, all laid out in the official Overview docs.

HTTP-based. The announcement's reasoning is pragmatic rather than defensive — "It would be rather misguided not to build a database protocol on top of HTTP in 2026." The reason is straightforward: load balancing, authentication, firewalls, intrusion detection — HTTP is a layer that this entire body of infrastructure already knows how to handle. There's no need to operate a new custom wire transport. There's a side benefit too: the DuckDB-Wasm distribution can speak Quack natively, so a DuckDB running in the browser attaches directly to a DuckDB running on EC2.

application/duckdb serialization. They created a new MIME type, and the encoding reuses DuckDB's own internal serialization primitives — the same code path used for WAL files. Per the docs, this avoids routing through an interchange format, and complex types like nested types, decimals, and intervals cross over losslessly.

Client-driven request-response. Every interaction is initiated by the client. There's no server-initiated push.

One round trip per query. After the connection handshake, a single query completes in one request-response pair. Large results stream back in chunks via subsequent FETCH requests, which can be parallelized across multiple threads.

Default port 9494. The team's explanation: 94 is the year Netscape Navigator came out, which makes it easy to remember.

There's a fun detail here about the DuckDB team drawing on their own history. These are the people who wrote Don't Hold My Data Hostage — A Case for Client Protocol Redesign (Raasveldt & Mühleisen, VLDB 2017) back in 2017 — a paper that measured just how expensive it is to move large result sets from a database to a client, and why existing protocols were inefficient. Nine years later, the authors of that paper got to design a protocol from scratch.

Why Not Arrow Flight SQL

An obvious question, and the announcement post answers it with a dedicated appendix. The reasoning splits into two threads.

One is technical. The team's point is that Arrow Flight SQL has "one fateful design decision" — "every single query requires at least two protocol round trips, CommandStatementQuery and DoGet." Two round trips is a disadvantage for small updates, especially in high-latency environments. This is exactly why Quack fixated on a single round trip.

The other is political. And this one is more candid — "We feel that in order to be able to keep innovating in data systems, we cannot allow ourselves to be restricted by formats that are controlled externally." In other words, they don't want to be tied to a format controlled by someone else. If they want to add a new data type or protocol message, they want to be able to ship it tomorrow.

This isn't a question of technical superiority so much as a governance preference. The team does credit Arrow and ADBC's value — as a successor to ODBC/JDBC, as an interchange API that reduces friction moving data between systems, they write that it "works pretty well." But there's caution about using an interchange format inside DuckDB itself, and the FAQ sums this up as a "clean slate implementation from the core DuckDB team" that "does not have any third-party dependencies."

As a reader, it's worth taking this reasoning at face value while still tallying the implications. Quack is DuckDB-only. Both client and server must be DuckDB. The advantage Arrow Flight SQL offers — heterogeneous systems speaking the same protocol — is something Quack deliberately gives up by design. Third-party alternatives that fill that gap still exist, like the airport extension and GizmoSQL, and the announcement post thanks GizmoSQL's Philip Moore for "paving this path first."

Benchmarks — Starting From the Fact That They're Vendor Self-Measured

Now for the numbers. Let's nail this down first. Every benchmark below was designed and run by the DuckDB team itself — a vendor self-measurement. In a post announcing their own protocol, their own protocol wins. That's not surprising, and it isn't inherently dishonest either — but you need to look at what was measured and what wasn't.

The test environment is specified: AWS m8g.2xlarge (8 vCPUs, 32GB RAM, network "up to 15 Gbps"), Ubuntu on Arm, client and server on different machines in the same availability zone, average inter-instance ping about 0.280 ms. Median of 5 runs. The benchmark scripts are public too. As far as vendor benchmarks go, this is fairly diligent.

Bulk transfer — wall-clock time to transfer the TPC-H lineitem table at increasing row counts (lower is better).

RowsDuckDB QuackArrow FlightPostgreSQL
100k0.07 s0.07 s0.20 s
1M0.24 s0.38 s2.20 s
10M0.89 s2.90 s25.64 s
60M4.94 s17.40 s158.37 s

Small writes — inserting one row at a time, each its own INSERT transaction, into an empty table with the same schema as lineitem, for 5 seconds, at increasing thread counts; transactions per second (higher is better).

ThreadsDuckDB QuackArrow FlightPostgreSQL
11,038 tx/s469 tx/s839 tx/s
21,956 tx/s799 tx/s1,094 tx/s
43,504 tx/s1,224 tx/s2,180 tx/s
85,434 tx/s1,358 tx/s4,320 tx/s

How Far to Trust These Numbers

There's one caveat the team disclosed itself. Below the bulk-transfer table it says — "In fairness we have to mention that the standard PostgreSQL clients do not parallelize reads over multiple threads, but Quack and Arrow can." So PostgreSQL's 158 seconds carries a built-in handicap: "the standard client reads single-threaded." Credit where due — the announcement wrote this down itself.

The Arrow Flight side also needs its conditions checked. The Arrow Flight comparison point was served by a GizmoSQL server, and that server uses DuckDB internally. So this isn't "the theoretical best case of the Arrow Flight protocol" — it's "one particular implementation." It's closer to an implementation comparison than a protocol comparison.

The write benchmark's framing is generously drawn. A workload that INSERTs one row at a time as individual transactions is precisely what PostgreSQL was born to do, and there's no reason a columnar analytical engine should be good at it. And yet Quack wins up through 8 threads. Here the team shoots itself in the foot on its own terms — "Beyond that, we hit a current limitation of DuckDB itself in concurrent insertions per second into the same table. PostgreSQL scales better here, which is something to look into for us in the near future." They're saying, in their own words, that it hits a ceiling around 5,500 tx/s, and beyond that PostgreSQL scales better. That's exactly why the benchmark table stops at 8 threads.

One piece of arithmetic. The announcement introduces the 60M rows as "76 GB in CSV format!" But if 76GB moved in 4.94 seconds, that's 15.4 GB/s, roughly 123 Gbps. The stated link is "up to 15 Gbps," or about 1.9 GB/s — physically impossible. Streaming 76GB over that link would take at least 40 seconds. The conclusion isn't hard to reach — the 76GB figure is meant to give a sense of the dataset's CSV-equivalent size, not bytes that actually crossed the wire. Quack's binary representation is far more compact than CSV. The announcement didn't lie, but if you put the two numbers side by side and picture "15GB per second," you've been misled.

What wasn't measured. Everything is same-availability-zone, 0.280ms ping. There are no numbers for WAN or high-latency environments. This matters more than it looks, because the whole reason Quack invested in a single round trip was "latency-sensitive environments." The exact measurement where that design would actually pay off — high-latency conditions — is precisely what's missing. There's also no cost figure for turning on TLS (the default is plaintext, so the benchmarks are likely plaintext too). And no mixed workload with concurrent reads and writes.

To sum up — the claim that bulk transfer is fast is credible, backed by a reproducible script, and the direction isn't surprising (columnar binary transfer beating Postgres's row-based protocol is also the conclusion of that 2017 paper). But you shouldn't read this as "Quack is a faster database than PostgreSQL." Those tables are protocol transfer benchmarks, not query-engine benchmarks.

Security Defaults — Where to Be Most Careful

The first line of the Security docs is alarming. A Quack server exposes the entire SQL surface of the underlying DuckDB instance — including read and write access to every table that session can see.

That's why the defaults are set conservatively.

  • The server generates a random auth token on startup, and the client must present it on every connection.
  • The server binds to localhost only. To open it to an externally reachable address, you must explicitly pass allow_other_hostname => true.
  • TLS is not handled by the server itself. The reasoning is that pulling TLS infrastructure into localhost communication only adds dependencies with no real benefit.

And the announcement's recommendation is blunt — "We do not recommend opening up a DuckDB Quack endpoint directly to the Internet." Instead, it says to put a proven HTTP reverse proxy like nginx in front and terminate TLS there. On the client side, non-local URIs default to assuming HTTPS (overridable).

The authentication/authorization model is, in true DuckDB fashion, pushed out to extensions. The announcement's wording is honest — "We are likely unable to capture everyone's use case, certainly not in a first release. The smart thing is therefore not to try." So it ships default authentication and default "allow everything" authorization functions, but both can be swapped out with user code. Query LDAP, read a text file, roll a die — up to you. The callbacks can just be SQL macros.

The practical translation: the default settings are safe, but the moment you make it useful you start flipping off safety switches one at a time. The instant you turn on allow_other_hostname => true, you've opened a "plaintext HTTP endpoint with read/write access to every table that DuckDB session can see," and the authorization function says "yes" to every query by default. Treating the proxy and the authorization callback as mandatory, not optional, is the right way to read this.

What Quack Doesn't Do

This is the core of this post. These are the items the FAQ was kind enough to answer explicitly.

It doesn't do distributed query processing. Straight from the FAQ — "Does DuckDB with Quack support distributed query processing? Currently, DuckDB with Quack does not support distributed query processing." Quack is a structure where multiple clients attach to a single server. It's not multiple nodes splitting up one query. The problem of "the query doesn't fit on one machine" is not solved by Quack. There was community reaction right after the announcement suggesting "this solves horizontal scaling" (one of the reactions quoted in InfoQ's coverage), but that's not what the docs say. Quack scales access, not computation.

There's a ceiling on capacity. The FAQ's DuckLake comparison item gives a number — a Quack server uses DuckDB's native DB format, which "scale[s] up to a few terabytes." If you need petabyte scale, the docs point you directly to DuckLake (or Iceberg-style systems).

There's a ceiling on write concurrency. The roughly 5,500 tx/s at 8 threads seen above, beyond which you hit DuckDB's own limitation. The team says it plans to improve this ("we plan to work on greatly increasing the transactions per second achievable"), but that's future tense. If you need tens of thousands of writes per second right now, this isn't the answer. The FAQ does answer "Yes" to "can this be used for OLTP workloads," but the caveat attached is "a few thousand writes per second on a server with 8 CPUs and 32 GB RAM." Whether "a few thousand" matches your numbers is something only you can know.

It's still beta. The protocol, function names, and default settings can all still break.

And one important thing in the opposite direction. The in-process model isn't going anywhere. The FAQ nails this down too — "Yes, DuckDB as an in-process database will continue working just as it did before, and we will treat the in-process deployment model as a first-class citizen in the DuckDB ecosystem." Quack is an addition, not a pivot. If you're the person scanning a Parquet file in a notebook, you don't need to read this post.

Quack vs. DuckLake — Easy to Confuse

Both claim to be "multiplayer DuckDB," both are extensions, both allow multiple writers. So it's easy to mix them up. Here's the difference the FAQ lays out.

QuackDuckLake
DuckDB-only?Yes (DuckDB talks to DuckDB)No (also supports Spark, DataFusion, Trino, etc.)
StorageDuckDB native DB formatObject storage + Parquet
ScaleA few terabytesPetabyte-scale possible
SetupInstall extension on both sidesCentral catalog DB + object storage

The announcement writes, "part of this could also already be achieved with DuckLake, Quack makes this far simpler and provides far higher performance." But that's specifically about the narrow axis of concurrent write access, and the two tools don't share the same purpose.

And the two aren't mutually exclusive. Starting with v1.5.3, DuckLake can use Quack as its catalog database. The FAQ's recommendation is clear — if your data is huge or you need time travel, use DuckLake, but access it through a DuckDB instance served over Quack. This is also the direction the announcement previews for what's next: the DuckDB server itself becoming a remotely accessible DuckLake catalog server, with expected gains in data-inlining performance.

DuckLake's basic idea — putting metadata in a SQL database instead of a mountain of JSON and Avro files — was already covered in DuckDB in Practice.

So, Should You Use It

When it earns its keep

  • Multiple processes need to read and write the same DuckDB database concurrently. This is the first scenario the FAQ lists. The announcement's example is concrete — several processes collecting telemetry insert into the same DB while a dashboard queries that same table at the same time. Until now this just wasn't possible with DuckDB, and people either hand-rolled custom RPC or moved to Postgres.
  • Data lives on a server and you want to bring computation close to it. A big server does the processing, and only the results get viewed on a laptop.
  • You frequently pull large result sets down to the client. That's exactly where those bulk-transfer numbers pay off.
  • You need a browser (Wasm) to attach to a remote DuckDB. A direct dividend of the HTTP-based choice.
  • You've been hand-writing RPC to bolt client-server onto DuckDB. You can delete that code now.

Not yet

  • Production. It's beta. The team says so themselves.
  • Distributed query processing is required. Not there by design.
  • True OLTP. Tens of thousands of transactions per second, high write concurrency — use Postgres. Even the DuckDB team wrote that Postgres is better above 8 threads.
  • Petabyte scale. That's DuckLake or Iceberg territory.
  • In-process is already enough. Most DuckDB users fall here. There's no reason to give up zero protocol overhead.
  • Heterogeneous clients need to attach. Both sides of Quack must be DuckDB. A Java app or a Go service can't speak to it directly.

Closing

The most interesting thing about Quack isn't the protocol design — it's the judgment behind it. The DuckDB team built its brand on the in-process architecture. But so many people kept bolting client-server workarounds on top of it — custom RPC, GizmoSQL layering on Arrow Flight, MotherDuck's own protocol, even pg_duckdb stuffing DuckDB inside Postgres — that they eventually conceded "this is what people actually want." The announcement describes this process as "we bit the bullet, eventually, finally." Its own explanation — choosing user experience over architectural purity — is attached right there in the text.

Honestly stated, the result is narrow in scope. DuckDB didn't become a distributed query engine, and it didn't become a Postgres replacement. What it got is the ability for multiple DuckDB clients to concurrently read and write a single server holding a few terabytes. But calling it "just that" undersells it — that's exactly the reason people have spent seven years building workarounds.

And as always — judge this by your own workload, not by vendor benchmarks. Those tables are the DuckDB team's own measurements, taken under same-availability-zone, 0.280ms ping, plaintext HTTP, with the reproduction script published. A diligently measured vendor benchmark is still a vendor benchmark. Whether your latency, your TLS, your mixed workload produces the same picture is something worth measuring yourself once v2.0 ships.

References