Retrieving file

How I Built a Result Portal That Serves a Million Students on a Single 2 vCPU Server

· Pradeep Chandran M

Go, PostgreSQL, Performance, Load Testing

Every semester the same thing happens. The results go live, thousands of students open the college portal at the same moment, and the page spins. I saw it up close when I built the result notifier. My script had the results in 40 inboxes while the official portal was still falling over under the load. That stuck with me. The portal wasn't doing anything hard. It was showing each student a table of marks. Why couldn't it survive them all showing up at once? So I tried to answer a different question: what would it take for one small server to hand a million students their results on the day? Why Result Day Is a Strange Workload Most web traffic is a mix of reads and writes spread over time. Result day is nothing like that. Almost everyone arrives in the same few minutes. Almost everyone does the same three things: open the login page, enter a roll number and date of birth, read one page. Nothing is written. The results are immutable once they're published. That last point is the whole design. If a student's page can't change, there is no reason to build it while they wait. Every request on result day can serve something that could have been computed before anyone arrived. Where It Started I didn't begin with a clever design. I began with a plain Go server, PostgreSQL, and Nginx in front, and I load-tested it before touching anything. The first test, on a 100,000-student dataset, managed 247 student journeys per second. The cache hit rate was 1.75%. Then I read the profiles instead of guessing. A few things stood out: | Finding | Evidence | |---|---| | The result cache almost never hit | 271,721 misses to 4,844 hits | | Page rendering went through reflection | 21.6% of all allocations | | Two database queries per result view | plus a statement re-prepared on every call | | A session map that never evicted | 83% of the live heap, all scanned by the garbage collector | | A log line on every request | a global mutex and a write syscall each time | | No keepalive between…

All journal entries