The URL shortener, built from its own brief
The system-design course designed it on paper. This is the same system as running code: a key generator that never collides, a redirect that is mostly a cache hit, and click counts that do not slow the redirect down.
The business problem
Take the url-shortener brief from the system-design course and build what it describes: create a short link, redirect it, count the clicks, expire what nobody uses. The brief's numbers are your load test's targets.
The bar is the redirect path. It must answer from a cache almost every time, never generate the same key twice under concurrent creation, and count a click without a synchronous write on the path the user is waiting on.
What you will have at the end
- A key scheme you can defend: length, alphabet, collision handling, and what happens at a billion links
- A redirect that is a cache hit with a measured hit rate, and a database read only on a miss
- Click counting off the redirect path, with the number eventually right
- Expiry and deletion that do not scan the table
Milestones
Each one ends in something you can observe. Without that a milestone is a heading, and you have no way to know you finished.
Create, with a key that cannot collide
Decide between a random key checked on insert (a unique index and a retry on conflict) and a counter encoded in base62. Write the decision down with the arithmetic from the brief.
done whenA test creates 10,000 links from 50 threads and every key is unique; the database's unique constraint is what caught the collision, not application code.
Redirect from the cache
GET /{key} reads Redis first and the database on a miss, filling the cache with a TTL. Return 301 or 302 on purpose — the brief says which and why.
done whenA load test at the brief's read rate shows a cache hit rate above 95% and p99 redirect latency under 10 ms, and you can say what the miss path costs.
Count clicks off the path
The redirect increments a Redis counter or publishes an event; a job folds counts into the database in batches. The redirect never waits for the database.
done whenThe redirect's latency does not change when the click-fold job is stopped, and the counts catch up when it resumes.
Expiry without a scan
Links expire; a job deletes them. Index what the job queries on (expires_at) and page the deletes so no statement holds a lock across the table.
done whenDeleting a million expired links runs in batches and the redirect's p99 is unchanged while it runs.
The stampede and the missing key
A famous link expires from the cache at peak; a key that does not exist is requested a million times. Handle both: single-flight rebuild for the first, a negative cache for the second.
done whenA load test against a deleted key shows the database seeing a handful of reads, not one per request.
Data
One table: key (primary key, the short code), target URL, owner, created_at, expires_at, clicks. The interesting index is on expires_at for the deletion job; the primary key already serves the redirect.