Scaling series, Part 2. Part 1 was The Database Scaling Ladder.
If you've ever tried to buy a train ticket online before Eid in Bangladesh, you know the feeling.
It's 7:59 AM. Phone in one hand, the page open, thumb on refresh. At exactly 8:00 the tickets are released, and a very large part of the country presses the same button in the same second.
I don't think there's a sharper traffic spike anywhere in Bangladesh's digital landscape. During an Eid ticket sale, traffic jumps to more than 7× the average within the first five minutes. A smaller version happens every single morning at 8 AM, when the day's tickets are released. It's only about 2× the average, but it's just as steep.
The usual engineering reflex is: add more servers. This article argues that's only half the story, and maybe the smaller half.
You Can't Autoscale a Train
The spike

The chart is illustrative, shaped on the ratios above. Traffic sits near average, a "refresh storm" builds in the last few minutes before 8:00, and then demand goes almost straight up. There's no gentle ramp to warn an autoscaler.
That alone is a hard engineering problem. But it isn't the real problem.
The real problem: demand vs. supply

Here's the arithmetic that changes the whole conversation.
- On an Eid sale day, roughly 8 lakh (800,000) users try to buy tickets.
- The inventory for that day is roughly 30,000 seats.
- A single user can buy up to 4 seats in one transaction: one payment, one OTP, four seats.
So even if every seat went to a different person, only 30,000 of 800,000 users (3.75%) could succeed. If most buyers take four seats, as families travelling home for Eid usually do, that drops to around 7,500 buyers (under 1%).
Somewhere between 96% and 99% of the people on the site that morning will leave without a ticket. That's true however well the system performs.
This changes what "success" means. Scaling the hardware can make the journey smooth: pages load, payments don't time out, and there are fewer complaints and less public outcry. It cannot create seats. A perfectly scaled system still disappoints almost everyone who visits. It just disappoints them faster and more politely.
So every proposal below has to answer two questions, not one:
- Does it help the system survive the spike?
- What does it do to the experience of the 96–99% who won't get a ticket, and to the few who will?
With that lens, here are six ideas I'd put on the whiteboard, each with its usability cost.
Idea 1: A virtual waiting room
Instead of letting everyone hit the booking system at 08:00:00, put a waiting room in front of it. Visitors get a place in the queue, and the system admits them at a rate it knows it can handle.
How it helps the system: the backend sees a smooth, predictable flow instead of a wall.
The usability cost:
- Waiting feels like losing. Being told "you are number 214,568" at 8:00:05 is deflating, especially when people know seats are scarce.
- It's a black box. Users can't see what's happening behind the queue. Is it moving? Is it fair? Are other people skipping ahead? Without visibility, suspicion fills the gap.
- People try to game it. Users open multiple tabs and devices to "improve their chances," which can add load instead of reducing it.
- Ordering is a fairness debate. FIFO rewards the fastest connection. A randomised order is arguably fairer, but it feels like a lottery nobody signed up for.
What could soften it: a live position and estimated wait, a visible "seats remaining" counter, and above all closing the queue the moment seats run out, so nobody waits 40 minutes to learn there was never a chance.
Open question: Would users accept a waiting room if it were fully transparent, or does any queue feel worse than a slow page?
Idea 2: Move work out of the spike

Most of a booking doesn't need to happen at 08:00:00. Only the seat does.
The proposal: a "get ready" window before the sale. Between 7:00 and 7:59, users log in, verify their OTP, and save passenger details for up to four travellers. At 8:00 the only thing left is pick seats and pay.
How it helps the system: authentication, OTP SMS and profile writes get spread across an hour instead of crushed into five minutes. The SMS gateway, often a hidden bottleneck, gets breathing room.
The usability cost:
- It changes a habit people have followed for years. Users know the current flow by heart. A new step, even a helpful one, causes confusion and complaints in the first few sales.
- Sessions expiring at the wrong moment is worse than no change at all. If a user prepares at 7:30 and gets logged out at 8:00, the frustration doubles.
- Preparing raises expectations. Someone who did everything "right" and still gets no ticket may feel even more cheated.
Open question: Is it better to introduce this on normal days first, so people learn the habit before Eid?
Idea 3: Treat seats as tokens, not rows

