← STATIONSCH 04 — SPEC/CH/04 · ESC TO CLOSEENFR

Pharmapedia — pharmacy revision platform

A revision platform for pharmacy students, in production through two exam periods. Six study years of curriculum, access sold as licences rather than enrolment, and revision quizzes generated on demand from a question bank. The load is the interesting part: it is close to nothing for most of the year, then the whole cohort arrives in the fortnight before exams.

CURRICULUM
Six years, semester → module → lesson
ACCESS
Licences by year, semester or module
ASSESSMENT
Practice, timed exam, generated revision
SURFACE
80 route handlers, one Next.js process
PEAK
Exam fortnight, a few thousand students
FIG. 1 — REVISION QUIZ GENERATION
SELECTION
modules / lessons
LICENCE
scope check
BANK
active only
DRAW
shuffled, ×N
SESSION

↳ progress autosaved; submit is transactional↳ a second submit on a finished attempt is rejected

USERS
A few thousand pharmacy students
SHAPE
Load concentrated in exam periods
OBSERVED
No reported outage across two exam periods
DATA
PostgreSQL 16 · Prisma · 12 migrations

THE HARD PART

Access is sold as licences, not enrolment, and a licence scopes to a study year, a semester or a module — exactly one of the three. Working out what a student may open means intersecting their year with every active licence they hold, on every request, and that is the query that has to stay cheap when the whole cohort signs in the same evening.

WHAT I REJECTED

Enrolment as the access model. It matches how a university thinks, but not how the product sells: a student buys one module, or a semester, or the year. Making the licence the unit meant access could be granted and expired without touching the curriculum it points at.

WHAT I'D FIX FIRST

Caching and rate limiting are both in-process, which pins the application to a single replica: a second instance would serve its own stale reads and double the effective rate limit. Moving both behind Redis is what has to happen before the load grows again — the current shape has been survivable, not comfortable.