Skip to content

How it works

Three planes. Keep them separate or you couple storage to a machine again.

Control / data / compute

┌────────────┐     ┌─────────┐     ┌──────────────┐
│  Client    │────▶│  pdo    │────▶│  D1 control  │
└────────────┘     │  API    │     └──────────────┘
                   │         │
                   │         │ service binding
                   │         ▼
                   │    ┌──────────────┐     ┌─────────────┐
                   └───▶│  pdo-duckdb  │────▶│  R2 Parquet │
                        │  DuckDB-WASM │     │  org_*/…    │
                        └──────────────┘     └─────────────┘

Control plane (D1)

Orgs, ducklings, placement, query metrics. Tiny rows. This is where D1 belongs.

An org is the tenancy and placement boundary. A duckling is a compute unit that can scale to zero (Durable Object for lifecycle; SQL runs elsewhere).

Data plane (R2)

org_acme/
  events/part-000.parquet
  customers/part-000.parquet

Table name = first directory under the org prefix. Catalog today is a directory listing, not Iceberg.

Durable bytes live here. Kill every Worker. Data remains.

Compute plane (DuckDB)

pdo-duckdb is a minified Worker running @ducklings/workers (DuckDB-WASM with Asyncify httpfs). Stock @duckdb/duckdb-wasm wants eval / dynamic compile. Workers refuse that. Ducklings ships a precompiled module.

On query:

  1. Auth + SQL policy on pdo
  2. Resolve org prefix from D1
  3. Service-bind to pdo-duckdb
  4. Engine ensures an R2 secret, builds VIEWs over read_parquet('r2://…') for tables named in the SQL
  5. Run query, return JSON

Why two Workers: the WASM is ~41MB / ~10 MiB gzip. Stuffing it into the UI app exceeds Cloudflare’s paid script limit. Split the engine.

Placement without copy

     org_acme

        ├── worker duckling  ──┐
        │                      ├──▶  r2://…/org_acme/**   (unchanged)
        └── native duckling  ──┘

Local lab (pnpm demo) flips Worker → native and checks Parquet content hashes. On Cloudflare, native graduation is still on the roadmap. The invariant is already the point: placement is metadata.

Query path, short

bash
curl -s -X POST https://pdo.b-christopher-3rd.workers.dev/v1/orgs/org_tpch/query \
  -H 'authorization: Bearer demo-token' \
  -H 'content-type: application/json' \
  -d '{"sql":"SELECT count(*) FROM lineitem"}'
# 600572

Warm groupbys on the sample orgs land around 150–300ms of engine wall time. Client latency is higher. Isolates and the service hop are real.

Security (honest)

P0 uses a shared demo bearer. SQL policy blocks ATTACH/COPY/INSTALL and URI escapes. The DuckDB Worker still holds account R2 keys. Fine for a demo. Not fine for hostile multi-tenant production. Per-org keys and scoped credentials are P1.

MotherDuck-shaped architecture. Not a warehouse.