DEV Community

authagonal
authagonal

Posted on • Originally published at authagonal.io

Scale-to-zero can't count to one

The advice always had the same shape. Your auth traffic is spiky. Your Kubernetes cluster sits mostly quiet. Azure Container Apps scales to zero and bills you for what you use. You are paying around the clock for a cluster that spends most of its life waiting.

We took it seriously enough to cost the migration properly. Then we killed it. Not because of the price of the cutover, and not because of cold starts, although cold starts alone should have been enough. We killed it because the platform's vocabulary is missing a number, and it's the only number our auth core actually cares about.

Serverless counts to zero and to N

Scale-to-zero runtimes know two numbers. Zero, when nobody is calling. N, when they are, with N floating on demand. For stateless request handling that vocabulary is perfect, which is why the advice sounds so right. Most web backends really are that shape: every request independent, every instance disposable, zero traffic deserving a zero bill.

An auth system's public face looks like that too. Token endpoints, login pages, JWKS. Stateless, spiky, cache-friendly.

Underneath it is a layer that is none of those things.

The number is one

Below the endpoints, our backplane runs a coordination layer. At any moment exactly one replica holds the cluster leadership lease (a blob lease these days, not gossip, which is a story for another post) and runs the singleton jobs: the retention sweep, the webhook delivery pass, the work where two concurrent runners means double-fired side effects and zero runners means things silently never happen.

The number that layer needs is one. Not zero when things are quiet. Not N under load. One, held continuously, with a verifiable handover when the holder dies. Scale-to-zero has no way to say "one, always". It can say "at least one while there's traffic", which is a different promise, and the difference between those two promises is exactly the failure mode a leader election exists to prevent.

This is why the utilization graph was lying to us. A quiet auth cluster is not doing nothing. It is holding a lease, keeping signing keys hot, and standing ready to answer the next token validation in single-digit milliseconds. Quiet and idle are different states, and billing dashboards only show you one of them.

You can fake it, and that's worse

You can force "exactly one" onto a serverless runtime. Pin minimum replicas to one and you have bought an always-on server on a platform whose whole instinct is to take quiet instances away from you. Every scale event, every platform-initiated restart, every revision swap becomes a leadership question you don't control the timing of. We would be fighting the runtime's core behavior to preserve ours, forever, and paying for the privilege.

And the failure lands on the worst possible person. The request that pays a cold start is a human trying to log in. "Your sign-in was slow because our auth service was asleep" is not a sentence anyone should ship.

The bill had a boring fix

What about the money that started all this? It fell to a scheduler and a SKU. The dev cluster now deallocates when nobody is using it, and the node pool moved to a cheaper SKU. One paragraph is all that deserves, and that's the point: "stop paying for idle" is a goal, not an architecture, and most of the goal was reachable with a config change instead of a re-platform.

The shape test

The rule we kept: match the runtime to the workload's actual shape, not to its traffic graph. Serverless rewards stateless and bursty. It punishes anything that must hold a continuous, exclusive role, and an auth core that guards signing keys and elects who runs the destructive jobs is the purest example of that second kind we know.

We're not against serverless. Plenty of our stateless surface would live happily on it. But the layer that has to count to exactly one is staying on boring, always-on, inspectable infrastructure.

That layer is also precisely what you stop operating when you run auth on Authagonal instead of running the backplane yourself.

Top comments (0)