In this post, you will learn how to add tracing to a Node.js application on AppSignal. You will use an existing Quotes app that talks to a PostgreSQL database to fetch the quotes.
Let’s get going!
Why You Need Tracing and an APM Tool: A Case Study
Let’s start with a story. You are pinged by your manager who tells you that the app you deployed yesterday has been reported as slow by users. Your manager claims that the complaints started yesterday at 2 pm, and you released your change at 1:55 pm. Without an Application Performance Monitoring (APM) tool or data trace, you are just grappling to understand what really happened.
Your tests on staging were fine, and you experienced no issues when developing with the small data set you had on your local machine. After almost two hours of digging around, you find out that due to the high volume of data in the production database, a couple of the queries you introduced yesterday caused the slowness. You push a fix, adding a couple of indexes to the problem tables, and the app is back to its normal response time again.
This is where the power of APM and tracing becomes critical, if not crucial to your applications (especially in a production environment). If you had an APM like AppSignal, you would know about the problem minutes after you deployed by checking your app's performance graphs.
If you had a trigger and alert set up for the throughput or response time, the issue would be caught minutes after deploying the new change.
Proper tracing can pinpoint the line of code and query that introduces a problem. In the next section, we'll set up tracing and APM for an example application.
Tracing and APM for a Node.js Application
You will set up tracing for an example Quotes app built with Node.js using Express. It fetches data from PostgreSQL hosted on ElephantSQL. The application, built with Node.js and Postgres, is open source and the code is available on GitHub.
The app is hosted on Render.com for free, and the database is also on a free plan on ElephantSQL. You can see an example of the quotes returned by the API below:
To follow this tutorial, you can clone the GitHub repository and copy the code to your machine with:
Then run npm install
to install all the dependencies. It has been tested with Node.js v18, the latest LTS at the time of writing. After that, if you run npm start
and hit http://localhost:3000/quotes
on your browser of choice, you will see a similar output to the above.
Now, let's add the AppSignal library to this Quotes app.
Add AppSignal to the Node.js Project
To add AppSignal to any project, you first need to register for an account. AppSignal offers a 30-day free trial with no credit card information required, so you can sign up without any issues.
After you sign up, you can follow the official Node installation docs to set up AppSignal in your Node.js application.
Run the Node.js App with AppSignal Reporting
As the library has been added to the app, when you run the app, it will start reporting data to AppSignal. The npx
command creates an appsignal.cjs
file on the project's root that connects your app to AppSignal. To include this pre-run script, you need to change the app start script to node --require './appsignal.cjs' ./bin/www
in the package.json file’s start
script.
Now if you run your Quotes app again with npm start
and hit it with some requests, it will show up in the “development” environment of the project, which is taken from the NODE_ENV variable. It will look something similar to the below after a couple of requests:
Hooray! You have successfully added the AppSignal library to your app, which communicates data and metrics to AppSignal from your local machine.
In the next section, we will send some requests to the app.
Sending Requests to the Node.js App
One of the easiest ways to send lots of fabricated requests at the same time is to use the Vegeta load testing tool. Being a load testing tool, it can send lots of requests consistently, every second, to the given target URL. You can read more about Vegeta on GitHub. The binary can be downloaded and used without installation.
After you have the executable binary for Vegeta, run the following command to send 3 requests per second for 1 minute (60 seconds) to your local web server.
It will show an output like the below after running for 60 seconds:
The report says that a total of 180 (3*60) requests were sent, and all came back with a status code of 200 OK. The fastest request took 184.803 ms, and the slowest one was 2.95 seconds. There are other interesting stats here too.
Multiple factors can slow down your application's responsiveness. Luckily, most of them are under your control and you can work on them to make your app faster. Some of the factors that you can influence to speed up your application include:
- Balancing the resources allocated to the application
- Code level optimization
- Adding database indexes
- Improving the SQL queries
Depending on the architecture and infrastructure of the project, you can also look into horizontal scaling and caching.
How you send the queries to the database can be an issue too. AppSignal gives you deeper insights on queries and the time they take to execute. We'll cover how to speed up SQL queries later in this post.
Analyzing Stats and Graphs
Now if you check your app's development environment in AppSignal after a minute, you can see the load handled by the app in metrics and graphs. You can check for memory usage, response times, throughput, and errors.
Checking Memory Usage
On the Node.js Heap Statistics dashboard, you can see that the memory consumption of this app while serving 3 requests per second was around 40 MB.
Checking memory usage is very useful to know how much memory your application consumes. Suppose your application gets a lot of traffic at the same time, and only 128 MB of memory is allocated to the application. The container/pod might be killed due to an Out Of Memory (OOM) issue.
Similarly, on the flip side, maybe your application is allocated 512 MB of memory, and, for weeks, it has only consumed 100 MB at its peak. Then it is time to allocate a lower amount of memory. This also saves costs, as 80% of the allocated memory was not used and will not be used for the expected traffic. The same logic can be applied to other resources allocated to the application, like the CPU.
Visualizing Response Times
You can also check the app's response times, throughput, and other stats while it was serving 3 requests per second (RPS) in 1 minute at Performance > Graphs. It looks like the following for the above Vegeta load test.
So, at around 180 requests per minute (the load sent using Vegeta), it responded at ~760 ms on the 95th percentile.
This means the slowest 5% of the requests responded to the client in 760 ms. Response times are very important for APIs and web applications. More than a decade ago, Amazon found every 100ms of latency could cost them $1.6 billion in sales. So slow websites and APIs are not great for your business.
Keep a tab on response times and do what's needed to optimize them. Response times can be decreased by either consuming fewer resources, like CPU and memory, or adding more resources to your servers/containers.
Response times can also be brought down by speeding up slow queries, and we'll discuss this next.
Know Your Slow Queries
You can pinpoint the slow queries in your application under Performance > Slow queries. For example, see the slow SQL query below.
If you know about slow queries, then you can fix them in multiple ways. You can use an EXPLAIN
prefix to find out why SELECT
queries are slow. It returns the execution plan generated by the query planner.
To oversimplify EXPLAIN
, it does a full table scan to tell you if a query includes more rows to get results than needed (i.e., the query could have got the same rows by scanning fewer data). You can add indexes to relevant column(s) to make the queries faster. Indexes make reads fast, but they also make writes slow. So make an informed decision as to whether you should add indexes.
Another way to speed up slow SQL is by rewriting and refactoring a query. For instance, you may remove a join that isn't needed or convert a subquery to a join on a case-by-case basis.
Let's now turn our attention to tracking errors in AppSignal.
Tracking Errors
You can go to Error > Graphs in AppSignal to see your application's error rate. Depending on the errors or even response times, you can add triggers at Anomaly Detection > Triggers. Then set alerts on those errors.
For example, if your throughput is less than 100 per minute, you can trigger an email alert. If the error rate is more than 2%, you can trigger another alert, and so on.
Below is a code snippet from the quotes file in the example app, where an error has been deliberately introduced.
The quotes service does not have the getError
method. This will always go into the catch section. As the appSignal.setError
method is called with the error, it is sent to AppSignal. After the URL is hit, after some time the error will show up on the AppSignal interface under Errors > Issue list, as follows:
Then, you can click on the particular error in the GET /quotes/error-fix
path. You will see the error summary and can click on the 'Samples' tab to see something like the below:
If you select one of the samples, you can see more details about that error sample:
Then if you scroll down to the Backtrace
section and click the green button saying Show full backtrace
, you can view exactly where the error came from (and what files and functions led to that function call), as follows:
Given this was a fabricated error, it will be fixed as soon as you call an existing method on the Quotes
service. You can also view error graphs for different time ranges on AppSignal like the below:
Now, imagine debugging a pesky bug or a performance issue and having all this data, metrics, and stats at your disposal. It's like having a powerful torch in a dark room — you can find issues much more quickly and are one step closer to solving them.
Other Useful Features in AppSignal
Play around with the links on the left sidebar in AppSignal. You can add an uptime monitor. If your website or web application is down for some reason, you will be notified. Your app is pinged every minute from 4 different locations across the globe.
There's the Host metrics section too, which includes CPU Usage, Load Average, Memory Usage, and other host stats.
Wrapping Up
In this post, you first learned why tracing and Application Performance Monitoring (APM) is important. After that, we introduced an example app that serves Quotes via a JSON API. Then we added AppSignal APM and tracing to that example app.
We explored some of AppSignal's useful features for your Node.js app, including performance graphs, slow queries, errors, and response times.
Happy tracing!
P.S. If you liked this post, subscribe to our JavaScript Sorcery list for a monthly deep dive into more magical JavaScript tips and tricks.
P.P.S. If you need an APM for your Node.js app, go and check out the AppSignal APM for Node.js.