Build TinyURL - A URL Shortener at Scale
caching scalability performance
System Design Deep Dive
URL Shortener at Scale
100,000 redirects per second with sub-10ms latency - where the bottleneck is never the database
A URL shortener looks deceptively simple: take a long URL, generate a short code, return a redirect. Any junior developer can build a working version in an afternoon with a single table and a random string generator. But then a viral tweet drops a short link that gets clicked 50,000 times in the first 60 seconds. The database melts. The service returns 503. The tweet goes cold, and with it, your product’s reputation.
The problem is not the write path - most URL shorteners handle far more reads than writes, often at a 1000:1 ratio. A link shared by a celebrity may receive 100,000 clicks in minutes, each one hitting your redirect endpoint. Think of it like a highway toll plaza: the challenge is not issuing passes (writes), it is processing millions of cars passing through every hour without every car stopping to verify its pass with the central DMV office. The toll reader needs to work at the edge, with a cached copy of valid passes, not a round-trip to headquarters.
At 100,000 redirects per second with a sub-10ms p99 latency target, no database on earth can keep up if it is consulted for every redirect. Even a highly-tuned Postgres instance with index-only scans tops out around 50,000 simple reads per second per node before latency climbs. The math simply does not work for direct-to-database reads at this scale. We need to push the read path off the database entirely, catching 95%+ of requests in Redis or CDN edge cache, and only falling through to Postgres for cold-cache misses.
But the read side is only half the problem. The other half is collision-free short code generation, which must work under concurrent write load without central coordination, and analytics tracking, which must capture click metadata without adding latency to the redirect itself. We need to solve for collision-free code generation, sub-10ms redirect latency, and decoupled analytics pipeline simultaneously.
Requirements and Constraints
Functional Requirements
- Create short URLs from long URLs, returning a 7-character short code
- Redirect users visiting a short URL to the original long URL
- Support custom slugs (user-specified short codes)
- Support optional link expiry with a TTL
- Track click analytics: timestamp, IP, user agent, referrer, country
- Provide per-link analytics dashboards for creators
- Support link deletion and updating the destination URL
Non-Functional Requirements
- Redirect throughput: 100,000 redirects per second sustained, 500,000 peak
- Write throughput: 1,000 new links per second at peak
- Redirect latency: p99 under 10ms (CDN edge), p99 under 30ms (Redis cache hit), p99 under 100ms (DB fallback)
- Availability: 99.99% (52 minutes downtime per year)
- Durability: Zero link loss after successful 201 response
- Short code uniqueness: Guaranteed - no collision in the URL namespace
- Analytics freshness: Click counts visible within 60 seconds of click
- Storage: 10 billion stored links, roughly 200 bytes per link = 2 TB
Constraints and Assumptions
- Short codes are 7 characters from
[0-9A-Za-z](Base62), giving 62^7 = 3.5 trillion possible codes - Analytics are eventually consistent - exact counts may lag by up to 60 seconds
- Links do not expire by default; expiry is an optional feature
- We do not crawl or validate destination URLs for safety (separate concerns)
- Users can be anonymous (no auth required to shorten a URL)
High-Level Architecture
The system has two clean paths that share nothing except the URL storage database. The write path - POST requests to create a new short URL - goes through the Shortener Service, which generates a code, checks for collisions, writes to Postgres, and populates Redis. The read path - GET requests to follow a short link - goes through CDN first, then the Redirect Service backed by Redis, only falling back to Postgres on a cold cache miss.
Four major components drive this architecture. The API Gateway handles rate limiting, authentication (for premium features), and routes POST /shorten to the write path and GET /{code} to the CDN-fronted read path. The Shortener Service encapsulates all code generation logic including Base62 encoding, collision detection via Bloom filter, and transactional writes to Postgres and Redis. The Redirect Service is a thin, stateless Go process that calls HGET on Redis with the short code and returns an HTTP 302 response in microseconds. The Analytics Pipeline is fully async - the Redirect Service fires a Kafka event on each click and returns the redirect immediately, with no latency added to the hot path.
The redirect service and the analytics pipeline must be completely decoupled. If you try to write click data synchronously during redirect, you add database write latency to every single redirect - turning your 10ms SLA into a 50ms one under load.
The Shortener Service
The Shortener Service’s job is to take a long URL and produce a guaranteed-unique 7-character short code, then durably store the mapping.
A naive approach generates a random string and checks if it exists in the database. This works until you have billions of URLs, at which point collision probability grows meaningfully and each insert may require multiple database round-trips. A smarter approach uses a hash-then-encode strategy: compute an MD5 of the long URL, take the first 43 bits of the hash, encode it in Base62 to get 7 characters, then verify uniqueness. MD5 is not cryptographically secure, but for this purpose we only need fast, uniformly distributed bits - security is not the concern.
The 43-bit extraction is deliberate. 62^7 = 3,521,614,606,208 distinct codes, and 2^43 = 8,796,093,022,208, so the Base62 encoding of a 43-bit integer will always produce exactly 7 characters. This makes the mapping deterministic and reversible.
Premium Content
Unlock the full article along with everything else in the archive — all in one place.