List the key functional requirements for the system (Ask the AI for hints if stuck)...
List the key non-functional requirements (performance, scalability, reliability, etc.)...
Estimate the scale of the system. Consider daily active users, read/write ratio, storage requirements, bandwidth, and any relevant QPS calculations...
Define the APIs expected from the system. This is your chance to analyze and define the read and write paths so that you can come up with the high-level design...
Describe the overall system architecture. Identify the main components needed to solve the problem end-to-end. Use the diagramming tool to create a block diagram.
Define the data model. Identify the main entities, their attributes, and relationships. Consider the choice of database type (SQL vs NoSQL) and justify your decision based on access patterns...
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
1. ID Generation — Snowflake-style → base62 We mint a 36-bit unique numeric key: [timestamp | datacenter ID | machine ID | sequence], then encode to base62 → 7 chars (62⁷ ≈ 3.5T keys ≈ 96 years of headroom at 100M/day). Uniqueness is guaranteed by construction — no collision check or retry loop on the write path. Datacenter + machine bits plus a per-machine monotonic sequence prevent split-brain duplicates; a machine with clock skew relies on the sequence counter continuing from its last value.
Alternatives considered and rejected: 128-bit UUID → ~22 base64 chars, too long for a shareable short URL; shared autoincrement DB → becomes a single-point bottleneck to scale; hash-and-check loop → extra disk seek per create. Keys are enumerable, but the data (URL mappings) isn't sensitive, so guessability is an accepted trade-off.
2. Write path — synchronous write-through (no queue) Create flow: gateway rate-limit check → URL validation → generate key → insert into DynamoDB → populate cache → return short URL. Write volume is only 1.2K QPS, which DynamoDB absorbs trivially, so Kafka is unnecessary: a queue only decouples a write rate that doesn't need decoupling, and async writes open a consistency window where a fresh link can 404 if the cache is lost before the batch lands. Write-through costs a few ms of create latency and guarantees the mapping is durable before the user ever sees it — so a cache failure can never lose or 404 a valid link.
3. Read path — L1 → L2 → DynamoDB
DynamoDB scales horizontally to 300K+ read QPS with no sharding logic to maintain, so read replicas aren't needed — the store is the horizontally scaled tier. Request coalescing is therefore optional polish, not survival machinery: single-flight per key (lease + waiter timeout) is only added if measured p99 degrades under extreme hot-key skew.
4. Abuse protection — tiered access + delayed activation Rate limiting alone can't stop a distributed botnet (per-IP limits leak IPs × limit), and banning IPs collateral-damages shared-NAT users. So access is tiered:
Banned links live in a key-value store mirrored into gateway local memory via pub/sub; the gateway returns 403 before any cache or app node is consulted, so 1s L1 staleness never serves a banned link. Client blocking (edge ban) is used for clear abuse, but layered behind the tiering so good users aren't collateral damage.
5. Load balancing L4 load balancer for low latency and uniform distribution; L7 grouping of reads/writes causes hotspots with a few million always-active users.