Complex Systems · Phase 4: High-Level Design (HLD)
Scalable URL Shortener (TinyURL & Bitly)
Design a high-throughput URL shortening service handling 100M new URLs/month with Base62 encoding, collision-aware ID generation, 100:1 read-to-write caching, and explicit redirect cache policy.
Primary specification: RFC 9110: HTTP Semantics — Redirection
Lesson 24 of 33
1d streakThe Architectural Question
Single database auto-increment counters become a centralized write bottleneck and single point of failure under global scale.
Core Engineering Objectives
- Base62 encoding (`[a-zA-Z0-9]`) vs hashing with collision resolution
- Distributed 64-bit ID generation: Twitter Snowflake vs pre-allocated database counter ranges
- HTTP 301 Permanent Redirect (client caching) vs HTTP 302 Temporary Redirect (analytics tracking)
Step 1: Problem Statement & Operational Context
1. Client
Creates or opens a short link.
2. Edge
Routes requests and caches popular redirects.
3. API
Validates writes or resolves cache misses.
4. ID service
Leases unique ranges for new aliases.
5. Store
Persists alias, destination, owner, and expiry.
The redirect path normally stops at the edge or cache; the ID service belongs only to the create path.
A URL shortener maps arbitrarily long web addresses (which frequently exceed 500 to 2,000 characters due to UTM tracking parameters, session tokens, and deep-link paths) into compact, alphanumeric aliases such as https://tiny.url/7bX9k2. These shortened URLs conserve character limits on SMS and social platforms, improve readability in print and QR codes, and provide a centralized interception point for link telemetry, geo-routing, and security inspection.
The Hidden Scale and Availability Trap
Base62 Encoding
A positional numeral system using digits [0-9], lowercase [a-z], and uppercase [A-Z] (62 alphanumeric characters). Unlike Base64, it omits URL-unsafe characters (+, /, =), making it natively URL-safe without percentage-encoding.
Read-to-Write Asymmetry
Workloads where reads vastly outnumber writes (e.g., 100:1). This dictates a CQRS (Command Query Responsibility Segregation) architecture where the read path is optimized for cache hits and zero-lock execution.
Ticket Server / Range Reservation
A distributed coordination pattern (popularized by Flickr) where centralized sequence generators allocate contiguous blocks of unique 64-bit integers to worker nodes, eliminating round-trip coordination for individual IDs.
Step 2: Requirements (Functional & Non-Functional)
A production URL shortener operates under strict contracts across both public redirection endpoints and administrative creation APIs.
| Dimension | Requirement Specification | Architectural Implication |
|---|---|---|
| Functional: URL Shortening | Accept valid long URL (up to 2048 chars); return unique 7-char alias | Deterministic Base62 generation from unique 64-bit integer ID |
| Functional: Redirection | Resolve short alias and redirect user to target destination within <10ms | Multi-tier cache (Edge CDN + Regional Redis) with HTTP 302 redirection |
| Functional: Custom Aliases | Allow users to provide vanity aliases (e.g., /my-campaign-2026) | Unique index constraint with atomic validation prior to persistence |
| Functional: Expiration (TTL) | Support optional link expiration date/time | Automated lazy expiration on read + background TTL purge job |
| Functional: Click Telemetry | Record click events (timestamp, country, referrer, user agent) | Asynchronous event streaming to Kafka; zero latency impact on redirect |
| Non-Functional: Availability | 99.999% availability for the read path (5.26 minutes downtime/year) | Stateless multi-region edge deployment; survive complete DB outages |
| Non-Functional: Latency | p99 redirect latency < 10ms globally | Anycast routing + Edge caching + in-memory Redis cluster |
| Non-Functional: Predictability | Short keys must be unguessable to prevent link enumeration scraping | Bit-shuffling / Feistel cipher permutation on sequential IDs |
Use it when
Adopt an asynchronous CQRS architecture with distinct read and write services when the read:write ratio exceeds 50:1 and read latency must remain bounded under 10ms.
Reconsider when
Use a unified single-service monolithic CRUD model only when total traffic is under 500 requests/sec and administrative writes require immediate read-your-own-writes consistency across all nodes.
Step 3: Detailed Back-of-the-Envelope Capacity Estimations
Capacity sizing derives the infrastructure bounds required to sustain 100 million new URLs created per month and 10 billion redirects per month.
| Metric | Baseline Calculation | Sustained Value | Peak Value (2.5x Factor) |
|---|---|---|---|
| Write Throughput (Creations) | 100,000,000 / (30 days * 86,400s) | ~38.6 writes/sec | ~100 writes/sec |
| Read Throughput (Redirects) | 10,000,000,000 / (30 days * 86,400s) | ~3,858 reads/sec | ~10,000 reads/sec |
| Read:Write Ratio | 10,000,000,000 : 100,000,000 | 100 : 1 | Up to 250 : 1 during campaigns |
| Write Ingress Bandwidth | 38.6 writes/s * 600 bytes per record | ~23.2 KB/sec | ~60 KB/sec |
| Read Egress Bandwidth | 3,858 reads/s * 500 bytes (HTTP 302) | ~1.93 MB/sec | ~5.0 MB/sec |
| 5-Year Total Records | 100M URLs/month * 12 months * 5 years | 6,000,000,000 records | 6 Billion unique URLs |
| 5-Year Storage Footprint | 6 Billion records * 600 bytes (with indices) | ~3.6 TB raw (~7.2 TB with replicas) | ~10 TB provisioned |
| Cache Working Set (20% Pareto) | Assume 50M distinct hot mappings * 600B logical record size | ~30 GB logical payload | ~64 GB after measured cache overhead and headroom |
Key Space Combinatorics Math
Step 4: High-Level Architecture & Component Responsibilities
The architecture isolates the low-volume, write-heavy URL creation path from the latency-critical, high-volume redirection path.
- 01DNS & Edge Routing: DNS and routing policy steer traffic to a healthy edge point of presence (POP), which terminates TLS and checks its edge cache. The selected POP is not guaranteed to be geographically closest.
- 02API Gateway Fleet: NGINX / Envoy proxy fleet enforces rate limiting, authenticates API tokens, and routes traffic between URL Creation Service and URL Redirection Service.
- 03URL Redirection Service (Read Path): Ultra-lightweight, stateless Go or Rust services that query the local Redis cluster. On cache hit, they return an immediate HTTP 302 Found. On cache miss, they query PostgreSQL/CockroachDB, backfill Redis, and emit a redirect.
- 04URL Creation Service (Write Path): Orchestrates validation, malware scanning, range-based sequence ID acquisition, Base62 encoding, and persistence.
- 05Distributed ID Generator (Ticket Server): Centralized sequence allocator reserving ranges of 1,000,000 contiguous IDs per worker, eliminating lock contention.
- 06Authoritative Storage Tier: Sharded PostgreSQL or distributed SQL (CockroachDB / YugabyteDB) maintaining persistent URL mappings with multi-AZ replication.
- 07Asynchronous Telemetry Pipeline: Redirection services emit lightweight click events to Apache Kafka, which streams data into ClickHouse for sub-second analytical queries.
Complete Isolation of Analytics from the Critical Read Path
Step 5: Detailed Data Models & Storage Schema
The system separates transactional mapping tables from append-only analytics event streams.
-- PostgreSQL / CockroachDB Authoritative URL Schema
CREATE TABLE url_mappings (
id BIGINT PRIMARY KEY, -- 64-bit globally unique sequence ID
short_key VARCHAR(7) NOT NULL, -- Base62 representation (7 chars)
original_url TEXT NOT NULL, -- Target URL (up to 2048 chars)
user_id UUID NULL, -- Creator ID for authenticated users
custom_alias BOOLEAN NOT NULL DEFAULT FALSE,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP WITH TIME ZONE NULL -- NULL represents perpetual link
);
-- Unique index enables logarithmic B-tree lookup with a small tree height
CREATE UNIQUE INDEX idx_url_mappings_short_key ON url_mappings (short_key);
CREATE INDEX idx_url_mappings_user_created ON url_mappings (user_id, created_at DESC) WHERE user_id IS NOT NULL;
-- ClickHouse Analytics Schema (Optimized for append-only analytical aggregations)
CREATE TABLE url_click_events (
short_key LowCardinality(String),
clicked_at DateTime64(3, 'UTC'),
ip_pseudonym FixedString(64), -- Rotating keyed HMAC; still personal data
country_code LowCardinality(FixedString(2)),
referrer String,
user_agent String,
device_type LowCardinality(String)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(clicked_at)
ORDER BY (short_key, clicked_at);Architectural Boundary · Do Not Confuse
Relational Store (PostgreSQL / CockroachDB)
NoSQL Key-Value Store (DynamoDB / ScyllaDB)
Relational engines guarantee ACID uniqueness constraints on custom aliases and enable seamless range sharding. DynamoDB provides predictable single-digit millisecond reads at any scale, but custom vanity alias reservations require conditional PutItem transactions across multiple tables.
Step 6: Core Algorithms & Step-by-Step Request Flows
The core conversion maps an allocated integer from the finite range [0, 62^7) into a fixed-width Base62 key. If sequential keys are too easy to enumerate, apply a reviewed keyed permutation over that same finite domain or generate random keys with a uniqueness check.
ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
KEY_SPACE = 62^7
function createShortKey():
rawId = idAllocator.nextUniqueId()
require 0 <= rawId < KEY_SPACE
// Optional: use a reviewed, keyed, reversible permutation whose domain
// is exactly [0, KEY_SPACE). Do not apply an unrestricted 64-bit transform:
// its output may not fit in seven Base62 characters.
publicId = finiteDomainPermutation(rawId, secretKey, KEY_SPACE)
return encodeBase62(publicId).leftPad("0", 7)
function resolveShortKey(key):
require key.length == 7 and every character is in ALPHABET
publicId = decodeBase62(key)
rawId = inverseFiniteDomainPermutation(publicId, secretKey, KEY_SPACE)
return mappingStore.get(rawId)- 01Step 1 (Write Execution): Client issues
POST /api/v1/urlswith{ "original_url": "https://..." }. API Gateway validates URL syntax against an SSRF blocklist. - 02Step 2 (ID Generation): Write Service requests an ID from its local in-memory counter. If the allocated range is exhausted, it reserves a new 1,000,000-block range from the central Ticket Server via
UPDATE id_allocator SET next_id = next_id + 1000000 RETURNING next_id. - 03Step 3 (Encoding & Storage): The sequence ID is bit-shuffled and Base62-encoded to 7 characters (
7bX9k2). The record is written to the primary DB withINSERT INTO url_mappings (...). - 04Step 4 (Pre-Warming): The newly created mapping is immediately written to Redis with a 24-hour TTL to ensure that immediate clicks avoid database hits.
- 05Step 5 (Read Execution): User navigates to
https://tiny.url/7bX9k2. Routing policy selects a healthy edge proxy. Redis evaluatesGET url:7bX9k2. On hit, the server returns HTTP 302 Found withLocation: https://original.url. The service publishes an analytics event according to its chosen durability and overload policy.
Step 7: Deep Dives into Critical Bottlenecks
Architectural Boundary · Do Not Confuse
HTTP 301 Moved Permanently
HTTP 302 Found / 307 Temporary Redirect
301 communicates that the redirect is permanent, while 302/307 communicate a temporary redirect; 307 also preserves the request method. Actual reuse depends on HTTP cache headers and intermediary or user-agent behavior. If revocation and per-click server visibility matter, use a temporary redirect with an explicit restrictive cache policy such as `Cache-Control: no-store`. Even then, clients and bots can ignore policy, so redirects do not guarantee complete telemetry.
Cache Stampede (Thundering Herd) on Expired Viral Links
Malware and Phishing Injection Prevention
is_active = FALSE, redirecting visitors to an interstitial warning page.Step 8: Real-World Production War Stories
Flickr: Independent Ticket Servers
auto-increment-increment = 2 with different offsets, so one issued odd IDs and the other even IDs. The design favored simple, coordinated uniqueness over perfectly contiguous numbering. Its operational invariant is configuration ownership: two allocators must never issue the same residue class. Range leasing is another option, but lease ownership and fencing must be durable before a worker serves IDs.Failure Exercise: Abuse Damages the Shared Domain
Step 9: Interviewer Deep-Dive Gauntlet & Active Recall
Deep-Dive Q1: If sequential 64-bit IDs are used, how do you prevent attackers from enumerating and scraping every URL in the database?
Deep-Dive Q2: Why is standard Base64 encoding unsuitable for URL shortener keys without custom modifications?
Deep-Dive Q3: How do you handle expired URLs and database compaction at scale without taking exclusive table locks?
Deep-Dive Q4: How do you achieve sub-10ms redirect latency for users in Singapore when the primary database is in US-East?
Cache-Control: public, max-age=60. 4) When a Singapore user clicks a link, the nearest edge POP terminates TLS, checks its local Redis replica in <1ms, and returns HTTP 302 without ever crossing the Pacific Ocean.Active Recall Checkpoint
Calculate the total number of unique URLs that can be represented with a 7-character Base62 string, and explain why 6 characters is insufficient for long-term multi-decade scale.
Hint: Evaluate 62^6 vs 62^7 and compare against 100M URLs created per month.
Show answer & explanation
62^6 ≈ 56.8 billion keys and 62^7 ≈ 3.52 trillion keys. At 1.2 billion allocations/year, sequential collision-free allocation would exhaust them in about 47 and 2,934 years respectively. Random generation needs a separate birthday-collision analysis and collision retry; neither calculation guarantees future traffic or lifetime scale.
Active Recall Checkpoint
Why should an enterprise URL shortener default to HTTP 302 Found instead of HTTP 301 Moved Permanently?
Hint: Consider browser caching behavior and marketing analytics requirements.
Show answer & explanation
A temporary redirect better communicates that the mapping can change. Pair it with an explicit cache policy such as Cache-Control: no-store when revocation and server-side telemetry matter. A 302 alone does not guarantee that every client contacts the service, and analytics should never be described as complete.
Topic sizing snapshot
The four numbers that materially affect this architecture. Use them as bounds, not as a generic calculator embedded in every lesson.
Peak QPS
10,000 Read RPS / 100 Write RPS (2.5x peak assumption)
5-Yr Storage
~3.6 TB raw for 6B records at 600 bytes each
Bandwidth
~0.04 Gbps redirect egress at 10k RPS and 500 bytes
Working Set RAM
~30 GB logical payload for 50M hot mappings; provision after measurement
Architectural Trade-Off Analysis
Compare latency, throughput, consistency, and operational complexity across core industry alternatives.
Architecture Decision Matrix
Multi-axis engineering evaluation and FAANG interview rules of thumb.
SQL (Postgres / MySQL)
Relational ACID Store
Structured schema, relational joins, ACID transactions, and strict foreign keys.
NoSQL (Cassandra / DynamoDB)
Distributed Non-Relational Store
Horizontal partition keys, denormalized documents/wide-columns, and tunable consistency.
| Evaluation Axis | SQL (Postgres / MySQL) | NoSQL (Cassandra / DynamoDB) | Details |
|---|---|---|---|
Latency | Moderate | Strong | |
Throughput | Moderate | Strong | |
Scalability | Moderate | Strong | |
Consistency | Strong | Moderate | |
Complexity | Strong | Moderate | |
Cost | Moderate | Strong |
When to Choose in an Architecture Interview
Choose SQL when ACID guarantees are non-negotiable (financial ledgers, payment processing, inventory checkout) or when query access patterns are unpredictable and require ad-hoc analytical joins.
Choose NoSQL when write volume exceeds single-node capacity (>20K writes/sec), data is unstructured/hierarchical (logs, telemetry, user feeds), or multi-region active-active replication is mandatory.
Never say "NoSQL is faster than SQL" without qualification. SQL with proper B-Tree indexes on indexed primary keys is lightning fast. NoSQL wins on horizontal write scaling, not inherent single-row read speed.
Production Postmortem & Verification
Production Incident Postmortem
- Trigger / Setup: A URL shortener issues HTTP 301 Permanent Redirects to reduce edge server read traffic.
- Production Symptom: Caches may reuse the redirect without contacting the service, reducing telemetry and delaying mapping changes or revocation.
- Root Cause & Fix: Use a temporary redirect plus an explicit restrictive cache policy when server visibility and fast revocation are requirements.
Acceptance Criteria & Outcomes
- Derive key length: 62^7 ≈ 3.5 trillion unique URLs, sufficient for decades of scale.
- Size storage and cache: 100M writes/day ≈ 1,160 writes/sec; 100:1 read ratio ≈ 116,000 reads/sec.
- Model schema: `id (BIGINT PK)`, `short_key (VARCHAR UNIQUE)`, `original_url (VARCHAR)`, `created_at (TIMESTAMP)`.
End-of-Chapter Synthesis & Key Takeaway
Scalable URL Shortener (TinyURL & Bitly) is built around one fundamental rule: Establish clear responsibility and explicit trade-offs.. Before adding components, establish explicit quantitative SLAs for latency, throughput, and consistency. Choose the simplest architecture that meets stated constraints.
Architectural & Technical Interview Review
Core FAANG Interview Questions
Q01How do you justify introducing Scalable URL Shortener (TinyURL & Bitly) into a high-scale production architecture?Click to reveal model answer →Hide answer ↑Requirements & Design Brief
Introduce Scalable URL Shortener (TinyURL & Bitly) only when measured scale, data size, or failure boundaries exceed single-node capacity. State the exact bottleneck, evaluate operational overhead, and verify that the trade-offs justify the added moving parts.
Q02What is the primary trade-off and hidden operational cost of Scalable URL Shortener (TinyURL & Bitly)?Click to reveal model answer →Hide answer ↑Architecture Trade-offs
It improves scalability and availability, but introduces network hops, serialization overhead, and eventual consistency or coordination complexity.
Q03How do you handle catastrophic failure or dependency degradation in Scalable URL Shortener (TinyURL & Bitly)?Click to reveal model answer →Hide answer ↑Failure Review & Recovery
Implement connect/read timeouts, exponential backoff with jitter, and fallback defaults so downstream degradations do not cause cascading outages.
Production Pitfalls & Anti-Patterns
Critical Failure Anti-Patterns
Premature Distributed Complexity
Scattering distributed components before identifying concrete write, read, or storage limits creates accidental points of failure and operational debt.
Production Mitigation: Start with the simplest modular design that satisfies requirements, evolving it only as measured constraints dictate.
Unbounded Retries and Cascading Storms
When a dependency degrades, immediate synchronized retries multiply downstream traffic, turning a minor slowdown into a total fleet outage.
Production Mitigation: Enforce strict retry budgets, exponential backoff, and full randomized jitter on all client calls.
Neglecting Observability and Metrics
Deploying architecture without distributed tracing or p99 tail latency alerts leaves engineers blind when partial partitions occur.
Production Mitigation: Export metrics for request rate, error rate, p95/p99 duration, and saturation at every service boundary.