NodeJS development is extremely fun as a hobby, but when it’s time to take your app and serve it to users in a production environment there are few things you must know in order to avoid horrible performance and keep your app from crashing.
As part of our work at MyHeritage, we developed a doppelgänger app for the 2019 Eurovision Song Contest where you can take a selfie and find out which Eurovision star you most resemble.
Beyond the facial recognition logic, our app had a very clear requirement: it needed to serve tens of thousands of concurrent users in production, because the Eurovision is watched by millions of people around the world.
We realised very quickly that having a load balancer in front of our app, configured with an auto-scaling group, is not enough to cover for bad performance. We found the following tips very useful:
- Hope for the best, expect the worst: measure how many concurrent users can be served by your app in X amount of time (in a single instance). For example, in our case the test determined we can serve 200 concurrent users in every EC2 instance within 10 seconds, so when we knew we had to serve 10,000 concurrent users we simply had to prepare 50 servers behind our load balancer. We used a great tool called JMeter to perform that test.
We found this tutorial useful for performing load testing using JMeter.
- Avoid blocking: blocking actions (such as fs.readSync) are tempting because they look cleaner in your code but they’re literally performance killers. Use async/await instead because while the async action is being processed the CPU is available to handle other tasks (Event Loop).
const res = fs.readSync('file.txt');After:
const res = await fs.readAsync('file.txt');
- Increase your memory limit: Node is configured by default to a memory limit of 1GB. Assuming your server has allocated 4GB just for your app you will have to manually set the max memory limit by utilising Node CLI and the following flag:
node --max-old-space-size=4096 server.js
- Make sure you use all of your CPU cores: running Node is single threaded by default. If you haven’t defined any special configuration that runs multiple threads of Node on the same server, save your money and pick a server with only 1 core.
- Save trips to your app: Use page rules such as forcing HTTPS or permanent redirects on the highest level you can (proxy for example). This will keep your app less busy and more available to receive new requests that actually need to be handled.
- Error handling: Use a logging tool such as Logz.io/AWS CloudWatch to track down errors that can easily fail your app. DO NOT report all of your errors to services like Slack because errors usually come in masses and they can easily block your app for throttling. We used a great library for NodeJS logging called Winston.
At MyHeritage, these tips resulted in a tenfold improvement in our app’s performance and helped us keep our production environment clean even when we had to serve thousands of concurrent users.
Thank you for reading 🙂