Section 6: Fewer Boxes Than You Expected - High-Level Architecture
Executive Summary: With the API and the split data model settled, the interviewer asks for the boxes. The college system needs about ten: a CDN, a static origin, two stateless API instances, the college SSO, one managed relational database with a synchronous standby, a batch flusher, a close-check job, a tally job, a results publisher and an admin console. The design splits into two paths that never touch: a write path that must be exact and carries at most 1 vote/s, and a results path that carries 400 requests/s and is a static file. Then we name what is missing on purpose (queue, cache, microservices, second region), why, and what would change each answer.
The interviewer slides the marker across. "You've got the contracts and the tables. Draw me the system."
You: "Before I draw, here are the three numbers the drawing has to respect: a voting peak of 1 vote/s, 4.8 MB of ballots for the whole election, and a results spike of 400 requests/s after close. The first two say this is a small database problem. The third says the busiest part of the system should not touch the database at all."
The Whole System on One Board
| Component | Its one job | Why this and not something bigger |
|---|---|---|
| CDN + static origin | Serves the web app, the public ballot definitions, the results JSON and the bulletin board snapshot | The 400 requests/s spike becomes cache hits at the edge |
| API service | Reads identity from the SSO token, looks up the roll, runs the cast transaction, returns the random-looking tracking code | Stateless, so two instances behind a load balancer survive losing a zone. Either could carry 1 vote/s alone |
| College SSO | Proves who Alex is | We never store passwords. The college already runs this |
| Managed relational DB | Holds the roll, participation, the ballot buffer, ballots, the bulletin board codes and the audit log | 4.8 MB fits in memory many times over. One database gives us a real transaction for exactly-once |
| Batch flusher | Every 15 minutes, if the buffer holds at least 25 ballots, shuffles them into ballots. At close it moves the remainder |
Breaks the link between write order and participation (Section 7 owns the details) |
| Close-check job | After close: confirms ballots = participation = board codes | A count-only view, so it learns totals and never reads a selection or a voter id |
| Tally job | After close and after the close checks: count | Read-only role on ballots; nothing in participation |
| Results publisher | Writes the results JSON and the bulletin board file (codes sorted, never in cast order) to the origin | One write, then the CDN does the rest |
| Admin console | Election setup, roll import, scheduled open and close | Any change needs two committee members, and every action lands in the audit log |
Each component also has a database role that matches its job. The API can insert into participation, ballot_buffer and bulletin_board but cannot read ballots. The flusher can move rows from the buffer to ballots and sees no voter ids. The close-check job sees only counts. The tally job reads ballots only. Section 5 covered what each table must not contain; the roles make sure each process can only reach the tables it needs.
Quick check before moving on:
Sign in to continue reading
The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.
Sign in to read the full lesson