07 April 2022
We reduced the cloud bill from $30k to $2k a month. If you want to reduce yours, read this case study
“Our cloud bills just keep getting bigger and bigger!!!” This is something that we hear more and more about from various business cloud users quoted by industry pundits. But it wasn’t until we heard from one of our clients that they paid unusually huge monthly cloud bills that we decided that something just has to be done about it!
Speaking from our experience, most of the troubles with excessive cloud infrastructure costs stem from rushed decisions regarding application architecture. As a result of such actions, your company uses up a lot more resources than it actually needs. And since the majority of cloud customers use the pay-as-you-go model, the costs keep piling up.
But we have some good news for you:
- A lion’s share of this cost is most definitely avoidable.
- Even if you made non-optimal decisions regarding your infrastructure and architecture at the beginning, you can still turn the situation around. It’s not too late!
- In order to achieve it, all you need is a team of experts ready to go the extra mile to create a highly customized solution tailored to your needs.
We have just completed a project that confirms all of these assumptions. It all started with a successful implementation of a web app that allows its users to upload short videos to the system. Traffic to the application increased exponentially. So did the cost of cloud infrastructure – to around $30,000 a month! We looked into that and realized that the architecture consists of 25 servers running over 1,000 WordPress websites!
The cloud can certainly help you reduce costs. But this is true only as long as you really take care of the architectural efficiency of your system.
We’re going to show you just how much you can achieve with expert cloud cost optimization. But first, let’s take a closer look at the client.
About the client
It’s a United Kingdom-based business that works with students and universities to turn graduation ceremonies into one-of-a-kind experiences for all alumni. The outbreak of COVID caused the company to accelerate a project of moving some of its business to the internet.
Timely and quality development? TSH comes in!
The Software House helped make the conversion possible, delivering a new app at a record pace, ahead of a new graduation ceremony season. The app:
- used React for the frontend and Node.js for the backend,
- opened the door for many new clients to come in, including +800 universities and thousands of students,
- featured stress tests from our QA specialists to make sure it withstands all the new traffic.
Both the client and our team were satisfied with the speed of development and the quality of deliverables.
It was a success. But the cold hard truth is that an application needs to be ready for both failure and success. And this one wasn’t quite there yet.
The client run into infrastructure cost troubles that were not caused by their cloud services or cloud providers themselves, but rather by inefficient use of cloud storage. It turned out that the increase in traffic affected parts of the system that were not created by us. These parts included the previously mentioned multiple servers that powered over a thousand WordPress websites, each of which required a separate database. The increased traffic directed to these sites caused the monthly cloud bill to skyrocket. If nothing was done about it, the cloud storage costs would just keep on growing.
Cloud optimization challenges
First, we needed to understand:
- why our client needed all this,
- how it is used by users,
- and how it is connected to the rest of the system.
The whole system itself was quite huge at this point. It consisted of 4 different frontend apps and one monolithic core.
Why did it end up being so unscalable?
The WordPress sites were introduced as a simple way of generating a new page for each new incoming client. The system worked reasonably well when it only served a small group of clients. But the customer base started growing very fast at the beginning of the COVID-19 pandemic when the product became fully virtual. As evidenced by the chart below, from May 1st, 2020 till March 28th, 2021, the app was visited by 1,300,000 unique users, who generated 3,100,000 views.
Before we started implementing any changes, the simplified structure of the system looked like that:
There were a lot of services (the WordPress instances) and some additional panels running on the old application. That’s what we had to work with. The plan was to transform it into something like this:
All WordPress instances should be replaced with one instance of a Next.js application. We couldn’t do it all at once. We needed a transitional step, which was to involve something like this:
Why was this transitional step necessary?
The transition – coexistence of two systems
One of the challenges we faced was to run two systems concurrently. As is often the case when you take over an existing project, we couldn’t just turn off the old one and start using the new one. The migration process involved business activities so we had to maintain both systems.
Our friends at TSH have vouched for the power and versatility of Next.js multiple times before. We agree with these assessments, but since we wanted to use Next.js in an unusual way, we still run into some troubles.
We needed a mechanism for rendering a page with the same URL’s path structure (which means that it is exactly the same page for the application) but fed with different data, depending on the domain. And that’s quite an ambitious goal because Next.js was not designed to be used like that. A typical Next.js app is a Single Page Application so it is visible under just one domain rather than under multiple completely different ones.
Next.js app implementation
As you can see, in order to succeed, we needed to modify the standard behavior of Next.js quite a bit. First, we tackled the transitional phase.
Incorporating a new system into the existing one
As mentioned earlier, we built a new system in addition to the existing one. The new features we introduced did not match the original features one-to-one. We strived to map the models and relations of the original system to the new one. We used this mapping as a basis for developing new features. This approach allowed us to continue working with the existing system quite smoothly.
Building a Next.js application
The new application was rather simple. It consisted of a home page and a few additional pages. From the developer’s perspective, the Next.js app was a SPA. However, in reality, it was just the frontend component of a sophisticated mechanism hidden underneath. The frontend communicated with the core of the old system through rendered views.
Here is a general overview of the new home page:
We used getServerSideProps to prepare data for a page meant to be pre-rendered by the Next.js engine. The function works like this:
Houston! We have a problem!
So far so good. We created a SPA and an API to go with it. We connected them and they work well together. Thanks to Server-Side Rendering, the performance is more than satisfactory. But the job is far from over. Why? In order to understand this, we need to take a step back and analyze the way Next.js works from the perspective of the goals of our project.
SSR and caching in Next.js
To put it simply, Next.js-powered Server-Side Rendering takes some data from a URL path as well as some query string parameters and then applies it all to the React component. The result of that action is an HTML page stored in the cache. The next time a user visits the same page, the server will not need to render it for them again. Instead, they will be served the cached version.
Everything seems fine so far. But here’s the catch – using Next.js you can find the URL path quite easily but it is not going to include the domain! It means that from the Next.js app’s point of view, there is no difference between calling foo.example.com and bar.example.com. As far as the app is concerned, it is the exact same home page!
One instance – many domains – one solution
Our idea was to make our app visible under the *.example.com address and have it handle all of the subdomains. To make it happen, we needed to find a way to somehow force the Next.js framework to take into consideration the full address of the domain attached to the application.
In our pursuit of a way to overwrite the normal behavior of Next.js, we decided to dive deeper under its hood. When we did that, we realized Next.js was using Express.js internally for all kinds of processes. That was it! Express.js definitely had a method to access the domain name! Still, we wanted to implement the change in a way that does not interfere with the framework or app too much. That’s because the more you interfere, the more likely you are to cause unexpected behaviors within the application.
We started our implementation from a very simple Express.js app (the code below is even further simplified to focus purely on the main issue at hand).
As you can see above, we encapsulated the Next.js application inside the server and used it in expressHandler. Here is a simplified version of the handler:
The application needs to be able to check if a given page should be cached. If it should, we call renderAndCache, which in turn calls nextJsApp.renderToHTML under the hood calls if necessary. If it should not, we simply call nextJsHandler.
To put the whole process in a few words:
- We replaced multiple WordPress instances with one Next.js application.
- The new app handled all the traffic previously received by the WordPress pages.
- We wrapped the Next.js app with customized Express.js code to pass domain names as parameters.
- In doing so, we lost the out-of-the-box Next.js caching mechanism, so we built a new one.
Are you in need of a cloud development team capable of implementing such optimization projects?
Work with one of the biggest cloud and DevOps teams in Poland.
Deliverables – costs down to 2k a month & scalability
Are you looking for a simple list of deliverables we produced during the project? Don’t worry, there will be one!
But what’s really the most important thing here is the fact that we delivered on our two main promises: cost optimization and scalability.
As far as cost optimization is concerned, our project exceeded all expectations. The goal was to simply reduce costs – there was no fixed number we were supposed to achieve. And yet, we managed to reduce the cost 15-fold, down to a mere $2,000 a month.
In the field of scalability, we delivered plenty as well. Not only is it much easier now to build new features due to the unification of all the content under a single application, but the deployment of the features to the clients is going to be way more straightforward.
Oh, and the list of deliverables? Here it is:
- A brand new application powered by Next.js to replace over 1,000 WordPress sites.
- A redesign of the app for all clients – since the new app replaced all the numerous WordPress services simultaneously.
- A whole new process for the implementation of new features, based on Next.js and Express.js.
- A new client panel for all of the customers.
What does it all mean for business?
Business implications – no more punishment for growth
We really love projects in which it is easy to demonstrate how the developer’s blood, sweat, and tears translated into tangible, countable benefits for the client.
The main benefits are divided into two areas:
- The decrease in the time required to create new clients sites. Before, it required a manual setup of a new page complete with a database. In the new Next.js-powered up, all it takes is to fill out a single form. It is a big thing in terms of efficiency and usability for both clients and admins.
- The reduction of cloud infrastructure costs. Before, acquiring each new client was causing the cloud bill to become bigger and bigger. As a result of replacing the inefficient WordPress sites with a Next.js app, the number of servers dropped from 25 to just 1 Azure App Service.
The client no longer worries that each new customer and feature causes the whole application to become even more costly and unmanageable. They can focus on growing their business instead. The software they own has suddenly become their ally in that pursuit. And that’s how it should be.
How to reduce cloud costs – conclusions
So what do you think about our project? Does it feel like it is something that you need or might need in the future? Do you need to optimize cloud costs too? Given the current trends in the usage of cloud computing, chances are that the answer is: “yes”. One way or the other, you need to know your options and what you’re capable of. That’s why we want to reassure you that:
Yes, you can:
greatly decrease your current cloud infrastructure bill and save money through skilled cloud cost management,
replace outdated and efficient architecture with a new one and keep the application running at all times,
replace multiple inefficient servers with just one,
simplify the management of your architecture,
- future-proof your application so that further development of new features and apps does not significantly increase your cloud bills.
Of course, ideally, you would want to do all that before your application is released.
That’s why regardless of whether you’re already in the business or about to enter it ...
… make sure to consult your architectural choices and cloud cost optimization practices with seasoned professionals. Skilled management of cloud resources leads to cost savings both in the short and long term!