System Design Cases
Design URL Shortener
Классический системный собес — спроектировать TinyURL/bit.ly. Разбираем стратегии генерации ID, cache-aside паттерн, узкие места при масштабировании. Пилотный кейс нового /cases формата.
URL Shortener
This case uses an explicit illustrative target: 10 million new links and 1 billion redirects per day. That is about 116 creates/s and 11,574 redirects/s on average; capacity planning uses measured peak factors rather than confusing average and peak rates.
Identifier and storage correctness
A seven-character Base62 namespace contains 62^7 = 3,521,614,606,208 values. At 10 million creates/day it lasts about 965 years if values are never reused. Workers lease disjoint integer ranges from a durable NO CYCLE sequence and encode the complete integer. Sequence caching can leave gaps after crashes, which is harmless; wrapping or truncating would not be harmless. A database unique constraint is the final concurrency guard. Expired aliases remain reserved so old caches and links cannot resolve to a different destination.
The database is authoritative. Cache warming happens only after commit, so this is cache-aside with a post-write warm, not write-through. Reads try Redis, then replicas, then the primary for a just-created alias hidden by replication lag. Negative results get short TTLs. Link expiry bounds every cache TTL.
Redirect and cache semantics
The design returns 302 for mutable links with explicit Cache-Control. A permanent 301 or 308 can be reused heuristically by caches and user agents, so a CDN purge alone cannot promise to retract every cached permanent redirect. Edit and delete purge CDN cache tags and Redis entries; authorization, expiry, and abuse rules are still enforced at origin on a miss.
CDN hits never reach the application, so edge access logs feed the click stream. Origin misses emit the same idempotent analytics event. Analytics is asynchronous and never delays a redirect.
Rate limiting and validation
Redis INCR plus expiry implements a fixed window, not a sliding window. The increment and initial expiry run atomically in a Lua script (or equivalent transaction). Fixed windows can permit a boundary burst, which is an explicit trade-off; stronger policies can use token bucket or a true sliding log. Destination URLs are length-limited, accept only allowed HTTP(S) schemes, reject credentials and unsafe internal targets where server-side fetching exists, and preserve an audit trail for owner edits.
Scenarios
Primary sources
- RFC 9110 HTTP Semantics: https://www.rfc-editor.org/rfc/rfc9110.html
- Redis rate limiter pattern: https://redis.io/docs/latest/develop/use-cases/rate-limiter/
- Redis INCR atomicity guidance: https://redis.io/docs/latest/commands/incr/
- PostgreSQL CREATE SEQUENCE: https://www.postgresql.org/docs/current/sql-createsequence.html
- Cloudflare purge by cache tags: https://developers.cloudflare.com/cache/how-to/purge-cache/purge-by-tags/