Estimate the scale of the system. Consider daily active users, read/write ratio, storage requirements, bandwidth, and any relevant QPS calculations...
We have two main endpoints.
We have 4 main components: server(API), Key Generation Service, Cache and Database.
The two request flows:
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...
First component, Key Generation Service (KGS).
KGS is two part component: service and database. KGS generates upfront a list of short codes and stores them in its own database, where we have code and status (used or not-sued). KGS loads a small batch of codes from KGS database and uses random() in the query and marks them used, so now KGS has the list in memory for faster access. KGS marks each batch as used in the db atomically before loading into memory, ensuring no two nodes ever hand out the same key. When server receives POST call, server asks KGS for the code and afterwards saves the whole new entity in the main database.
This component is single point of failure. If it goes down, server cannot create new short urls. That is why we need to make KGS redundant. One KGS database but multiple KGS services.
Second component, Cache.
Cache component is important in GET request flow.
This whole system mainly deals with GET requests not POST. Let's assume its 100:1 read/write ratio. First, cache would be populated whenever there's a GET request and the url is found in DB and then we cache it. System would cache 20% of urls that probably would be responsible for 80% of traffic. Main worries is eviction policy for the cached urls. I would use Least Recently Used principal, so when cache is full system drops items which were accessed least recently. Probably would add Time to Live (TTL) policy as well to drop urls that havent been accessed in a fixed time window and TTL would also ensure that urls with expiration date also are dropped. If user disables URL, explicitly purge it from cache immediately. For viral links need to use CDN.
Third component, Database.
Main DB is partitioned across multiple nodes using consistent hashing on the short code. Same code always maps to same node, every lookup hits only one shard directly. Adding nodes only moves small slice of data from one neighbor node.
This system favours availability over consistency — a newly created short URL may take a few seconds to propagate across nodes, which is acceptable. The main bottleneck at 10x traffic is the database layer — consistent hashing and read replicas would need to scale horizontally. KGS is the most fragile component; at extreme scale we'd consider replacing it with a distributed ID generation approach like Twitter's Snowflake
Rate limiting - Per-IP throttling on to the POST endpoint, like 100 urls per hour per IP, so someone cannot programmatically create unlimited short urls and ddos the server.
Bust handling on GET, to stop someone hammering the GET endpoint or bots crawling.
Implement via token bucket or sliding window counter sitting in front of server. Return 429 if limits are exceeded.
Short codes are generated using randomly sampled base62 characters (a-z, A-Z, 0-9), not sequential counters, ensuring the keyspace is unpredictable and resistant to enumeration attacks.