Why R2 specifically

On a disaggregated engine, a cache miss is a read out of object storage — billed as egress on S3/GCS. So a cold cache costs more on those providers. R2 charges zero egress, which removes that cost structurally. That makes it a natural fit for:

  • Read-heavy workloads with a large working set that doesn't fully fit in cache.
  • Frequently cold-started / scale-to-zero databases (each cold start re-reads pages).
  • Many branches read concurrently off one base.

The engine, durability model, and API are otherwise identical to the S3 backend.

Open it

import { open } from "@twilldb/bun";
using db = open("r2://my-bucket/mydb");
cargo run -p twill-server -- --listen 0.0.0.0:5433 --db r2://my-bucket/mydb

Credentials

R2 uses S3-style access keys (create an R2 API token in the Cloudflare dashboard), supplied via the standard variables. The R2 S3 endpoint is your account-specific URL; set it through your deployment configuration.

export AWS_ACCESS_KEY_ID=...        # R2 access key id
export AWS_SECRET_ACCESS_KEY=...    # R2 secret access key
# R2 S3 endpoint: https://<account-id>.r2.cloudflarestorage.com  (set via deploy config)

Secrets stay in the environment

Keep R2 tokens in environment configuration, never in the connection string or the repo.

What you gain (same as S3, plus zero egress)

  • Scale-to-zero — stateless compute idling to nothing; cold starts warm by WAL replay. See Scale-to-zero.
  • Durable single-writer lease — CAS-epoch fencing against split brain.
  • Copy-on-write branching — child key-prefix overlays, no page copies. See Branching.
  • Zero egress — cache misses and cold reads don't incur per-GB transfer charges.

Note on deployment

R2 also pairs with the exploratory WASM / Cloudflare Workers deployment track (a parallel deliverable, not the native critical path). On the native server/embedded path documented here, R2 is simply selected by the r2:// scheme.

Twill DB documentation · Licensed under BUSL-1.1. · Author