FastAPI is a Python 3.7+ web framework that allows developers to quickly spin up REST services. It’s very performant, easy to use, and comes with auto-generated, interactive documentation that can be used to test the various API endpoints. 

Developers often choose to use FastAPI because of its perks. But just like with any framework, your FastAPI app can experience slowdowns that can frustrate the customers who use it. Thanks to observability tools like New Relic, you can tap into the platform to gain more insight into the performance issues that you’re presented with, and resolve them accordingly. 

In this blog post, you’ll learn what slows down FastAPI apps. Then, you’ll work with an example FastAPI app to learn how to use New Relic to pinpoint the root cause of an issue, and how to apply the observability insight to increase the performance of the application. At the end of the blog, you'll learn how to use a quickstart to install the New Relic Python agent in your own FastAPI app.

Causes of slow downs in FastAPI apps

Although FastAPI is designed to be fast, there are a few scenarios that can lead to slowdowns in your applications:

Heavy request payloads

If your application has a hefty amount of data that needs to be processed at a time, the app can move slower and eventually time out. To mitigate this, you can incorporate asynchronous function calls in your application.

Blocking input/output (I/O) bound operations

If your application performs too many blocking operations, such as receiving multiple requests at a time, there’s the possibility that the application will be stalled as it waits for previous requests to complete before moving forward. You can resolve this by using non-blocking I/O operations and asynchronous processing in your application, where relevant.

Insufficient hardware resources 

If the hardware you’re using has low disk space, the application will slow down as it spends time searching for free space to execute.

In addition, having an outdated operating system can cause a timeout error because the application may be too powerful for the OS version to handle, in order to be fully processed.

How to use New Relic to solve a performance issue in a FastAPI app

To better understand and pinpoint the root causes of performance issues present in your FastAPI app, you can turn to the New Relic observability platform. With New Relic you can explore metrics, events, logs and traces more deeply, using a comprehensive application performance monitoring (APM) dashboard. 

New Relic supports a ton of technology integrations with quickstart guides, including a FastAPI quickstart. This quickstart walks you through the process of instrumenting a FastAPI application with the New Relic Python agent. The quickstart also configures out-of-the-box observability tools—a dashboard and alerts—which help to provide insight into what’s happening with your  app internally.

Let’s dive into an example of how New Relic can help solve a slow down in a FastAPI application. In this example, you’ll solve a performance issue in a steganography application that encrypts images with coded messages. The application has several functions involved in the encryption process that complete these tasks:

  • Encode a plain text string into a bytestring. 
  • Extract pixels from an image.
  • Combine the image pixels with the generated bytestring, to encrypt the message into the image.
  • Decode the message from the image.

In this example, you’ll be troubleshooting the endpoint /retrieveImagePixels that returns the extracted pixels from an image that’s provided in the program. Take a look at the provided image and a code snippet of the original pixel extraction process:

The image that's used in the steganography app.
@program.get("/retrieveImagePixels")
def retrieve_pixels_from_image():
  pic = Image.open("Vibin.png")
  pixels = list(pic.getdata())
  return pixels #returns the pixels in a list of tuples

Now let’s take a look at how the application responds when attempting to retrieve the image’s pixels. To query the /retrieveImagePixels endpoint, you’ll use the auto-generated FastAPI documentation in the browser.

Initially, you can see a Page Unresponsive error is thrown at the top of the browser window in a popup message. Select the wait button to see what happens next. As time progresses, the /retrieveImagePixels  eventually times out before a response with the extracted pixels is successfully retrieved. Notice the message in the next image of FastAPI docs: Could not render xt, see the console.

Could the root cause of these errors be the result of a slow down as mentioned earlier? Let’s use New Relic to find out.

Step 1:  Log into the New Relic Platform and find the steganography API under APM & Services.

When you instrument a FastAPI app with the New Relic agent, you can view it on the APM & Services page. In this example, the steganography app is called steganography-api in New Relic. The next image shows the summary view of the steganography-api.

Step 2: Find the /retrieveImagePixels endpoint call that’s populated in the Transactions table.

The Transactions table gives you more insight into the exact function calls that are made as you interact with your FastAPI app, along with the error rate and average duration of time that it takes for each endpoint to completely execute. 

In the previous image of the steganography-api service, notice that the retrieve_pixels_from_image function took about 599 milliseconds to execute before timing out. To gain even more insight into what’s happening in each function, you can select the name of the function in the Transactions table and you’ll see a more detailed breakdown of what is happening in the function call. 

Step 3: Select the retrieve_pixels_from_image function and turn your attention to the Breakdown table.

The Breakdown table section shows a starlette.middleware.errors:ServerErrorMiddleware.__call__ error at the top of the stack, which takes up 95% of the execution time of the function call, clocking at 570ms . After doing some research, a couple of helpful explanations can be found on Stack Overflow as well as on the New Relic forum. Both resources explain that synchronous code can cause blocking I/O operations, which, among other issues, can result in a slowdown.  

