The 400ms warm tier: parking a database without breaking single-writer
The pod exists, the container runs, and Postgres hasn't started — it's blocked polling a gate on the gateway via bash /dev/tcp. Opening the gate IS the wake.
Part 5, closing the scale-to-zero Postgres series.
By part 1's measurements, a cold database wake costs ~2.5 seconds — and almost none of it is the database. Neon's stateless compute attaches in ~150ms; the rest is kubelet sandbox setup, container starts, and probe propagation. You can't tune away pod creation. But you can stop creating the pod.
The gated pod
The warm tier is a second compute Deployment whose pod always exists but whose entrypoint blocks before starting Postgres:
# entrypoint-warm.sh — parked until the gateway says go
until (exec 3<>"/dev/tcp/${GATE_HOST}/${GATE_PORT}") 2>/dev/null; do
sleep 0.2
done
exec /compute-files/entrypoint.sh # the normal attach pathTwo details I like about this. First, the polling target is a gate port on
the gateway itself — a net.Listener the gateway opens and closes in-process.
Gate open means the listener accepts (each accept releases one waiter); gate
closed means the listener doesn't exist. No ConfigMap propagation delays (a
kubelet sync can take a minute — that's a wake path, so it's disqualified), no
kubectl exec (the review that killed the prototype's shell harness was
right to), no sidecars. Second, the poll uses bash's /dev/tcp because the
stock compute image ships no curl — the mechanism bends to the image, not the
other way around.
Waking is opening the gate: no scheduling, no image pull, no container start. Just the ~150ms attach plus one poll interval. Measured: 413ms p50, 206ms best, every percentile under a second. Re-parking is equally clean: close the gate and delete the pod — the Deployment respawns it, and the fresh replica blocks on the gate again. Parked.
The invariant that makes this dangerous
Here's the problem hiding in the design: the warm pod and the cold Deployment's pod attach to the same timeline. Neon's architecture is strictly single-writer per timeline — two attached primaries is not "degraded," it's the one corruption class the whole storage model exists to prevent.
So the gate has a guard. Before opening, the gateway asks the Kubernetes API two questions and requires both answers to be zero: the cold Deployment's replica count, and — separately — the count of its pods including Terminating ones. That second clause is earned scar tissue: a draining pod still holds the timeline for its grace period, and "replicas: 0" says nothing about it. Any API error refuses the gate — when unsure whether another writer exists, the answer is no.
The test I care most about in the entire repository is the negative one:
With the cold compute running, the gate must refuse to open.
It's verified twice — as a Go table test with a fake API, and live in the drill, which deliberately scales the cold compute up and asserts the warm connection fails. Positive tests prove features work; this negative test proves the feature can't destroy the database. Only one of those keeps you employed.
Honest pricing, honest naming
A parked pod reserves its requests around the clock — 256Mi of RAM and a CPU
sliver, per database, forever. So the docs refuse to call it scale-to-zero;
it's a warm-RAM tier, opt-in per app, and it ships disabled
(replicas: 0) so the default posture stays truly zero. The tier table an app
team reads is two columns: cold-zero (wake ~2.5s, idle cost 0) vs warm (wake
~0.4s, idle cost one parked pod). Nothing in the application changes either
way — same DSN, same driver; the only observable difference is the first
connection after idle.
That honesty had a second payoff I didn't anticipate: the warm tier's existence is what settled the foundation debate in part 3. Pod-recreation architectures (hibernation) bottom out at 4–5s; a parked stateless compute bottoms out at the attach. The tier isn't a bonus feature — it's the structural argument.
Series wrap: what five parts add up to
- Part 1: separated storage makes the database disposable; the platform is glue around that fact.
- Part 2: the glue is a proxy that speaks 100 bytes of protocol and treats its own sleep logic as distributed-systems code.
- Part 3: foundations get chosen by same-harness measurement, and ship with kill criteria.
- Part 4: DR is only real when rehearsed — and sometimes real means a crc32c trailer.
- Part 5: the last 2 seconds of latency were never the database; park the pod, guard the invariant, price the RAM honestly.
Everything is drill-measured with receipts in the repo's BENCHMARKS.md —
getknext-dev/scale-zero-pg,
built on Neon's open source. The
platform now pairs with knext so the app
and its database sleep and wake together — which was the point all along.