System Design: Basic approaches
Tags: webdev, devops, architecture
Published: 2023-03-27 21:13:16

System design is a very important topic in the technology industry today. It doesn't matter whether working in a decision-making role or as part of a development team, having a basic understanding of system design is essential.

#### TL;DR;

- Systems need to adapt to their requirements as they are created or as they grow:
  - respond faster;
  - respond to more simultaneous users;
  - use more storage;
  - be highly available.
- To adapt, systems have to scale. They basically must use vertical or horizontal scalability techniques.
- Scalability must consider the costs to implement each different solution.
- Basic System Designs:
  - 1 server (application + database);
  - 2 servers (1 for application, 1 for database);
  - Load Balancer (many application servers).
- Database strategies beyond how to set up hardware can also apply:
  - Database normalization or denormalization.

## Introduction

One of the major challenges for professionals in IT is system scalability. Planning how to design a system to meet all non-functional requirements is challenging and uncertain.

The design must be carefully assessed before releasing a system, or it needs to be reviewed after problems arise.

This article aims to list a few "basic approaches", which can be easily found in many small systems, mostly the ones hosted on-premises. 

Designing a system needs to take many aspects into account:
- How many users will concurrently use the application?
- How much space is needed? How will it grow?
- How crucial is the availability of the service?
- How much money can be expended with services and/or hardware?

Below there are some concepts and the presentation of basic system design for applications.

## Types of Scalability

Scalability relies basically on two pillars:
- Vertical Scale
- Horizontal Scale

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rm1rtwayx3ugym2tlo5v.png)

### Vertical Scale

In short, it implies that, when the system is not responding as desired, then **more hardware has to be added for it**. Hardware like memory, CPUs or disk space are usually added.

### Horizontal Scale

In short, it implies that, when the system is not responding as desired, **more machines have to be added for it**. System infrastructure is increased with other servers, considering that there are separate concerns, redundancy, etc.


## In old (or very old) days

### Single Server Design

In the "old days", the common design of a system consisted in having one server with all the services, usually maintained on-premises.
The same machine was responsible for hosting the web server for the application, the database, the filesystem and whatever else was needed.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7tje5i0wyhrlwlft1r7x.png)

This approach is very fragile, as many things can interfere in the availability or usability of the system:
- if the CPU has to process a complicated query - it will make the whole application very slow;
- if the disk is out of space, it will make the application unavailable;
- if the web server has too many requests, it will return an HTTP server error response (500 errors).

Apart from the things mentioned above, it has a "single point of failure". It means that, if the server goes down, the whole application goes down too.

In this type of design, to scale a system, it is usually used a technique called "Vertical Scale", where the server gets more RAM, hard disk or processors (CPUs).

Even though it is an approach that has been in place for years, the Single Server Design still has space for small systems, usually sitting in a computer inside the office, which serves daily tasks for a limited number of users (e.g. 10 users or so). It can be set up on-premises or even in a cloud provider.

### Application Server + Database Server Design

A better approach is to separate the database from the application server. It usually brings more resilience for the architecture, although it still has a single point of failure.

In case the CPU is overloaded in the application server, at least the database is isolated and can still operate. Even the risk of corruption is reduced since the database uses a separate machine with a dedicated hard disk and processors.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x27mohpvwz6i9gmxah88.png)

### Adding a Load Balancer

> Modern approach for scaling "expects" Horizontal Scale.

Another option of a system design is to set up the application behind a Load Balancer. It distributes the loading of the application between different servers.

> A load balancer is a device that acts as a reverse proxy and distributes network or application traffic across a number of servers. Load balancers are used to increase capacity (concurrent users) and reliability of applications. ([What is a Load Balancer?](https://www.f5.com/glossary/load-balancer))

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b47hy8v5diz6kgl8uksw.png)

This approach works well if servers are "stateless", where they don't store anything that makes a request depend on what a previous request has stored.

However, it still depends on a single point of failure. If the database crashes (for any reason), the application cannot rely on any other data source.

## Database Normalization

> Normalization is the process of organizing data in a database. This includes creating tables and establishing relationships between those tables according to rules designed both to protect the data and to make the database more flexible by eliminating redundancy and inconsistent dependency. ([Description of the database normalization basics](https://learn.microsoft.com/en-us/office/troubleshoot/access/database-normalization-description))

Normalizing data is something very basic, and we usually tend to do it to avoid redundancy by creating more tables and relationships.
Yet, it may not be the best solution for a given problem.

The better we organize the database and avoid redundancy, the less space we use. And that is usually great. On the other hand, we usually spend more time to return query results due to joins between different tables or multiple queries to return a set of data.

It is important to define what's the trade-off. If performance a high priority and there isn't a concern about how much it can cost to store all data, denormalizing the database may be an option.

### Example

The example below shows a very simple invoice stored in a normalized database. The data is stored in two different tables:
- `INVOICE`
- `CLIENT`

#### Normalized database

A normalized database uses less storage, but it requires more lookups.

##### Table `INVOICE`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oex6j1jyvloxpmxybcju.png)

The table has a foreign key (CLIENT_ID) that enforces the relationship with the table `CLIENT`.

##### Table `CLIENT`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zc4il9i35dyi8tefuxwq.png)

The table `CLIENT` stores the client's information and avoids duplication.

#### Denormalized database

A denormalized database uses more storage, but it requires just one lookup.

##### Table `Invoice`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cuopvrksjdr7wwj78qro.png)

The question is: do you need to respond quickly without multiple joins and queries? Or, do you need to minimize disk usage?

## Conclusion

This article presents very basic System Design for applications. They can be widely used for applications that do not require large scalability.

The database denormalization technique can help to minimize bottom necks of performance in high-availability applications, however it has its downside, which is more use of disk-space.

It is important to consider that the simplest architecture that meets the project traffic requirements should be used, <u>but not the smallest</u>.

More to come in next posts about System Design.