In a classic design, every booking attempt locks a seat row in the relational database. At 7× traffic, thousands of requests pile up behind the same locks. That's the "hot row" problem from Part 1.
The proposal: keep seat availability in memory (Redis or similar) as counters or tokens. A booking attempt does an atomic decrement and gets a timed hold to complete payment. Payment runs idempotently, confirmed bookings are written to the database in batches, and unpaid holds return to the pool.
How it helps the system: the scarce resource, the seat, stops being the slowest part of the system.
The usability cost:
- "Ghost" seats. A seat shows as gone, then reappears when someone's hold expires. To users that looks like a glitch, or worse, manipulation.
- Timer stress. A countdown during payment, on a slow mobile connection, with a four-passenger booking, is anxious work. A failed payment that loses the hold feels deeply unfair.
- It's mostly invisible, and that's good. Of all six ideas, this one changes the least about what users see.
Open question: How long should a hold be? Too short and genuine buyers lose seats mid-payment. Too long and seats sit locked while others wait.
Idea 4: Cache the reads at the edge
During a rush, most requests are reads: Which trains run? How many seats are left? Is it sold out?
The proposal: serve those from a CDN or edge cache refreshed every few seconds, so only the actual booking touches the core system.
How it helps the system: it can take a large share of traffic off the origin servers, cheaply.
The usability cost:
- Stale numbers break trust. "12 seats left" followed by "sold out" one tap later feels like being lied to, even if the data was only 5 seconds old.
- Coarse labels are safer but less satisfying. "Available / Few left / Sold out" avoids false precision, but users want exact numbers in a high-stakes moment.
Open question: During the first five minutes, should seat counts be exact, approximate, or hidden?
Idea 5: Bots and workarounds, an arms race
Bots and automated scripts are already blocked. But when more than 25 people compete for every seat, people keep finding new workarounds. Every round of blocking is followed by a new trick.
Options typically include identity-based limits per NID and phone, smarter rate limiting, challenges at the queue entrance rather than at checkout, and behavioural detection of bookings that move at inhuman speed.
The usability cost:
- Every barrier also hits genuine users. A CAPTCHA on a slow phone, or an extra verification step for an elderly traveller booking from a village, is a real cost.
- False positives hurt the most. Being wrongly flagged as a bot, on the one morning that matters, is the worst possible experience.
- It never ends. No blocking strategy is final. The real question is how to make workarounds not worth it, rather than just harder.
Open question: Would changing the incentive (say, a lottery where speed doesn't matter) beat any technical defence?
Idea 6: Rethink the release itself

If the core problem is demand vs. supply, the most effective fix might not be technical at all. It might be changing how seats are allocated.
- Everyone at 08:00 (first come, first served): familiar for years, but it creates the biggest spike, rewards the fastest network, and still leaves almost everyone empty-handed.
- Staggered release: release by route, zone or travel date every 15 to 30 minutes. That gives smaller spikes, but users must learn new times, and some will miss "their" slot.
- Lottery window: accept applications over several hours, then allocate by random draw. Load becomes flat and speed stops mattering. But it's the biggest change to a years-old habit, everyone waits for the result, and many will see it as gambling.
Open question: Would people accept a fairer process if it meant giving up the familiar 8 AM race?
Every fix has a user cost

Here's my rough assessment of each proposal across four dimensions: load relief, user friction, change to habit, and fairness gain. It's an opinion, not measured data, and reasonable engineers will weigh these differently.
The pattern is clear, though. The ideas that relieve the most load or improve fairness the most also tend to ask users to change the most. And users rarely welcome change to a process they've followed for years, especially one tied to getting home for Eid.
Design for the 96%
If 96–99% of visitors will leave without a ticket no matter what, maybe the most important design question is: how do they leave?
A few proposals for the people who don't get a seat:
- Tell the truth early. Show live remaining inventory, and the moment a train is sold out, say so clearly instead of letting people keep trying.
- End the queue when supply ends. A waiting room that keeps people hoping after seats are gone is worse than no waiting room.
- Offer a next step. Other dates, other trains, or a notification if cancellations release seats.
- Explain the numbers publicly. When people understand that tens of thousands of seats meet hundreds of thousands of buyers, frustration turns, at least partly, from "the system failed" to "there weren't enough seats."
Scaling makes the "no" smoother. Honest design makes it fairer.
The questions that remain
None of these six ideas is a complete answer. A few questions stay open:
- Waiting room: would full transparency make a queue acceptable, or does any queue feel worse than a slow page?
- Release model: first come at 8:00, staggered, or a lottery for Eid?
- Change vs. familiarity: how do you introduce a new flow to millions of users without a public backlash?
- Beyond technology: what can other countries' rail systems, or other industries, teach us about allocating scarce seats fairly?
The hardware problem is solvable. The harder problem is designing a fair, honest experience when almost everyone has to hear "no." That's where I'll pick up in Part 3.



