Estimate the scale of the system. Consider daily active users, read/write ratio, storage requirements, bandwidth, and any relevant QPS calculations...
There will be 2 services: redirect service and a tiny URL generator service. Requests from the clients will hit a load balance, which will route the reads and writes to the 2 services. The POST endpoint will reach the tiny URL generator service. The GET endpoint will reach the redirect service. The POST endpoint will map the generated tiny url with the original url. There is a potential that clients may generate already tiny URL, so whenever a tiny url is generated, the service should assign a unique id to it. In the database, the table should have a column for the unique id, a column for its corresponding tiny url and the original url. For the unique id, we can use the UUID to ensure for uniqueness. (UUID is unique but long i.e. 36 characters, base62-encoded is 22 characters, so compact. )For the GET method, it will read short key, looks up the original URL and return 301 status code. For lower latency, we can insert a cache layer between the redirect service and the database.
The database stores one core table: url_mappings with columns id, short_code (the base62 key), long_url, created_at, and expired_at for TTL. THe short_code gets a unique index since every redirect is a lookup by that key. For scale, horizontally paritionable store like a DynamoDB with short code as the primary key or sharded MySQL keyed on a hash of the short code. The way each redirect hits exactly one node, no scatter or gather. For consistency: strong on creation, a generated key must never be duplicated and must be durably stored before returning 201 to the user. Eventual is acceptable on reads, a briefs delay before a new mapping propagates only risks a transient 404, never corruption. A cache layer like Redis can be put in front of the database, which will remain as source of truth.