Skip to content

Database Architecture Plan

Database Architecture Plan

Decision: PlanetScale PostgreSQL + Cloudflare Hyperdrive + Prisma ORM

Vendor Choice

PlanetScale was selected as the primary PostgreSQL provider because:

  • Official Cloudflare Workers partnership with dedicated integration tutorial
  • Native PostgreSQL v17/v18 (not Vitess/MySQL emulation)
  • Zero-downtime schema migrations via deploy requests
  • Multi-AZ HA with automatic failover from day one
  • High write throughput on NVMe SSD storage
  • Standard pg wire protocol — works with Hyperdrive and Prisma out of the box

The existing adblock-db database on PlanetScale is already linked to the Cloudflare Hyperdrive binding (HYPERDRIVE, id: 126a652809674e4abc722e9777ee4140).

Architecture

flowchart TD
    worker["Cloudflare Worker"] --> d1["D1 — L1 cache"]
    worker --> hyperdrive["Hyperdrive"]
    worker --> r2["R2 — blobs"]
    worker --> kv["KV — cache"]
    worker --> queue["Queue — async"]
    hyperdrive --> planetscale["PlanetScale PostgreSQL<br/>(source of truth)"]

Storage Tiers

TierTechnologyRoleLatency
L0KV (COMPILATION_CACHE, METRICS)Hot-path key-value cache~1-5ms
L1D1 (DB)Edge SQLite for structured cache lookups~1-10ms
L2Hyperdrive -> PlanetScalePrimary relational store (source of truth)~20-80ms
BlobR2 (FILTER_STORAGE)Large compiled outputs, raw filter content~5-50ms

Data Flow

OperationL0 (KV)L1 (D1)L2 (PlanetScale)Blob (R2)
Compile (cache hit)Read
Compile (cache miss)WriteRead metadata + Write eventRead blob
Store compiled outputWrite hashWrite metadataWrite blob
Auth (API key check)Read api_keys
Health monitoringRead/WriteWrite snapshots
Admin dashboardRead aggregates

Branching Strategy

PlanetScale supports database branches that map to git branches:

Git BranchPlanetScale BranchPurpose
mainmainProduction
dev / developdevelopmentDevelopment/staging
Feature branchesAd-hoc branchesSchema experimentation

Schema change workflow:

  1. Create a PlanetScale branch from main
  2. Apply migration on the branch: npx prisma migrate dev --name <description>
  3. Open a deploy request on PlanetScale (equivalent to a PR)
  4. Review schema diff, then merge
  5. Production picks up the change automatically

Synchronization Strategy

Schema sync: Prisma -> PlanetScale

Prisma is the single source of truth for schema. The prisma/schema.prisma file defines all models, and npx prisma migrate generates SQL migrations that are applied to PlanetScale.

flowchart TD
    edit["Developer edits schema.prisma"] --> migrate["npx prisma migrate dev (local)"]
    migrate --> push["git push (commit migration SQL)"]
    push --> ci["CI applies migration to PlanetScale dev branch"]
    ci --> deploy["Deploy request merges to main"]

Data sync: D1 <-> PlanetScale

D1 remains an L1 cache layer. It does NOT need to mirror PlanetScale exactly. Instead:

  • Reads: Worker checks D1 first. On miss, queries PlanetScale via Hyperdrive, then caches result in D1 with TTL.
  • Writes: Worker writes to PlanetScale (source of truth), then invalidates or updates D1 cache.
  • Stale data: D1 entries expire via application-level TTL (60-300s depending on data type).

Data sync: R2 <-> PlanetScale

R2 stores binary blobs (compiled filter lists, raw downloads). PlanetScale stores metadata + R2 object keys. No bidirectional sync needed — PlanetScale references R2 keys, R2 stores the content.

Connection Configuration

Production (Cloudflare Workers)

The worker connects to PlanetScale through Hyperdrive:

import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import { Pool } from 'pg';
function createPrisma(env: Env): PrismaClient {
const pool = new Pool({ connectionString: env.HYPERDRIVE.connectionString });
const adapter = new PrismaPg(pool);
return new PrismaClient({ adapter });
}

Local Development

Local dev uses a PostgreSQL instance (Docker or native) with a direct connection. Set credentials in .env.local (see .env.example):

DATABASE_URL="postgresql://<user>:<password>@127.0.0.1:5432/adblock_dev"

See local-dev.md for setup instructions.

Wrangler Hyperdrive

The wrangler.toml Hyperdrive section points to PlanetScale in production. For local dev, set the real connection string via env var (see .env.example):

Terminal window
# .env.local (gitignored)
CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="postgresql://<user>:<password>@127.0.0.1:5432/adblock_dev"

Migration Phases

See the GitHub issue #587 for the full phased migration plan. Summary:

  1. Phase 1 (this PR): Schema, config, documentation, local dev setup
  2. Phase 2: Create HyperdriveStorageAdapter implementing IStorageAdapter via Prisma + @prisma/adapter-pg
  3. Phase 3: Authentication system (users, API keys, sessions)
  4. Phase 4: Data migration from D1 to PlanetScale
  5. Phase 5: Cutover — PlanetScale as source of truth, D1 as L1 cache only

References