· Protocolzone
On a Saturday afternoon, every wagering brand we run peaks in the same minute. The metro meeting that matters most goes off, and the load on one tenant is the load on all of them: bets, price updates, scratchings and cashouts arriving together, because the punters are all watching the same race. Six days a week the same estate sits at a fraction of that. That traffic shape decided the cloud architecture more than any preference did.
This post is drawn from two systems we build and operate, rather than from a reference architecture. One is a multi-tenant tote and fixed-odds betting platform running on Google Kubernetes Engine (GKE) on Google Cloud Platform. The other was a tipping app, BetrBets, that ran serverless on AWS: Lambda behind API Gateway, with DynamoDB for state. They sit on opposite ends of the container-versus-serverless decision, and both are correct, because they carry different load.
The two shapes
Start with the load, because the load is the whole argument.
The wagering platform has a steady baseline and a sharp, synchronised peak. Steady because there is always live racing somewhere and always a book to run; synchronised because Australian racing concentrates demand: a Saturday metro card pulls every tenant to its peak at once. Tenant demand correlates across brands, so you cannot count on one brand being quiet while another is busy. They are all busy together.
The tipping app had a different shape entirely: near-zero between meetings, a spike around each jump as users opened the app to check tips, then back to near-zero. Event-driven, and idle most of the week.
The wagering platform: a synchronised peak wants a shared cluster
Correlated demand sounds like the case against consolidation — if everyone peaks together, where is the saving? The saving is that a shared cluster provisions headroom once, for the aggregate peak, instead of N times for N separate stacks each carrying its own peak buffer. On GKE, horizontal pod autoscaling adds replicas across the estate for the Saturday window and removes them after. The idle cost the rest of the week is one cluster’s worth, not N.
That is the consolidation economics stated in infrastructure terms: one multi-tenant estate instead of a stack per tenant. It is also why tenant onboarding is a Helm operation measured in minutes rather than a new environment build. A new tenant is configuration onto machinery that already exists and already scales.
Containers earn their place here for reasons beyond the peak. The bet path holds long-lived state and long-lived connections: Kafka consumers that must stay subscribed, connection pools into ScyllaDB and Cassandra, in-process caches that are expensive to warm. Those want a process that stays up rather than one summoned per request.
We run this on GKE specifically, meaning managed Kubernetes, and it is worth being plain about what we did not choose. We do not run EC2 as primary compute, and we do not run ECS. Self-managed EC2 would mean building and owning the orchestration we get from Kubernetes for free; ECS would tie the workload definition to one cloud and cost us Helm, which is how tenant onboarding stays a minutes-long operation. The Kubernetes API and the Helm packaging around it are the reason the estate is portable and repeatable, so we chose GKE deliberately rather than falling into it.
Why not serverless for the platform
Serverless would handle the Saturday peak — scaling per request is exactly what it is good at. It is the wrong tool anyway. A cold start in the bet path during a metro card is a latency spike at the worst possible moment. Per-invocation connection churn fights the persistent Kafka and ScyllaDB connections the engine depends on. And a platform with a steady baseline pays for that baseline either way, so the headline serverless saving, scale to zero, is one this workload never gets to collect. You do not scale a book to zero.
The tipping app: event-driven spikes scale to zero
BetrBets was the mirror image. Load arrived in bursts tied to race times and fell away to almost nothing between them. Running that on a cluster means paying to keep capacity warm for six mostly-idle days to serve a few busy hours, plus the operational cost of owning the cluster.
So it ran serverless: Lambda for compute, API Gateway at the edge, DynamoDB for state. Between meetings it cost close to nothing because there was almost nothing to run. The bursts fanned out into concurrent Lambda invocations without a capacity decision on our side. Scaling to zero is a real cost lever, and here it was the right one because the traffic genuinely went to zero.
On AWS the container options were there: ECS, or containers on EC2. We did not take them for the tipping app. A warm cluster to absorb an intermittent, stateless workload is money spent on idle capacity plus an orchestration layer to babysit. The serverless fit removed both.
The factor engine: compute you can put down
The third case sits between the two. Our factor engine, which computes racing probabilities and ratings, runs on dynamic compute-node allocation: it claims nodes to compute a card and releases them when the work is done. The cost lever is the release. Probability computation is bursty and schedulable, so the wrong design is a fixed pool sized for the busiest card and left running through every quiet stretch between race meetings. Scaling the compute down when there is nothing to rate is where the money is saved.
The rule underneath all three
Match the compute model to the traffic shape, and be honest about the shape.
Reach for containers on a shared cluster when there is a steady baseline, when demand across tenants is correlated so pooled headroom pays off, and when the workload holds long-lived state and connections. Reach for serverless when load is genuinely intermittent and stateless, when it falls to zero often enough that scale-to-zero is real money, and when a cold start now and then is acceptable.
Reach for neither on autopilot. Serverless punishes a steady, stateful workload that cares about tail latency, hitting it with cold starts and connection churn. A Kubernetes cluster punishes an intermittent one with idle capacity and standing operational cost. The estate is mixed on purpose: GKE where the load is sustained and synchronised, serverless where it is bursty and disappears. The mistake is standardising on one because it is the one you already run.
- kubernetes
- gke
- serverless
- aws-lambda
- autoscaling
- cloud-architecture