Stories · Season 2 — Frontrow · Episode 02

The Ticket Page That Hugged the Database to Death

In which a band's TikTok goes viral, and Frontrow's database receives more love than it can survive.

The story

A comedian posted a fifteen-second clip about their upcoming tour. It did the thing. Eight million views overnight, and a meaningful fraction of those people did the same thing at the same time: they opened the Frontrow event page to see if tickets were still available.

The event page wasn’t even doing anything complicated. It showed the event name, the venue, the date, and “412 tickets left.” But to render that, the app ran the same handful of queries — for every single visitor. Tens of thousands of identical questions per second, all asking the database the same thing it had already answered a millisecond ago.

The database did what databases do under that kind of affection: it slowed, then queued, then fell over. And here’s the cruel part — when the read traffic took the database down, it took checkout down with it. People who were ready to buy couldn’t, because people who were merely looking had exhausted every connection. Frontrow was turning away money to serve window-shoppers.

Tobi, the founder, watched the revenue graph flatline during the single biggest traffic spike in company history. “We finally went viral,” he said, “and we couldn’t take anyone’s money.”

Why this is hard the traditional way

Reads are deceptively expensive at scale. A query that costs nothing once costs everything fifty thousand times a second. And the maddening part is that the answer barely changes — “412 tickets left” is true for whole seconds at a time, but the system recomputes it from scratch for every viewer.

The usual fix is to bolt on a cache by hand, and that’s where the second set of problems begins. Where does the cache key come from? When do you invalidate it? What happens when the cache is empty and a thousand requests all miss at once and all stampede the database simultaneously (the “thundering herd”)? Hand-rolled caching has a way of turning one hard problem into three.

How Relay changes the ending

In Relay, caching a read is a declaration, not a project. You mark the query [Cacheable], and the framework handles the key, the storage, and the part everyone forgets — single-flight stampede protection, so when the cache is cold, one request fills it while the rest wait, instead of all of them crushing the database at once.

[Cacheable(DurationSeconds = 5)]
public sealed record GetEventSummary(Guid EventId) : IQuery<EventSummary>;
// 50,000 viewers/second now cost ~one database query every 5 seconds.
// Checkout keeps its connections. The money keeps flowing.

Five seconds of staleness on “tickets left” is invisible to a human and a lifesaver to a database. Cache keys are deterministic and tenant-isolated out of the box, and when the cache backend itself has a bad day, Relay degrades gracefully — it falls back to the real query rather than failing the request. The window-shoppers get served from cache; the buyers get the database all to themselves.

What it costs you to ignore this

  • Success becomes an outage. The moment you’ve worked toward — going viral — is the moment you go down.
  • Reads starve writes. Lookers crowd out buyers, and you lose revenue at exactly the peak.
  • Hand-rolled caches rot. Stale data, cache-key bugs, and stampedes become a permanent maintenance tax.
  • You over-provision out of fear. Without caching, the only lever is “buy a bigger database,” which is the expensive way to solve a cheap problem.