Here's something that happens to every great product: sooner or later, it starts attracting more users. Since those new users expect the same performance and availability of the service as the old users, a successful app needs to handle this increased traffic volume.
As your application grows over time, it needs to handle more requests per minute. If you don't prepare it to handle that extra load, its performance is going to drop. And you know what this means, right? You might lose your users – even the ones who were with you from the very beginning. You'll also damage your brand's reputation.
How do you build a scalable app? And when should you start considering to scale your web app?
Keep on reading this article to see all the key factors teams should consider when building web applications to ensure that they're scalable and deliver excellent performance no matter how many people are using them at once.
Table of contents:
- What is app scaling and why should you consider it?
- What are the most common problems with application scalability?
- How to scale applications effectively?
- App scaling – summary
What is application scaling and why should you consider it?
Let's start with a definition: what exactly is scalability in web apps?
Application scalability basically refers to the potential of your app for growth over time. The idea is to build a web app that can handle more and more requests per minute efficiently.
You can probably tell by now that scalability isn't a simple on and off switch. It's a long-term process that involves almost every item in your technology stack. The problem of scalability touches both the hardware and software sides of your system.
If your web app is experiencing performance problems, can't you just add a few CPUs or increase the memory limits of your infrastructure?
If you do so, you're only boosting the throughput and not the performance itself. If your application is poorly designed, you may get to the point where adding more and more resources just doesn’t help as much as you desire. When you see that your web application has reached the limits of scalability, the solution above isn't a way out.
Unfortunately, developing a scalable web application isn't that easy and you need to take into account all the complexities around vertical scaling, horizontal scaling, and managing multiple servers.
That's why it's critical that you know your application really well before you start scaling it.
What are the most common problems with application scalability?
As mentioned before, scalability comes with a number of challenges. If you focus on your web application and its ability to scale, we're talking more about the entire system architecture – not only the framework you used to build it. By the way, scaling frameworks can be painful, so your team might consider switching to another framework capable of handling larger loads while you’re at it.
Some frameworks simply perform better than others. For example, Ruby on Rails has worked really well for Shopify at the backend for around a decade now. It can handle even 80,000 requests per minute and offer a blazingly fast 45ms response time.
Even if your application isn't receiving that much traffic, developing it with scalability in mind right from the start is a smart move.
What are the key problems developers might encounter when it comes to scaling? Here is a list of the most common issues teams run into.
Developers often encounter limitations in physical resources related to their underlying infrastructures like memory, CPUs, storage, and others. Addressing them right from the beginning helps to avoid problems in the future, no matter if you're using public cloud services, a hybrid solution, an on-premises solution, or a private cloud.
Problems with databases
Another common scalability problem is related to the use of an efficient database engine. Teams also note complicated database schema or bad indexing as potential culprits behind the unsatisfactory performance. Poorly written database queries inside your database will impact the application's performance as well.
Some of the most common reasons behind poor web performance are wrong web server configurations, limitations on the web server, or issues originating in multiple database servers. For example, you may have plenty of available CPU cores, but may not have enough RAM to deploy a required number of workers.
Poorly written code
Spaghetti code, bugs, and errors – they're all going to impact the performance of your application. There’s an infinite number of things that can be done wrong.
No monitoring tools in place
Lack of monitoring tools is another common issue behind inefficient application performance. How can you tell whether your application performs well or not if you're not monitoring it? Your performance might be faltering, and you won't even know it without performance testing. And even if you do at some point, how will you identify the bottlenecks?
Other common factors
Some other factors that impact application performance are a large number of external dependencies, lacking background jobs design, inefficient caching, or incorrect memory management.
How to scale applications effectively?
Is there a way to win the web application scalability game?
Sure, developers have plenty of open source tools at their disposal today. Teams can use them to fix all of these above-mentioned problems and properly identify bottlenecks in their systems.
Here are a couple of best practices for development teams that use Ruby on Rails as their framework of choice.
Keep your stack updated
The Ruby interpreter, application infrastructure, all gems, including Rails, keep getting improved from version to version. The latest, as of writing this, major version of Ruby on Rails, 6.1, comes with a number of scalability improvements: horizontal sharding and destroying associations asynchronously. Keep your stack continuously updated to benefit from all these goodies.
Don’t miss out on the gems
Instead of paying for extra memory in advance, use gems like
rack-mini-profiler to detect database-related issues, such as the N+1 queries (more on these below).
Identify and remove those N+1 queries
The ease of use of ActiveRecord associations is often tempting to perform queries wherever data is needed, even from views. Show the town from customer’s address, or the date of last comment for each post? No problem! Or is it? This may lead to performing an additional SQL query for each of the N results that you’re processing, N+1 queries total. To fight it, enable eager loading. Just start your query with
Take advantage of database level functions
We're talking about aggregates like SQL SUM, COUNT, MIN/MAX, AVG and other features, such as GROUP and DISTINCT. Here is an example: Instead of using
Model.sum(&:value), go for
Model.sum(:value). Why? Because the second one will perform an SQL sum function and return the results. The first one will fetch all the records, allocate objects, map the collection, process every single item, and then take the value attribute. It uses Array's sum method to get you the result you need. You can probably already tell which one is faster.
Choose update_all whenever you're doing bulk updates
And make sure to do that with the same value. Note that
update_all won't perform any callbacks or validations. You can also write a custom SQL database query on your own. By using AR methods like 'update' for thousands of objects, you will only run into problems. That's why today this is considered a bad practice.
Ruby on Rails comes with a number of built-in caching mechanisms, covering the whole application stack: from database to views with even more to come as gems. However, enabling them is just half the battle. Sometimes, the cache just can’t kick in or is missed too often, simply because of a variable part of your URL or wrong HTTP method.
What’s even worse than an underperforming cache is the one that’s “overperforming” and serving obsolete content. So whenever you enable cache of any kind, make sure you have proper invalidation rules in place. Don’t leave it for later – you’ll likely forget it.
Choose UUIDs instead of IDs
Here's what happens when developers use standard incremental IDs as their primary keys: all database writes in the majority of cases will have to go through a single database. When using UUIDs, developers can spread them out across many servers, which makes their lives easier in the future.
Drop `map` and use `pluck` instead
This way, you'll be able to fetch the same attributes for multiple objects without having to allocate heavy objects.
Avoid using '#length' for counting records
ActiveRecord::Relation# length loads all the records and then uses Ruby's
#length on the resultant array. So using it to count records is a bad idea. On the other hand, ActiveRecord provides us with the ‘count’ method. It simply runs the COUNT query. Regardless of whether the collection has been already loaded, or not. Fortunately, there’s one method to rule them all: Just use
size – it will only access the database when required. Since it does a single count query, it is generally a superior option.
Keep it thread-safe
While developing in Rails, it’s easy to forget that the production architectures, unlike the development ones, often run multiple threads, processes or even physical machines. That means you can’t just keep those counters in class variables or memoize thoughtlessly. Keep in mind that class- and class instance variables are shared among threads. If you really must share your variables among threads, synchronize access to them with a mutex.
Develop like it’s production
Development environment has traditionally been much simpler than the staging and production ones. This has often led to devs being just lazy about things that may affect the scalability until the app is deployed.
However, with powerful containerization technologies, it doesn’t have to be the case any more. With a little help from your DevOps team you can have your development environment set up exactly the same way the production environment is, allowing for better insight of issues that may arise when scaling up.
There’s no silver bullet for application scaling, but you’re not unarmed!
If you're looking for an easy, bullet-proof solution – I don't have one for you, sorry! Scaling an app is a real challenge, and there is no silver bullet for it. That's because every application is a unique system.
By paying attention to scalability while developing your app, you'll be able to handle its growth really well.
Thanks to the tools available for developers in the open source ecosystem, most of the time, you don't even have to become a seasoned expert in the field to successfully scale your application. Proper database indexing, optimizing the code, efficient database queries, and cashing should be enough to do the job.
But if you need expert help, don’t hesitate to reach out to us >
Application scaling – summary
Scalability is one of the most important aspects of building web applications.
If you fail to address it early on when developing your app, you risk that once it gets popular and starts receiving a lot of traffic, it will run into performance issues. And that's not the kind of experience you want to deliver to your users, right? That's why considering scalability right from the start is in your best interest.
We hope that this article helped you understand why scalability is so critical and how to implement best industry practices to make sure that every single application you deliver performs well at scale.
Do you have any scalability tips, best practices or lessons learned to share? Feel free to drop a few lines in the comments section.