shortenUrl()
redirectUrl()
Entry layer: A load balancer (or API gateway) sits at the edge of the system. It handles secure connections (TLS termination), distributes incoming requests across multiple server instances, and routes traffic to the right service: POST /shorten goes to the Shortener Service, GET /{shortCode} goes to the Redirect Service.
Write path (shorten URL): The Shortener Service receives the long URL, asks the ID Generation Service for a unique identifier, and stores the mapping shortCode → longURL in the database as a key-value record. The short URL (e.g., tinyurl.com/abc123) is returned to the client.
Read path (redirect): The Redirect Service handles GET /{shortCode}. It first checks the Redis cache:
The client receives a 302 Found response with the long URL in the Location header, and the browser redirects to the destination.
Identifier generation: A strategy exists to generate unique short codes and avoid collisions (details such as the specific encoding/algorithm are covered in the component design).
Storage layer: The database stores the shortCode → longURL mapping; Redis caches recently accessed mappings for fast reads.
Caching layer: Redis acts as a hot-read cache to serve frequent redirects without hitting the database.
1. ID Generation Service (avoiding collisions under concurrency) Generate a unique 63-bit ID using a Snowflake-style scheme: 41 bits timestamp + 8 bits server ID + 14 bits sequence counter. Base62-encode it to an ~11-character code. This guarantees uniqueness without a pre-check or retry loop, because no two servers can produce the same (timestamp, server ID, sequence) triple. The server ID (0–255) is assigned at startup, so even if one generator dies, the others keep issuing IDs — no single point of failure. This also makes codes random-looking and hard to enumerate (each code is derived from time + server, not sequential from 1).
Alternative acceptable answer: random 7–10 char string + unique DB index; on duplicate-key error, retry with a new random string. Simpler, but slower under high concurrency.
2. Caching layer (cache miss / TTL expiry path) Read path is cache-aside: check Redis → hit returns immediately; miss falls through to DB, serves the redirect, then populates Redis. Set a TTL (e.g., 24h) on each entry so memory doesn't bloat — a viral link gets refreshed on every hit, while cold links expire naturally. When a link is edited or disabled, the service actively purges it from Redis (and CDN if present), so updates take effect immediately.
3. Partitioning (sharding the database) Shard the DB on hash(short_code). Every redirect lookup routes to exactly one shard — no scatter/gather. Since short codes are random, traffic spreads evenly across shards. If a single link goes viral and overloads its shard, the Redis/CDN layer absorbs the reads so the DB never sees the spike.
4. Rate limiting A gateway-level rate limiter (e.g., token bucket) throttles per-IP or per-user to e.g. 100 create requests/min. On exceed, return 429 + Retry-After header so abusive clients back off without affecting normal users.
5. Scaling
6. High availability
Schema: The core table is URL_MAPPINGS with id (BIGINT PK), short_code (VARCHAR(10), unique indexed), long_url (TEXT), created_at, expires_at (nullable, for expiring links). A unique index on short_code is the primary lookup path for redirects.
Storage engine: DynamoDB (or Cassandra). Single-key lookups match the workload perfectly, it scales horizontally, supports strong reads on create, and has built-in TTL for expires_at. A relational DB would require manual sharding later.
Partitioning: Shard on hash(short_code). Each redirect hits exactly one shard (no scatter/gather), and since codes are random, traffic distributes evenly.
Consistency: Strong on creation — guarantees a short code is never duplicated. Eventual on redirect reads — a brief delay before a new code propagates only risks a transient miss.