Assuming there are 100,000 URLs shortened per day and 1,000,000 clicks per day on those shortened links.
In terms of storage size, we have 100,000 * 200 bytes = 20,000,000 bytes = ~20 MB per day to save in database. It's 7.3 GB per year.
Write capacity: 100000 / 86400s = 1.15w/s peak times 3. 3w/s
Read capacity: 1000000 / 86400s = 11.5r/s Peak times 3. 33r/s
All the api below can only be accessed by authenticated users.
POST /shorten: accept original url as input and return a shorten url as response.
GET /shorturl: Redirects the user to the original long URL based on the short URL.
GET /analytics/:shortUrl: Get analytical data for the short url passed in.
POST /shorten/custome: accept original url and custom alias as input and return a shorten url as response.
URL table:
Create index for shortUrl
Analytical table:
flowchart TD
B["client"];
C{"Shorten service"};
D["NoSQL Database"];
E{"Analytic service"};
F{"Retrieval service"};
G{"API Gateway"}
H{"Redis cache"}
B --> G;
G --> C;
E --> D;
C --> D;
F --> H;
G --> E;
G --> F;
H --> D;
sequenceDiagram
Client->>Shorten Service: Shorten the url
Shorten Service->>Data Base: Save shorten url to db
Shorten Service->>Client: Here is the shorten url
Client->>Retrieval Service: Go to this short url
Retrieval Service->>+Cache: do you have the resource available?
Cache-->>Data Base: No, fetch it from DB
DataBase-->>Cache: Here is the data
Cache->>-Retrieval Service: Yes. Here you go
Retrieval Service->>Client: 302 Redirect
For the shorten url service, i would like to use an algorithm to calculate the shorten url efficiently, may be use its database self-increment id as a seed, so that database can help us to do the deduplicate.
For the url retrieval service, we can use Redis served as cache so that it can return result for most recent queries. We can also set expiration for the cache data as the same as the expiration date for the url itself.
To achieve high availability, we can have api gateway to route user's request to healthy service node and spin up more service resources to serve peak time.
For Data Persistence, we can create db replica so that we can fail over to backup database in case main db is down. We can do eventual consistency here since the data for this case is not necessarily in sync in real time. And assuming occasional data loss is acceptable.
We are using cache to improve read performance, so it is adding complexity to the system since we need to make sure cache data is deleted once the url is expired.
The algorithm we are using to generate short url is relying on database self-increment id, it will be challenging if there are multiple database servers. Since their id generation is independent
And we are using eventual consistency for data sync here, so it may cause the issue that data may be lost and user may not be able to get the data they want if the data is still waiting for sync.
The initial design didn't use cluster for some of the services, so it may have single point of failure issue in case the service is down.
No cache for analytic data, there will be lots of reads to database if many users are querying for the same url.
If the cache is expired, and lot of requests come in, all of them will hit the database.
If the shorten url is posted by a famous person, there will be lots of queries come at the same time, since the url is not in the cache yet, all the requests will hit database.
Use clusters for all services so that we always have back up for services.
Need to create a filter service to filter phishing urls.
Create cache for analytic data.
If the url is expired, set the cache to return an empty result for query.
For the famous urls issue, there needs to be a lock on the searched key so that other threads need to wait when some thread is trying to search the key and update the cache. After the cache is updated and lock is released, other threads will be able to read the newly cached data. Or we can proactively push the url to cache if the url belongs to a power user