UCAM Cloud is the course-registration and section-selection platform of United International University (UIU) in Dhaka. On Saturday, 24 October, UIU ran section selection for the Fall 2026 trimester on it. For the first time, all eleven departments selected in the same window. Every department opened at 10:00, with CSE starting with its priority students, and CSE’s regular students, the biggest group of the day, followed at 13:00.
It placed more course seats than any selection day before it, and it was the smoothest. Here is the result first.
Key takeaways
- Scale: eleven departments in one window, a first for UCAM Cloud.
- Reliability: 7,000,333 API requests over the day with 8 server errors (0.00011%).
- Speed: a p99 of 114 ms for the whole day; each selection took 32 ms to process.
- Students: a typical student in the 10:00 rush placed four courses in 1 minute 23 seconds.
- Correctness: 29,047 course seats placed, and no section ever went over capacity.
- Next: the live seat board fell behind at the 13:00 peak without affecting selection, and is being rewritten on Erlang/Elixir.
- 7.0MAPI requests24 Oct, whole day
- 8server errorsout of 7 million
- 114 msp99 response timethe whole day
- 29,047course seats placeddistinct student-and-course pairs
- 0sections over capacitychecked after the window
- 11departments in one windowa first for UCAM Cloud
- 93.4%got a section on day oneof students who pressed Select
- 99.9999%requests without a server error8 errors in 7,000,333
- 100%sections within capacityno seat granted twice
Students didn’t wait hours, didn’t fight a frozen page, and not a single complaint about selection reached us. This post is how that day went, what we built to get there, and the one part of the system that has to grow before the next one.
Why selection day is hard
Section selection is the step where each student picks a section, a fixed class time with a faculty member, for every course they were advised to take. Every section has a fixed number of seats, and they go to whoever claims them first.
Selection day is like a flood you know the date of. For most of the trimester the platform is quiet. Then, at a time everyone can see on the notice board, thousands of students press the same button in the same second, because a seat in the section they want is on the line.
This time the flood came in two waves, and it covered more of the university than ever before:
Before UCAM Cloud, a day like this meant five to six hours of refreshing a portal that kept falling over. The goal for Fall 2026 was simple to say and hard to do: every department, one window, and every student done in minutes, not hours.
What we built for this window
Most of the work since Summer 2026 went into making the day fair and making it correct, not just fast.
- One active tab per student. Selection works in a single tab at a time. Ten open tabs no longer give anyone ten chances at the same seat.
- Protection against automation on the course faculty list and the section selection pages. A seat goes to a student pressing Select, not to a macro or a script.
- Nothing changes under a student’s feet. Syncs from the university’s system pause for a department while its window is open, and a selection can’t move once the university’s system is registering it.
- Every window is prepared automatically before it opens, can be rehearsed with a dry run, and registration runs only inside a live window.
- A cross-check against the university’s system, per department and per student, plus a backup and restore for each department’s selections.
- A fair limit per student on how fast seat requests can be sent, and roles and permissions so each department’s staff manage their own students.
All of it was switched on for every department on 24 October.
The day, hour by hour
- 07:00Window prepared automaticallyThe window's preparation ran on its own, hours before the opening
- 10:00All eleven departments openCSE starts with its priority students, and 3,194 backend calls arrive two seconds in
- 13:00CSE regular students joinThe biggest group of the day arrives, and the day's biggest wave begins
- 13:00:02Peak second5,191 backend calls in one second
- 13:00:05Peak selection389 seats granted in a single second
- 14:00The glide downTraffic falls to a fraction of the peak
- 24:00Day closes7.0 million requests, 8 server errors, no section over capacity
The shape of the day
Two cliffs, not two hills. At 10:00 the platform went from about 590 requests a second to 1,558 a second, averaged over the minute. At 13:00 it took 147,773 requests in sixty seconds, about 2,463 a second, and was back to half that within two minutes. Over the busiest clock hour, 13:00 to 14:00, it answered 1.95 million requests.
The part I’m proudest of is what didn’t move. p99 latency is the time within which 99% of requests are answered, so it tracks the slowest 1% of responses, the ones students notice. Here it is minute by minute, over the same eleven hours:
Even in the minute 147,773 requests arrived, 99% of them were answered in under a fifth of a second. Across the whole day the p99 was 114 ms, and the median was 17 ms.
The first seconds at 13:00
A minute is too coarse to see what really happens at an opening, so here it is per second, counted from every log line rather than sampled.
Before 13:00 the line is already at 1,100–1,400 calls a second: thousands of students waiting on the selection screen. Then the clock ticks over. 5,191 calls at 13:00:02, and every one of them answered.
The seat requests themselves arrive a beat later, once the page knows the window is open:
At 13:00:05 the platform granted 389 seats in one second. In the first three seconds almost every request got a seat. By the tenth second some sections were full, and the grey band grows. That grey band isn’t failure. It’s the system telling the truth about a section that just filled.
How long did a student take?
Throughput is the platform’s view. A student’s view is simpler: how long from the moment the window opens until my courses are placed?
- 10 sto the first tapmedian, after the window opened
- 1m 23sfour courses placedtypical student, 10:00 rush
- 4m 41sfour courses placedtypical CSE student, 13:00
- 32 msto process one selectionmedian, opening minute included
Half of all students made their first selection within 10 seconds of their window opening. A typical student in the 10:00 rush placed four courses in 1 minute 23 seconds, counted from the opening, not from when they arrived. CSE’s regular students at 13:00 took longer: a median of 4 minutes 41 seconds for four courses. That gap has nothing to do with the servers. The 13:00 wave was one large cohort going for the same popular sections. More of their taps came back “section full”, so they picked a second choice.
- 10:00 rush · within 1 minute41.8%
- 10:00 rush · within 2 minutes56.7%
- 10:00 rush · within 5 minutes72.0%
- 13:00 CSE · within 1 minute16.1%
- 13:00 CSE · within 2 minutes29.7%
- 13:00 CSE · within 5 minutes51.9%
The server side of each of those taps didn’t change at all under the rush. A successful selection took 32 ms to process at the median and about 50 ms at the 99th percentile, and the opening minutes at 10:00 and 13:00 were exactly as fast as a quiet afternoon. The waiting a student felt was the race for seats, not the platform.
That is the number this whole project was built to move: from five or six hours of refreshing to a few minutes, with half of the 10:00 rush done in under a minute and a half.
Every refusal was a rule doing its job
Over the day, students sent 74,802 seat requests, and 38,872 succeeded. Every other answer was a rule working as designed:
- Department's share full21,882 refusalsThe section had seats, but this department's share was used
- Section full6,299 refusals
- Course evaluation not done3,731 refusalsRequired before selecting
- Not eligible for the course1,243 refusals
- Already selected116 refusalsA double tap answers 'already selected', never a second seat
- Two students, same seat, same millisecond108 refusalsThe database let one through, and the other pressed again
- Other102 refusals
My favourite row is the sixth. 108 times, two students went for the same seat in the same millisecond. The database granted it to exactly one of them, every time, which is the core promise in the UCAM Cloud case study: a seat is granted by one guarded write. After the window, no section had more seats taken than it holds.
Better every window
Put Fall 2026 next to the selection days before it, measured the same way each time, and the trend is the story:
- Fall '25 · 8 Nov 202539.3%The earlier design, before the rebuild
- Spring '26 · 23 Feb0.71%
- Summer '26 · 4 Jul0.064%
- Fall '26 EEE · 5 Oct0.00065%One department
- Fall '26 · 24 Oct0.00011%Eleven departments
- Fall '25~5.1 s
- Spring '26136 ms
- Summer '26115 ms
- Fall '26 · 24 Oct114 msEleven departments, and the fastest multi-department day yet
| Selection day | Requests (whole day) | Server errors (5xx) | p99 latency | Course seats placed |
|---|---|---|---|---|
| Fall ‘25 · 8 Nov 2025 (earlier design) | 14.53M | 39.3% | ~5.1 s | not logged |
| Spring ‘26 · 23 Feb 2026 | 7.74M | 0.71% | 136 ms | 22,056 |
| Summer ‘26 · 4 Jul 2026 | 8.48M | 0.064% | 115 ms | 27,760 |
| Fall ‘26 EEE · 5 Oct 2026 (one department) | 1.08M | 0.00065% | 69 ms | 2,690 |
| Fall ‘26 · 24 Oct 2026 (eleven departments) | 7.00M | 0.00011% | 114 ms | 29,047 |
A year ago, on the earlier design, four requests in ten failed and the slowest 1% took five seconds. This October, with more departments in one window than ever, it was 8 errors in 7 million and 114 ms. The seat count kept climbing too: 22,056 course seats in Spring, 27,760 in Summer, 29,047 in Fall, with none ever seen over capacity.
Where the work actually went
The surprising number of the day wasn’t the peak. It was how little of the traffic was selection itself.
- API requests6.17MEverything that reached the API
- Backend calls5.22MThe rest were browser preflight checks
- Seat requests74,802Students pressing Select
- Successful selections38,872Including section switches
- Course seats placed29,047Distinct student-and-course pairs
Almost 90% of the backend’s work was the portal asking “anything new?” about things such as settings, banners, unread counts and eligibility. All of it was answered fast, and it’s also the cheapest headroom we have. Fetching settings once per page load and pushing changes instead of polling will make the next opening lighter for the same number of students.
The one thing that has to grow: the live seat board
In July I wrote about the live seat board, the screen that shows seat counts draining in real time. I built it under one rule: it is a spectator. If it ever falls behind, section selection must not notice.
On 24 October, it fell behind, and selection didn’t notice.
When CSE’s regular students came in at 13:00, thousands of students were watching live seat counts at once. The seat board’s server ran at full CPU from 13:00 to about 13:58, and for most of that hour live counts and the “who’s online” view stopped updating:
Through that whole hour the selection API answered every request, with no server errors, and kept granting seats correctly. The live board is a convenience, not a dependency, so the worst case cost students a live animation, never a seat. The rule from July held on the day it mattered most.
The cause is simple arithmetic. Every seat change is pushed to every student watching that course, so the work grows with connections × updates. At 13:00 that was thousands of connections and, in the busiest 30 seconds, 4,693 seat updates, all on a small server that handles the rest of the year comfortably.
The fix: I’m rewriting the realtime server on Erlang/Elixir. Erlang was designed for soft real-time systems with massive concurrency, and its virtual machine, the BEAM, runs huge numbers of long-lived connections, each doing a little work, on modest hardware. Elixir brings the same runtime with a friendlier toolchain, and Phoenix Channels is a proven pattern for exactly this kind of live, per-topic fan-out. Alongside the rewrite, the plan is to batch seat updates per course every few hundred milliseconds instead of sending them one by one, and to spread reconnects out so a slow moment can’t turn into a stampede.
What’s next
- The realtime rewrite on Erlang/Elixir, load-tested well past this October’s peak.
- Less polling: fetch settings once per page load and push changes instead.
- Faster confirmation e-mails: they ran up to 41 minutes behind at the peak because they were sent one at a time. Sending several at once brings that down to minutes.
- Alarms for every number in this post, so the next report writes itself while the window is still open.
On the second day, 25 October, the platform answered another 1.59 million requests by early evening, with a single server error.
Eleven departments, one afternoon, and a p99 that never left a fifth of a second. Students placed their courses in minutes, not hours, and went on with their Saturday, which is exactly what this platform is for.
Methodology: how these numbers were measured
- Requests, server errors and p99 come from the API’s CloudWatch metrics for the whole Dhaka day, the same definitions as the case study’s results table. Charts that cover 09:00–20:00 say so.
- Per-second rates and seat requests come from all 22.8 million function log lines in the window, downloaded and parsed, not sampled.
- Per-student times start at the window’s opening (10:00 or 13:00) and end at the student’s last successful selection within 30 minutes of their first try. The “rush” is students whose first try came within two minutes of the opening. Processing time is the server’s handling time for each successful selection.
- Course seats placed counts distinct student-and-course pairs with at least one successful selection between 09:00 and 20:00, so section switches count once.
- Seat-board connections come from 30-second samples of the live server, shown as a share of the day’s peak; CPU is the server’s 5-minute average.
- Not published: student, seat and enrolment totals (university policy), infrastructure identifiers, and how automation is detected.