Because the code for the retrieve_pixels_from_image function is not asynchronous, let’s see if making it asynchronous will increase the performance of the  function and resolve the slow down. With a little help from ChatGPT, here’s an asynchronous implementation:

#Asynchronous Solution
@program.get("/retrieveImagePixels-async")
async def retrieve_pixels_from_image_async():
    image ="Vibin.png"
    loop = asyncio.get_running_loop()
    image = await loop.run_in_executor(None, Image.open, image)
    pixels = await loop.run_in_executor(None, list, image.getdata())
    return pixels

Let’s test out the new asynchronous code in the FastAPI documentation. Unfortunately, even with the asynchronous design, the endpoint isn’t able to render the data as requested. Notice the failure message in the next image, Could not render xt, see the console.

Going back to New Relic, let’s look at the Transactions table for the steganography-api, as shown in the next image. You can see that although the requested response was not able to be rendered on the frontend, on the backend, the asynchronous solution actually performed a bit slower at 1.74s (1740 ms) than the original synchronous implementation did, which executed at 1.71s (1710 ms) the second time it was called. This timing does not support the claim that asynchronous design increases the performance of a FastAPI application.

Since the asynchronous implementation didn’t resolve the performance issue with the pixel extraction request, let’s try another solution to get to that end result. One of the causes of slow downs in FastAPI applications is when a request has a heavy payload. So let’s try recreating the retrieve_pixels_from_image function to decrease the size of the image inside of it and see if that makes a difference in the pixel extraction process. Notice that the endpoint is now called /retrieveResizedImagePixels and the function is called retrieve_pixels_from_resized_image:

@program.get("/retrieveResizedImagePixels")
def retrieve_pixels_from_resized_image():
    image = Image.open("Vibin.png")
    new_size = (100,100)
    resized_image = image.resize(new_size)
    resized_image.save("Vibin-resized-img.png")
    pixels = list(resized_image.getdata())
    return pixels #returns the pixels in a list of tuples

After resizing the image, test out the /retrieveResizedImagePixels endpoint again using the FastAPI documentation. Check out the next image—the pixels are successfully extracted!

You can see just how long retrieving the image pixels took in New Relic. Revisit the summary page for your FastAPI service, and review the Transactions table. 

This time around, the average duration of the retrieve_pixels_from_resized_image function is both significantly faster at 256 milliseconds and returns the anticipated response, so the image resize solution is both successful and performant.

What you've learned so far

Let’s recap this example. You were tasked with troubleshooting a synchronous function call that caused a significant slow down in a  steganography app, leading to a timeout after 549 milliseconds. You used New Relic to look into the retrieve_pixels_from_image function and confirm both the name of the error and how long the app spent on that error.

Research suggested that the performance issue in the code could be caused by a blocking I/O operation caused by synchronous code in the retrieve_pixels_from_image function.  After making this function asynchronous, you tested the broken /retrieveImagePixels endpoint again. You used New Relic to confirm that asynchronous code actually increased the execution time of that function call to 1.05 seconds (1000 milliseconds)!  This was not the expected result, especially given that the request still failed. 

Finally, you tried resizing the image before extracting the pixels. You tested this out in the steganography app’s online documentation and the solution worked. You also confirmed in New Relic that resizing the image decreased the operating time to 84.9 milliseconds, which ultimately increased the app’s performance. Do you think this is the result of a blocking I/O operation? Can you think of a more robust solution? If so, feel free to fork the steganography repository and try it out!

How to install New Relic with the FastAPI quickstart

In this section you'll learn how to install the New Relic Python agent into your FastAPI app using a quickstart. A quickstart is a guided installation process that also provides pre-built tools, including a dashboard and four alerts. 

Step 1: Sign up for New Relic and navigate to the quickstart

Here's what you'll do first:

  1. Sign up for a free New Relic account, or log in to your existing account.
  2. Visit the FastAPI quickstart and click the Install Now button at the top of the page. This will open New Relic in a new tab to the quickstart guide.

Step 2: Complete the quickstart within New Relic

You can follow along with the next video that shows how to use the quickstart in New Relic, or keep reading to see a written breakdown of each step in the video. Take note that the video has no sound.

1. Confirm you want to install the New Relic Python agent.

Within New Relic and the quickstart guide, select Begin installation to proceed to the next step.

2. The next page asks you to install the New Relic Python agent. Select the Begin installation button to proceed to the next step.

3. Within the next page, name your application and then select Save.

4. On the next page, follow the instructions to install the New Relic APM agent and download the custom configuration file.

Then follow the instructions to launch your application’s main module with the New Relic agent.

Select Continue at the bottom of the page when you're done.

5. The next page prompts you to connect your logs and infrastructure. Follow the instructions, and then select Continue.

6. After you’ve connected your logs and infrastructure, you can deploy the resources that come with the quickstart. Select See your data to get a view of the basic dashboard for your FastAPI application.