Ruby on Rails is a great framework to work with for startups and smaller applications, but we often come across the scalability issues when the project grows larger. Lets delve deeper into what a frameworks scalability means.
Consider the below architecture at the start of the Rails project –
This is a single server on which the following softwares are installed
- Nginx server;
- Rack application server – Puma, Passenger, or Unicorn;
- Single instance of your Ruby on Rails application;
- Single instance of the database; usually, a relational database like PostgreSQL.
Now this configuration can cope with 1,000 or even up to 10,000 requests per hour easily. But let’s assume that the application became very successful quickly and is now receiving 10 or 100 times more requests. What happens is that the application becomes unresponsive to the users as a single server architecture will crack under pressure of the increased load.
Boost server with more power
Also known as vertical scaling, it is the simplest way to make the server handle an increased number of RPMs. What you essentially need to do is add more RAM, upgrading the server’s processor, etc. In other words, you give your server computer more power. Unfortunately, there are several issues with this approach –
- This is good at the initial stage, but when the traffic keeps increasing, adding more RAM will become technically impossible
- Some components require more computational resources. In the case of Facebook – The requirement for updating news feed and processing images needs servers offering different performance. The news feed is used more frequently than image processing, this would require a more powerful server for newsfeed than for image processing.
When vertical scalability is no longer practical, we can look into scaling a Rails application horizontally.
Convert single server architecture to a three-tier architecture
Also known as horizontal scaling, the server and load balancer (Nginx), app instances, and database instances will be located on different servers, in order to allocate equal and smaller loads among machines.
A commonly used server software for Rails applications, Nginx is deployed on a single machine to serve as a load balancer and reverse-proxy. As it requires very little computing power to function normally under high loads, a medium powered server is sufficient.
The server could be set up to receive the initial request and forward it to the first machine. The second request will be sent from Nginx to the second machine, and so on. If you Rails application instances uses three machines, then the fourth request from the client (browser) will be sent, to the first machine again.
As we mentioned previously, you need additional servers to run app instances separately from the Nginx server. From the user’s perspective, the app stays the same; they simply access different app instances thanks to Nginx.
For communication between the application and Nginx, we use a special interface called Rack, which is an application server. There are many application servers for Rails-based apps, the best known being Unicorn, Phusion Passenger, and Puma. Application servers are responsible for input-output, letting the application deal with user requests.
Although Rails applications are mostly monolithic, we can extract a functional block and use it as a standalone service. This will also lessen the load on servers.
Another important aspect of scaling a Rails application is scaling the database. To economize we can deploy a database on the same server as the application, but this approach has several drawbacks. Consider an instance where Nginx redirects a user to a different app instance than where their data is stored, they will not be able to sign in because their data is located on a different machine. This happens when every application instance saves and retrieves data from its own database instance, then data for the entire application is spread across many machines.
Keeping the database separate from other servers enables to create a fault-tolerant architecture. This ensures if a database receives too many requests and collapses, the other database instances in the data tier can accommodate this load, and the Rails application will continue to work.
It is always recommended that the database should be transferred to its own server right away, in order to scale the Rails application. A standard procedure for multi-master relationships among databases is to have a tier consist of several machines, with each running databases. In order to update data across all databases, we use database replication. In a master-slave replication variant, databases communicate with the master (or main) database, which stores important data about other databases. All other database instances receive updates only when the master database updates data. Multi-master database replication and master-slave replication are the two common approaches to making our data layer consistent across multiple databases.
There are lots of resources on the web that explain how to scale web applications and how to scale Rails. Let’s say you want to spend as little money as possible on new servers. Is it possible to scale your Rails application while minimizing server costs? Indeed it is.
Take full advantage of Rails’ built-in action, page and fragment caching. Use Memcache to cache results that you’d otherwise pull from your database.
Minimize & handle external dependencies
Watch for dependencies on external services like ad serving networks or RSS feeds. If a service isn’t responding or can’t handle your growing request load, make sure that you have a fallback strategy.
Tend your database and your job handlers
Scrub your database periodically for indices that are no longer being used. Similarly, watch the resource consumption of your background and scheduled jobs. As your user base grows jobs can start to overlap, and daily log processing can start to take more than 24 hrs! This kind of thing can sneak up on you easily.