A service that has an endpoint to create a short URL and an endpoint that redirects to the real URL given a short URL.
Scalability, the service should be able to adopt to the increasing number of customers. Fault tolerance, the system should tolerate certain API service being down. Availability, the service should have low downtime. Performance, < 1 s creation and < 10 ms redirects. Security, HTTPS.
Creation: 200 rps. Redirection: 20 000 rps. URL: Up to 100. Short URL: typically 8.
Data consumption: URL 100 bytes, short ULR 8, metadata 50. So up to 256.
Over 5 years, 256 bytes * 200 writes * 60 * 60 * 24 = 4.4 gb per day
POST /v1/shorturl { "url": "..."} that returns the { "shorturl": "..." }
GET /v1/url/{shorturl} that returns { "createdAt": "timestamp", "url": ""... }
HTTP request to the short URL redirects with 308 permanent redirect.
The authentication for POST and GET is API key bearer token.
The main storage is a relational database with the schema URL, short URL, createdat, createdby. The storage is highly available and has read and write replicas for load balancing and failover. Short URL as index.
For frequently accesses URLs we have a memcached/redis cache that stores the recently used data. The data that is not read for a time period, e.g., an hour, is removed from the cache.
Client reaches out to the load balancer. The load balancer is stateless scalable and based on the load scales up and down. I load balances by round robin, no need for better load balancing here. The API service is a stateless service that implements both REST POST/GET and the redirects. If we later one prefer to scale REST and redirects separately, we may move the redirect service to a separate service. The write path writes to cache and then to the database (write through cache). The read path checks from cache and, if not found (the cache also includes caching "not found"), reaches out to the database and caches the result and returns it to the client. The redirect part of the API service performs the same procedure.
POST: The client runs the HTTP REST request. The load balancer selects some API service. The API service writes to cache and the database.
GET: The same as POST but reading instead of writing
Redirect: The same as GET put the response is the redirect status code with the URL to redirect to.
The caching layer. It's a redis cache with some redundancy, e.g., 3. We may increase the redundancy in case our service growth. On later stages, we may consider partitioning cache, e.g., for a given short URL for a redirect, to hash it and have a hash ring of redis groups to handle different URLs.
The database. We start with a relational database with a short URL as a primary key. The same as with the cache, we may introduce a distributed database with a short URL range of hashes as a partitioning strategy.
SQL vs NoSQL. SQL is great at surgical lookups, like by a primary key/index. It's more difficult to stale yet better for consistency than highly scalable nosql solutions such as Casandra. Also putting redis on top of sql fixes the load issue.
The biggest are for failure is the database. It should have failover considered. So, if the main database goes down, the system starts using the read only replica as the primary one.
Switching to a distributed database for the future.