How to design a highload app
Tags: highload
Published: 2020-07-11 18:35:27

In this article, we will talk about basic approaches to design highload application. It is easy do not worry) This article is separated to three blocks:
Several basic approaches that we use to build highload app
Step by step algorithm
Practice
In the next articles i will talk about each approach in more detail
Basic approaches
Service oriented architecture — The application should be separated into several services. Each service should be responsible for only one reason. It is like a single responsibility principle from SOLID.
Advantages: 
- we can develop each service in parallel. One team will work on one service, another team will work on another service
- if one of our services crashes, other services will continue to work.
- we can choose the best technologies for each our service
- it is easy to scale
Disadvantages: 
- there is an overhead for communication between services
- it is really hard to debug 
- poorly designed services can become worse than a monolith
Vertical scaling — The easiest way to scale your application. Just add some memory or processor resources. It’s cheaper than refactoring an application.
Horizontal scaling —It means that we have to keep our application on several servers so each request can be handled on any of our servers. To do this, we should not store any state on our servers, and we should not have common nodes. Each server must be independent. Also i want to say about three-link architecture. We have frontend, backend and storage. Frontend should return static content, buffer and balance the load on the backend in order not to break the backend and keep extra requests in the queue. The frontend should also handle slow requests. Imagine a user with a very slow connection downloading something from your server. He will download for a long time and all the time your server will be busy. Instead, the web interface performs this task and gives a response that was made on backend)to the user. The backend will be free very quickly
Deferred calculation — Why do something now if we can do it tomorrow)) If it is possible to do something later let’s do it later. Queue it and do it later. For example, sending emails or calculating statistics
Asynchronous requests — We must avoid synchronous requests to other resources. For example, if we need to get data from an external API, let’s do it asynchronously using crontab and put the data in a database or cache. After that, we can take data from the database or cache and show it on our website. We mustn’t make synchronous request to external APIs
Heavy client — Delegate some tasks to the browser. For example, the browser collects content from various services and displays. The heavy client is used on social networks
Cache — Cache is used everywhere. It helps to decrease response time but it is not good if your application can not work without cache. It is usually used to store responses for difficult queries and calculations. Also to cache static pages which take too much time for rendering. There are some problems with cache: cache invalidation and unheated cache
Sharding — When a single server cannot store such large data, we can store it on multiple servers. But we have to choose some criteria for data separating. There are several approaches to calculating the necessary shard: 
- Hash function on some criteria divided with remainder into a number of shards. In this case we ьфн encounter a problem when the data becomes too large and does not fit in all shards so we have separate it again.
- Сentral dispatcher which deals with separating data. Yes, this may be the place that fails first because of number of requests but it is really flexible thing
Tricky question: how to sort data by all shards? I will answer later.
BTW It is really helpful to store data on plenty of virtual shards but on one physical server and when free space will end we can easily move half of our virtual shards to another server
Replication — read operations are usually much more than write operations. Therefore usually we use master-slave replication with several servers for reading and one server for writing
Partitioning — Take a large table and divide it into several tables. For example, a news app. The most readable news is news for the last 24 hours so let’s store the news for the last day in a separate table.
Tricky question: how to move old news to the archive table? I will answer later.
Denormalization — We intentionally duplicate data and introduce redundancy to quickly show data to the user. It is difficult when we write something but we can wait a little while we write.
That is all i wanted to say about approaches. Now let’s talk about step by step algorithm
Step by step algorithm
Find out key business tasks, logic and development paths
Calculate the amount of data, the rate of their growth, where are the critical moments. Find out the numbers. How many users. How many pictures etc. We need to know the numbers that we expect. This way we will find out the bottlenecks.
Find out the degradation of the system. that is, where you can move away from the requirements. For example, more time to update a news feed on a social network.
Design data flow patterns. What happens to the data from the moment of creation to its end.
Design the system (with our approaches above)
Let’s try to crash our system
Practice
Designing social network profiles
Step 1 (key business tasks). Our system should have the following abilities:
- Create, update and store user profiles. 
- Logging system
- Users can see other profiles
Step 2 (numbers). We expect 200 000 000 users. Each profile takes 10 Kb (total 2 000 Gb). 5 000 000 000 daily hits
Step 3 (degradation). It’s impossible. It is important to show profile instantly
Step 4 (data flow patterns). Data is often read but rarely changes. All profiles take same space approximately. There is no leader in profiles. Each profile has id.
Step 5 (design). Can we use replication here? It is not a good idea. We have 5 billion hits per day (140k requests per second). In this case, we have to have a lot of servers to store the whole database. Let’s use sharding. But how to separate data ? What about profile id or by registration time? Then it will be an uneven distribution and some of our shards will be very busy while others will be free. The common practice is using a hash function (by login, for example) because a hash has even distribution. But keep in mind to use virtual shards first. For example, 10 000 virtual shards on 100 real servers. To protect our shards from losing data we also can use replication for shards (not for the whole database)
News site
Step 1 (key business tasks). The user reads fresh news. The user reads old news. Editor publishes news
Step 2 (numbers). Each news takes about 10 kb. We store news forever. About 10 000 new news every day. 35 000 news per year. 35 Gb for news per year. 10 million users per day
Step 3 (degradation). It’s impossible. It is important to show news instantly
Step 4 (data flow patterns). Almost all users read news for the last day or week
Step 5 (design). What about sharding? As you can predict we will have a very very busy shard for fresh news and almost unnecessary shards for old news. Good idea to use partitioning. Create table for fresh news and another table for old news. Here is answer for my tricky question: how to move old news to the archive table? Answer: no way) let’s write each news in each table at the same time. Then we do not have to move news from one table to another table. Easy) Of course, sometimes we will have to clean up table with fresh news 
Let’s also use cache for table with fresh news.
Friends news on social network
Step 1 (key business tasks). Each user can have any number of friends. Friend news are stored forever
Step 2 (numbers). The average user has 100 friends. The user writes an average of 3 posts. Every post is 1 KB. 10 million users per day. Each user makes 100 hits. Total one billion requests for friend per day. 30 million posts are generated per day. 10 billion records per day. There are leaders (popular artists for example)
Step 3 (degradation). The message may not be shown at the time of it’s creation. Friend news can be not in chronological order.
Step 4 (data flow patterns). The most of requests to head of friend news. There are users with millions of followers
Step 5 (design). How to receive friends news? It is impossible to check each friend for new messages. Servers will not withstand such a heavy load. It is better to store a list of friends news for each user. But only ids. If one of our friends writes a new post, we must add the post id to the news list of each of our friends. But what about someone popular who has 100 million followers. After all, we must write 100 million identifiers. Use the queue and make it later. 
When browser requests friends news our server gives first 10 post id’s. Notice that we use here a heavy client so browser should get post bodies from somewhere else.
Tricky question: how to sort data by all shards? Answer: no way again) I mean, it is better to use another table with sorted data. That is, introduce redundancy
That is all, for now, guys) I will talk about each approach again in more detail later. Have a good day and highload system)