Introduction
Usually, when writing basic back-end architectures for small business is common to make some mistakes like “skipping scalability”, “disregard performance”, “poor availability”, “hard maintainability”, and so on… To keep from these mistakes, we should be aware at all times how every decision could impact our back-end architecture (either choosing the right tool for every layer or improving code quality).
First of all, lets set the main goal. We need to implement a scalable back-end architecture for an MVP and able to support thousands of users without loose performance. Knowing the scope of our goal, we can start thinking about some approaches to start with.
In our case, we decided to go ahead with an approach based on Redis (database), NGINX (reverse proxy), and AWS (S3 and EC2). For the API, we’ll test 2 languages (Golang and Node.js) against themselves to get the better one.
So, throughout this post, we’ll cover how to scale a basic back-end architecture with the approach mentioned previously and finally test the API language options by using Gatling (load testing tool) to see which adapts better to our goal. Let’s get started!
Approach
The approach consists of:
- An Amazon EC2 instance that will be our server.
- An NGINX reverse proxy to add a level of abstraction and control to ensure the smooth flow of network traffic between clients and servers.
- An API based on Golang/Node.js.
- A Redis database to store all the needed data from the API.
- An Amazon S3 to store all media content.
The image below shows this approach in action.
Note: We aren’t using a Load Balancer to keep the architecture as simple as possible, although it’s highly recommended to implement.
In order to have a better context of the approach, let’s deep dive into each part of the architecture:
Amazon EC2
The most critical part of this approach is the use of an Amazon EC2 instance. We need a server where the whole back-end runs, able to increase/decrease the capacity of the resources as needed, and highly available. This makes Amazon EC2 the best for the job.
The Amazon EC2 instance type chose for this architecture is a t2.micro, and as long as the active users increase, we change it by another one with more resources. Keep in mind that you have to choose the Amazon EC2 instance type adapts better for your needs (the number of active users you expect to have your MVP).
In the image below there are more details about the Amazon EC2 instance details.
NGINX Reverse Proxy
Every part/layer of this approach is important and this one isn’t the exception. We need an intermediary server that forwards requests for content from multiple clients to different servers across the Internet. So, there are many options for choosing a reverse proxy tool, but just one can give us a straightforward setup, confidence and most noteworthy, be capable to bring us three important uses:
- Load Balancing (NGINX server traffic cop).
- Web Acceleration (SSL encryption and compress inbound and outbound data).
- Security and anonymity (Acts as an additional defense against security attacks).
NGINX provides us solutions for all requirements previously mentioned, becoming the right tool for the job.
Let’s take a look at the configuration file (Needless to say NGINX must be installed in the Amazon EC2 instance).
Note: There are some potential improvements to make at this file, but it’s fine for a basic example.
What is doing here the NGINX server is receiving all requests at the server ec2-0-00-00-00.region.compute.amazonaws.com (EC2 instance DNS) and depending on the URL route, will redirect them to the local APIs served at the Amazon EC2 instance by applying all uses we talked about. Then the local API will get back a response to the NGINX server, and it will send a response to the client from the same server ec2-0-00-00-00.region.compute.amazonaws.com.
Notice that our NGINX server has SSL encryption, making it more secure. Generating the SSL certificates is straightforward with openssl.
And last but important, we have a configuration to redirect all HTTP requests (port 80) to HTTPS (port 443).
Note: Make sure to use your domain instead of the EC2 instance DNS
Redis
Here comes the database! First of all, we need to highlight that you can choose the database that better adapts to your business model. In our case, we’ve chosen Redis because of is much more than a key-value store: it is a server that implements data structures in memory. In practice, this means that you have access to fundamental constructs like lists, hashes, sets. These data structures are implemented with amazing quality, consistency, and performance. Also, it’s extremely fast.
Let’s see how our Redis database is running in the Amazon EC2 instance.
For our use case, we added a “game” key with the “battleship” value.
Amazon S3
One amazing choice for storing media content in the cloud. We’ve created an Amazon S3 bucket for that. Even we can upload Redis dumps to S3 periodically. In this way, we can have a backup of our database.
Golang
What we are looking for is an API language that is fast and it’s designed with concurrency in mind. Golang meets the requirements since its focus is on concurrent applications. The major advantages of this language are; has a low memory footprint and is fast. Its simplicity makes it a good choice for this case.
Golang lacks an LTS version. Instead of that, go has an approach where every release has 1-year support (where are fixed some security issues, etc.), but the syntax is always the same.
At the time we wrote this post, we’ve chosen the 1.13.4 (1-year support) version of GO for the test.
Node.js
Node.js is a really good choice for our goal. It’s an asynchronous language, fast, straightforward and with a large community supporting it. Also, it has an LTS (Long Term Support) version, which implies we’ll have support for a long time.
At the time we wrote this post, we’ve chosen the V12.16.1 (LTS) version of Node.js for the test.
Load Testing
Our major goal is our architecture be capable to support thousands of concurrent users. To accomplish this, we’ll use Gatling (a Load Testing tool). We’ll have some test cases. For both GO and Node, we’ll create a test case using a GET and other one using POST HTTP methods, hitting to each of them a number of current users of 10, 1000, and 2000. So, let’s go through each test case.
Golang
- GET
For this test case, we just hit the GO endpoint https://ec2-0-00-00-00.region.compute.amazonaws.com/go with “GET” HTTP method and no parameters.
10 Users
This execution took: 1.2 seconds
1000 Users
This execution took: 4 seconds
2000 Users
This execution took: 8 seconds
- POST
For this test case, we just hit the GO endpoint https://ec2-0-00-00-00.region.compute.amazonaws.com/go with a “POST” HTTP method and 5kb of data in the request body.
10 Users
This execution took: 1.5 seconds
1000 Users
This execution took: 4.5 seconds
2000 Users
This execution took: 9 seconds
NODE
- GET
For this test case, we just hit the NODE endpoint https://ec2-0-00-00-00.region.compute.amazonaws.com/node with a “GET” HTTP method and no parameters.
10 Users
This execution took: 1.2 seconds
1000 Users
This execution took: 9 seconds
2000 Users
This execution took: 12 seconds
- POST
For this test case, we just hit the GO endpoint ec2-0-00-00-00.region.compute.amazonaws.com/node with a “POST” HTTP method and 5kb of data in the request body.
10 Users
This execution took: 1.5 seconds
1000 Users
This execution took: 10 seconds
2000 Users
This execution took: 13 seconds
Notice the Node API couldn’t handle all users efficiently with 2000 concurrent POST operations.
By seeing the metrics, we can notice that for our goal (support a lot of concurrent users with no performance degradation), GO could be a better option than Node.js since ith has better response times and it’s a little bit more consistent.
Conclusion
In general, terms, if you want to scale a backend architecture, it’s important to have more than one option when choosing a language for the API, Database, and so on… Doing this kind of test is really important if you want to make sure you are making the right choice, even more, if scalability and performance are present in the game.
In fact, we’ve describe how we scale a basic backend architecture. There are more ways to do so, but just one works for your goal and that what we wanted to show you.
Thanks a lot for reading!