DEV Community

ZeeshanAli-0704
ZeeshanAli-0704

Posted on

Design Instagram

Design Instagram
Problem Statement:
Let’s design a photo-sharing service like Instagram, where users can upload photos to share them with other users.

Step-1: Why Instagram ?

Instagram is a social networking service, which enables its users to upload and share their pictures and videos with other users.

Users can share either publicly or privately, as well as through other social networking platforms, such as Facebook, Twitter, Flickr etc.

For simplicity, we plan to design a simpler version, where a user can share photos and can also follow other users.

Timeline for each user will consist of top photos from all the people the user follows.

Step-2: Requirements and Goals of the System

We will focus on the following set of requirements while designing Instagram:

Functional Requirements:

  • Users should be able to upload/view photos.

  • Users can perform searches based on photo/video titles.

  • Users can follow other users.

  • The system should be able to generate and display a user’s timeline consisting of top photos from all the people the user follows.

  • Profile data & update.

Step-3: Some Design Considerations

The system would be read-heavy, so we will focus on building a system that can retrieve photos quickly.

Practically users can upload as many photos as they like.

Efficient management of storage should be a crucial factor while designing this system.

Low latency is expected while reading images.

Data should be 100% reliable, if a user uploads an image, the system will guarantee that it will never be lost.

Step-4: Capacity Estimation and Constraints:

Let’s assume we have 300M total users, with 1M daily active users.

2M new photos every day and so 23 new photos/second.

Average photo file size => 200KB

Total space required for 1 day of photos: 2M * 200KB => 400 GB

Total space required for 5 years: 400GB * 365 (days a year) * 5 (years) ~= 712 TB

Step-5: High Level Design

At a high-level, we need to support two scenarios, one to upload photos and the other to view/search photos.

Our service would need some block storage servers to store photos and also some database servers to store metadata information about the photos.

Image description

Image description

The user service is responsible for managing user onboarding, login, and profile-related actions. It runs on a MySQL database, which was selected because the data is structured in a relatively relational way and the system is optimized for read-heavy workloads, which MySQL is well-suited to handle.

The user service is connected to a Redis database, which stores all user data. When the user service receives a request, it first checks Redis for the requested information and returns it to the user if it is found.
If the data is not present in Redis, the user service will check the MySQL database, retrieve the data, and insert it into Redis before returning it to the user. Additionally, a similar process can be followed whenever new users or information are added to the database.

System Components

Image description

Our system will be composed of multiple microservices, each responsible for a specific task. To store the data, we will use a graph database like Neo4j. We've chosen this data model because our data contains complex relationships between elements like users, posts, and comments, which can be represented as nodes in the graph. The edges of the graph can be used to record relationships like follows, likes, and comments. We may also use columnar databases like Cassandra to store information such as user feeds, activities, etc.

Step-6: Database Schema

Image description

One simple approach for storing the above schema would be to use an RDBMS like MySQL since we require joins.

But relational databases come with their challenges, especially when we need to scale them.

We can store photos in a distributed file storage like HDFS or S3.

We can store the above schema in a distributed key-value store to enjoy benefits offered by NoSQL.

All the metadata related to photos can go to a Photo table, where the ‘key’ would be the ‘PhotoID’ and the ‘value’ would be an object containing PhotoLocation, UserLocation, CreationTimestamp, etc.

Similarly all the metadata related to User can go to a User table, where the ‘key’ would be ‘UserID’.

We also need to store relationships b/w users and photos, to know who owns which photo, another relationship we would need to store is the list of people a user follows, for both of these tables, we can use a wide-column datastore like Cassandra.

For the UserPhoto table, the ‘key’ would be ‘UserID’ and the ‘value’ would be the list of ‘PhotoIDs’ the user owns, stored in different columns. We will have a similar scheme for the UserFollow table.

Cassandra or key-value stores in general, always maintain a certain number of replicas to offer reliability.

Also, in such data stores, deletes don’t get applied instantly, data is retained for certain days (to support undeleting) before getting removed from the system permanently.

Step-7: Component Design

Writes or photo uploads could be slow as they have to go to the disk, but reads could be faster if they are being served from cache.

Uploading users can consume all the connections, as uploading would be a slower process.This means reads cannot be served if the system gets busy with all the write requests.

To handle this bottleneck we can split out read and writes into separate services.

Since most of the web servers have connection limit, we should keep this thing in mind before designing our system.

Synchronous connection for uploads, but downloads can be asynchronous.

Let’s assume if a web server have max 500 connections at any time, then it can’t have more than 500 concurrent uploads simultaneously.

Since reads can be asynchronous, the web server can serve a lot more than 500 users at any time, as it can switch between users quickly.

This guides us to have separate dedicated servers for reads and writes so that uploads don’t hog the system.

Separating image read and write requests will also allow us to scale or optimize each of them independently.

Image description

Step-8: Reliability and Redundancy

Losing files is not an option for our service, so we will store multiple copies of each file.

So that if one storage server dies, we can retrieve the image from the other copy present on a different storage server.

Same principle also applies to other components of the system.

If we want to have high availability of the system, we need to have multiple replicas of services running in the system.

So that if a few services die down, the system is still available and serving.

Redundancy removes the single point of failures in the system.
We can run a redundant secondary copy of the service that is not serving any traffic but whenever primary fails it takes over.

Creating redundancy in a system can remove single points of failure and provide a backup or spare functionality if needed in a crisis.

If two instances of the same service running in production, and one fails or degrades, the system swithces to healthy copy.

Switching can happen automatically or require manual intervention.

Step 9: API design

login (username, salted passwordhash): logs in the user and updates the last login time

search_user (search string, authtoken): returns public user data for the given search string (can be searched in
user first name, last name, and username)

getuserby_id (userid, authtoken): returns public user data for the given user ID

follow_user(userid, targetuserid, authtoken): adds follow data to the database

add_post(file, caption, userid, authtoken): uploads the file to the file storage server

delete_post(userid, postid, auth_token); deletes the given 
user's given post along with its metadata (using soft
delete)

get_feed(userid, count, offset, timestamp, authtoken): returns the top posts after the given timestamp of users

followed by the given user according to count and offset
getuserposts (userid, count, offset, authtoken): returns the posts of the given user according to count and offset

post_like(userid, postid, auth_token): adds the given pos ID to the given user's likes

post_unlike(userid, posfid, auth_token): removes the given post ID from the given user's likes

add_comment(userid, postid, comment): adds a comment to the given user's comment on the given post

delete_comment(userid, commentid): deletes the given user's comment with the given comment ID

Enter fullscreen mode Exit fullscreen mode

Step 10: News Feed Generation

Designing a customized newsfeed for each user that showcases the most recent post from each user they are following is a critical aspect of an Instagram-like service. For the sake of simplicity, let's assume that each user and their followers upload 200 unique photos per day. This means that a user's newsfeed will consist of a combination of these 200 unique photographs, followed by the reputation of previous submissions. This allows the user to see the most recent and relevant content from the users they follow.

To generate a news feed for a user, we will first retrieve the metadata (such as likes, comments, time, location, etc.) of the most recent 200 photographs and pass it to a ranking algorithm. This algorithm will use the metadata to determine the order in which the photos should be displayed in the news feed. This allows the user to see the most relevant and engaging content at the top of their feed.

One disadvantage of the news feed generation approach described above is that it requires simultaneously querying a large number of tables and ranking them based on predefined criteria. This can result in higher latency, meaning it takes a longer time to generate a news feed. To improve performance, we may need to optimize the queries and ranking algorithms, or consider alternative approaches such as pre-computing and caching the results.

Image description

Image description

Top comments (0)