shortenUrl()
redirectUrl()
Entry layer: An AWS Application Load Balancer (ALB) sits at the edge. It terminates TLS, health-checks service instances, distributes traffic across them, and routes by path: POST /shorten → Shortener Service, GET /{shortCode} → Redirect Service. ALB is chosen because it natively supports path-based routing and automatic failover, so no single server is a point of failure.
Write path (shorten URL): The Shortener Service (a stateless service, horizontally scalable) receives the long URL, requests a unique identifier from the ID Generation Service, and stores the mapping shortCode → longURL as a key-value record in DynamoDB. DynamoDB is chosen because every redirect is a single-key lookup — it serves these in single-digit milliseconds, scales horizontally without manual sharding, and has built-in TTL support for expiring links. The short URL (e.g., tinyurl.com/abc123) is returned to the client.
Identifier generation: Short codes come from a Snowflake-style generator — 41-bit timestamp + 8-bit server ID + 14-bit sequence counter — base62-encoded into an ~11-character code. Because each server's ID is unique, no two servers can produce the same code, so uniqueness is guaranteed with no collision-check retry loop. The time-based component also makes codes non-sequential, so users can't enumerate or guess other short links.
Read path (redirect): The Redirect Service handles GET /{shortCode}. It first checks Redis:
The client receives a 302 Found response with the long URL in the Location header; the browser follows it to the destination. (302, not 301, so every click hits our service — enabling click analytics and letting us update or disable links instantly.)
Caching layer: Redis runs in cluster mode — keys are sharded across nodes, with replicas for read scaling and automatic failover. A cache outage degrades to DB reads but never takes the system down.
Storage layer: DynamoDB stores the shortCode → longURL mapping, partitioned on hash(shortCode). Since codes are random, traffic spreads evenly across partitions; a viral link's reads are absorbed by Redis so the database never sees the spike.
Scaling & availability summary: All services are stateless → scale by adding instances behind the ALB. DynamoDB scales automatically. Redis scales via cluster mode. The ALB health-checks and routes around dead instances, and Redis replicas handle node failure — giving high availability without a single point of failure.
Security: All traffic is HTTPS-only (TLS 1.3), terminated at the ALB — data is encrypted in transit end to end. Data at rest is encrypted via DynamoDB's server-side encryption (AES-256 with AWS KMS); Redis data is encrypted the same way. DDoS protection comes from AWS Shield (absorbs volumetric attacks at the network edge) plus AWS WAF filtering malicious patterns (SQLi, XSS) before requests reach the ALB. A rate limiter (token bucket, e.g., 100 creates/min per IP) at the gateway throttles abuse and returns 429 + Retry-After on exceed — protecting the write path from being overwhelmed.
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.