.NET

From Zero to 600 in Seconds: Blazor, .NET, and the Art of High-Concurrency Exams

When the Stakes Are Real: Building a National Exam System That Had to Work

When you’re building an exam system for hundreds of examinees, the pressure isn’t just on them—it’s on the code too.

I didn’t truly understand what “high stakes” meant until I watched over 600 people log into our national exam portal at the exact same time. Each of them waiting for their screens to load, timers to start, and answers to be saved—without delay or error. We had spent weeks preparing. But no amount of testing could simulate the sheer intensity of real traffic on exam day.

This is the story of how we built, broke, and fixed a live exam platform—using Blazor, .NET, and a lot of late-night debugging.

The Setup: What We Were Building

We were tasked with building an exam platform for one of Oman’s major health institutions. On paper, the goal was simple: allow hundreds of examinees to log in, take exams, and submit answers in real time. But behind that simplicity was a technical architecture that had to handle:

  • Real-time synced timers
  • Stable sessions for every user
  • Concurrent autosaves and state updates
  • Secure authentication with Keycloak
  • Zero tolerance for downtime

The platform stack included:

  • Blazor Server
  • .NET Core + EF Core
  • PostgreSQL
  • SignalR for live syncing

It worked beautifully in local and dev environments. Then came live traffic.

The Day of the Exam: When Real Load Hits

Things started off smoothly. Users logged in, sessions were stable, timers were in sync. The first exam section went off without a hitch. For a moment, we thought: we’re good.

Then came the break.

As soon as the second section started, we were hit with the real concurrency test: 600+ examinees back online simultaneously, each one triggering autosaves, submitting answers, and updating timers in real time.

Suddenly, we saw:

  • UI timer drift for some users
  • Delays in question navigation
  • Database slowdowns from SaveChangesAsync
  • CPU spikes from hundreds of Timer.Elapsed events

The system wasn’t crashing, but it was clearly struggling—and silently so. Each Blazor Server session had its own SignalR connection, state, timer, and database context. The architecture was overloaded.

Debugging Under Pressure: Fixing While Live

There was no room for downtime. The exam was still running.

Step 1: Scale infrastructure
We quickly increased bandwidth and disk space to give the system breathing room. It helped, but we knew that wasn't the root cause.

Step 2: Fix concurrency logic
The biggest problem: hundreds of timers were calling OnElapsed every second, each trying to save to the database. We took the following steps:

  • Used IDbContextFactory<AppDbContext> to create short-lived DbContext instances and avoid memory locks.
  • Introduced randomized save intervals to stagger database writes:

UpdateAfter = new Random().Next(10, 30);

  • Ensured that SaveChangesAsync() was only called on meaningful updates (e.g. time or answer changes).
  • Optimized frontend logic to only send SignalR events when absolutely needed.

This combination of infrastructure scaling and code-level throttling got us through the rest of the exam.

What We Fixed—and What We’d Do Differently

After the dust settled, we reflected on what worked and what didn’t.

Timer Logic

We realized that giving every examinee their own active System.Timers.Timer wasn’t scalable. Hundreds of timers firing every second overloaded the server. We’re now exploring:

  • Reducing timer update frequency
  • Moving timer logic to the frontend
  • Potentially centralizing or batching updates

DbContext Handling

We fully switched to IDbContextFactory, ensuring stateless operations and avoiding long-lived context issues. It made the system far more stable under concurrent load.

Proactive Infrastructure Scaling

We now scale up before every major exam—not reactively during the event.

Smarter Load Testing

We're building a Selenium-based testing tool to simulate real user actions, not just API calls. We’re also evaluating a shift to Blazor WebAssembly + Web APIs for better scalability.

What Blazor Taught Us About Scale

Blazor Server gave us quick development and real-time capabilities. But with every user holding a persistent SignalR connection, the architecture got heavy fast—especially with over 600 concurrent users.

Blazor WASM may offer better load distribution by pushing more logic to the client side. We're seriously considering this shift.

We also learned that our autosave logic was too aggressive. Saving every few seconds across 600 users was unnecessary. We’re now focusing on smart batching and throttling.

And most importantly: tests should reflect real usage. Users don’t follow perfect scripts—they click erratically, pause, skip, and refresh. So should our tests.

We Didn't Just Survive Exam Day—We Delivered

Despite the pressure, we built a national exam platform that handled hundreds of users in real time. It didn’t crash. It held up.

It wasn’t perfect. But it worked.

And that’s something we’re proud of.

We’re still learning. We’re still improving. But now we know what “real-world ready” really means.

عرض مقالات الأخرى