1. Shorten URL: Given a long URL, the system generates a unique, shorter URL (e.g., https://short.url/7bXk9L).
2. Redirect URL: When a user accesses a short URL, the system redirects them to the original long URL with minimal latency.
3. Custom Aliases: Users can optionally provide a custom short code (e.g., https://short.url/my-custom-link).
4. Link Expiration (TTL): Users can optionally set an expiration time for short URLs. Once expired, the link should no longer redirect.
Optional / Advanced Requirements
1. Basic Analytics: Track usage metrics (click counts, referrer headers, geo-location, timestamp).
2. Rate Limiting: Prevent spamming and misuse of the shortening endpoint.
1. High Availability: Redirection must be highly available (99.99% uptime). Users should never get a 500 error on link redirection.
2. Low Latency: Link redirection must be extremely fast (≤50 ms for 99th percentile).
3. Scalability: System should easily handle high read-to-write traffic spikes.
4. Uniqueness: Short codes must be unique—two different long URLs should never map to the same active short code.
5. Durable & Predictable: Short links must not be guessable sequentially (prevents scraping/enumeration attacks).
EAssumptions
1. Read-to-Write Ratio: 100:1(Read-heavy application).
2. New URLs generated (Writes): 100 million per day (10 8 URLs/day).
3.URL Redirects (Reads): 10 billion per day (10 10 reads/day).
4. Retention: System will store URLs for 5 years by default.
QPS (Queries Per Second)
Write QPS:
100,000,000/86,400 sec≈1,160 write requests/sec
Peak Write QPS (2×): ∼2,300 QPS
Read QPS:10,000,000,000/86,400 sec≈116,000 read requests/sec
Peak Read QPS (2×): ∼230,000 QPS∼230,000 QPS
Storage Requirements
Average record size:
short_code: 7 bytes
long_url: 500 bytes
created_at: 8 bytes
expires_at: 8 bytes
user_id / metadata: 30 bytes
Total size per record: ≈500 bytes
Daily Storage: 100M×500 bytes=50 GB/day
5-Year Storage: 50 GB/day×365×5≈91.25 TB
Memory / Cache Estimation
Applying the 80/20 Rule (20% of URLs generate 80% of read traffic):
Daily Read Volume: 10 billion requests.
20% of requests to cache: 2 billion URLs daily.
Cache Memory Required: 2 billion×500 bytes=1 TB of RAM across cluster.
1. Create Short URL
POST /api/v1/shorten
Content-Type: application/json
{
"long_url": "https://www.example.com/long/path/article?id=12345",
"custom_alias": "my-custom-link", // Optional
"expire_in_days": 30 // Optional
}
Response (201 Created):
{
"short_url": "https://short.url/my-custom-link",
"long_url": "https://www.example.com/long/path/article?id=12345",
"expires_at": "2026-09-04T10:00:00Z"
}
2. Redirect Short URL
GET /{short_code}
Response:
HTTP 302 Found (Temporary Redirect) with header Location: https://www.example.com/...
Note: 302 is preferred over 301 (Permanent Redirect) if you want to capture analytics for every click. 301 causes browsers to cache the redirect locally, bypassing your server.
Client → Load Balancer → Web Server → KGS / Cache → DB.
Database Choice: NoSQL (Key-Value / Wide-Column Store)
Justification:
Pattern: Simple key-value lookups by short_code.
No Complex Joins: Transactions across tables are not needed.
High Scalability: Distributed NoSQL databases like Amazon DynamoDB, Apache Cassandra, or MongoDB can easily scale horizontally to handle 100k+ read QPS and multi-terabyte storage.
Schema: urls Table
Column Name Data Type Primary Key / Index Description
short_code VARCHAR(16) Partition Key Base62 encoded key (e.g. aB3x9L)
long_url VARCHAR(2048) None Original target URL
user_id VARCHAR(64) Secondary Index Owner ID (optional)
created_at TIMESTAMP None Creation timestamp
expires_at TIMESTAMP Secondary Index Expiration time
1. Hash-Based (MD5/SHA256):Take MD5(long_url), base62 encode first 7 characters.
2. Drawback: Collisions can occur. Resolving collisions requires database lookups, adding latency.
Key Generation Service (KGS) (Recommended):
1. A dedicated service pre-generates 7-character Base62 keys in advance and stores them in a key table (used_keys vs unused_keys).
2. When a write request arrives, the app server fetches an already available key from KGS memory instantly.
3. Concurrency control: KGS loads blocks of pre-generated keys (e.g., 5,000 keys) directly into app server memory buffer to avoid DB locks during generation.
** Caching Strategy
1. Use Redis / Memcached in front of the NoSQL database.
2. Cache Eviction Policy: LRU (Least Recently Used).
3. Read Flow:
Check Redis for short_code.
If present (Cache Hit), increment async metrics and return HTTP 302.
If missing (Cache Miss), read from NoSQL database, populate Redis, return HTTP 302.
** Expiration Cleanup (TTL)
1. Avoid querying the database periodically to remove expired links.
2. Lazy Deletion: When a user clicks an expired link, verify expires_at > NOW(). If expired, delete the key from cache and DB, return HTTP 404 Not Found.
3. Background Worker: A scheduled cron job (e.g., during low-traffic hours) batch scans DB partitions to reclaim storage.