The Runtime Theory
SystemDSAarchitecture

URL Shortener: The Write Path, Collision Retry, and the Cache-First Read

A step-by-step walk from the POST that generates a base62 key through the cache-first redirect path that serves millions of reads.

The Runtime Theory Team3 min read07 steps

trace spine

  1. 01 POST /shorten arrives
  2. 02 short key generated
  3. 03 key persisted
  4. 04 redirect cached
  5. 05 GET hits the cache
  6. 06 cache miss falls to DB
  7. 07 301 vs 308

A URL shortener is a tiny write path and a very hot read path: one insert per shorten, one redirect per click — with clicks outnumbering shortens by thousands to one. The design follows the ratio.

trace stepSystem
The client posts {"url": "https://..."}. The write path begins: validate the URL (scheme, host), then produce a short key. Key generation is where the first decision lives — base62 encoding of a counter, a Snowflake ID, or a random string with collision retry.
trace stepSystem
With 7 base62 characters there are 62^7 ≈ 3.5 trillion keys — for scale, that's ~100 years of traffic at a million new keys per day. The generator emits xK9f2Qp and must now prove it's free: the insert itself is the proof. On a unique-key violation (two generators racing, or randomness colliding — ~1 in a billion at 50M keys), the app retries with a new key; with an ID-pool or counter scheme, collisions are impossible by construction.
trace stepSystem
The row (key, url, created_at, owner) is inserted — a single PK insert, ~1–5ms. The key is the primary key, and the read path will be a point lookup on exactly that key. Some designs also store the URL's hash for deduplication ("this URL is already shortened — return the existing key"); dedup costs an index and a second query, and is optional.
trace stepSystem
The write path writes through to the cache: SET key → url (with a long TTL, or no expiry — short links don't rot). Now the read path can serve the most common case without touching the database.
trace stepSystem
A click arrives: GET /xK9f2Qp. The edge (CDN) or app does one Redis lookup, ~0.5ms, hit rate typically 95%+. It responds with a redirect. The browser follows: two round trips for the user, one cache read for the system. This is the whole performance story — the read path is a cache lookup plus a 3xx.
trace stepSystem
The remaining ~5%: a key that isn't cached (new, evicted, or cache restart). The app queries the DB by primary key — one indexed lookup, ~1–5ms warm — populates the cache, and answers. Expired or unknown keys return 404. This is cache-aside exactly as traced in the prerequisite: miss → DB → populate → serve.
trace stepSystem
The response is 301 Moved Permanently (browsers and CDNs cache this aggressively — which is what you want for share links) or 308 Permanent Redirect (preserves the HTTP method for API clients that POST links). The choice is a caching policy, not a formality: a 301 misused for a transient URL is how users get stuck on dead links.
text
POST /shorten {url}          # → key = base62(generate())
INSERT short_urls(key, url)  # unique violation → retry with new key
SETEX redirect:{key} url     # warm the cache
GET /:key                    # Redis GET redirect:{key}
                             #   hit  → 301 Location: url
                             #   miss → SELECT url WHERE key = :key → SET → 301

The cost ledger: one insert + one cache write per shorten; one cache read per click with a ~1ms tail to the DB on miss. At 100M clicks/day that's ~1,200 cache reads/second — a single Redis node's idle capacity. The database only ever sees writes and the 5% read misses, which is precisely why the system stays trivially small: the architecture is a ratio, not a pile of machines.