python

Monitoring Django Query Performance with AppSignal

Dejan Lukić

Dejan Lukić on

Monitoring Django Query Performance with AppSignal

Slow database queries are really a pain. It’s easy to blame Django for taking ages to process a request, but the real issue may lie in an SQL query not doing what it should be. These performance degrading queries are often hiding in plain sight. By the time you notice them, they are already affecting your end users.

AppSignal is an application performance monitoring (APM) tool with support for Python and Django out of the box. It can surface these exact issues with detailed insights for each individual request. In this short guide, you will learn how to quickly install it in your project. You will also find out about some common bad practices when querying in Django.

Let's get this thing going!

Prerequisites

Before you get started, you should have:

  • A Django project up and running
  • A seeded database of your choice (MySQL, PostgreSQL, SQLite3)
  • An application set up on AppSignal

Setting Up AppSignal in a Django Project

Let's start off by setting up AppSignal in your existing Django project. Navigate to the root of your project's directory and add the following packages inside the requirements.txt file:

  • appsignal: the AppSignal library
  • opentelemetry-instrumentation-django: automatically instruments Django's request/response cycle, capturing each request as a trace
  • opentelemetry-instrumentation-wsgi: instruments the WSGI layer so that spans are created at the server level before Django's middleware even runs
  • opentelemetry-instrumentation-asgi: the async equivalent of the above, required if you're running Django with an ASGI server like Uvicorn or Daphne
plaintext
# requirements.txt appsignal opentelemetry-instrumentation-django opentelemetry-instrumentation-wsgi opentelemetry-instrumentation-asgi

Based on the SQL database your application uses, you need to install Django ORM-specific instrumentation:

Note: The AppSignal for Python integration will automatically use this instrumentation when the corresponding package is installed. To disable this instrumentation without uninstalling the package, use the disable_default_instrumentations configuration option.

Next, install the packages and run the AppSignal's automated install tool, which will create __appsignal__.py for you in the project:

Shell
pip install -r requirements.txt python -m appsignal install

Note: If the pip and python executables do not work, please use pip3 and python3 instead.

The tool will prompt you for an app name (previously created on AppSignal), as well as the Push API key, which can be found in the Push & deploy section of your App settings.

Django requires a bit more configuration. Integrate the manage.py file, and import the appsignal module needs in the manage.py file. Then, inside the main method, call appsignal.start to initialize instrumentation configured in the __appsignal__.py (done automatically by the automated install tool).

Python
# manage.py #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" import os import sys # Add this import statement import appsignal def main(): """Run administrative tasks.""" os.environ.setdefault('DJANGO_SETTINGS_MODULE', '<YOUR_APP_NAME_HERE>.settings') # Add this method call appsignal.start() try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc execute_from_command_line(sys.argv) if __name__ == '__main__': main()

Make sure to put your actual application name instead of the <YOUR_APP_NAME_HERE>.

Note: See more instructions for production setups using WSGI or ASGI.

The __appsignal__.py contains sensitive credentials (in this case the Push API key). Please do not commit this key to version control. Move this to your app's security credentials or environment variables. See more details.

That's it for the AppSignal setup! Quick and easy, right? Your app is ready to be monitored.

Next, let’s see how AppSignal tracks and traces database queries.

How AppSignal Tracks Database Queries

Once the server runs, AppSignal wraps Django's database backend. Every SQL statement your ORM or raw connection.cursor() generates becomes a span: a timed unit of work attached to the request that triggered it.

AppSignal breaks each request down into two components you'll see throughout the dashboard:

  • sql — time spent waiting on the database
  • django — time spent in Python (view logic, serialization, middleware)

In a healthy request, those numbers are small and balanced. When something goes wrong, one of them dominates. Let's see what kind of issues you can catch.

Identifying Slow Queries in the AppSignal Dashboard

First, you need to see the issues that have happened. Navigate to Performance > Issue list in your AppSignal app.

Issue list shown in the AppSignal dashboard
Issue list in the AppSignal dashboard

Here, you have an overview of:

  • The originating request (Action name)
  • Its severity and status
  • Mean time-to-completion, throughput, and impact on performance

By clicking on the originating request, you will open a full breakdown with all the details.

Request summary in the Issue list
Request summary in the Issue list

The Samples table shows the latest fetches, their durations, N+1 query detection, and group breakdown.

Press on a specific timestamp next.

Request sample breakdown
Request sample breakdown

This section shows the most intriguing details. The Sample breakdown shows how much time has elapsed for each part of the request, for sql, and django, that is. This can help you pinpoint what you need to address. Is it the server or the query?

AppSignal also associates locally available tags to the request. A waterfall timeline is also shown so you can see what happens in your request chronologically. Plus, this shows what query was performed at each step. Neat!

Common Django Query Problems and How to Spot Them

Now that AppSignal is surfacing your query data, you need to know what you're looking for. Most Django performance problems fall into a handful of recurring patterns. Here are the most common ones and how the AppSignal dashboard helps you catch them.

The Good Old N+1 Queries

College peeps learn this early on. This is the most frequent database query problem that sneaks up on you more easily that you’d think. In fact, N+1 happens when you fetch a list of objects and execute an additional query for each one.

Python
# views.py def book_list(request): books = Book.objects.all() # 1 query for book in books: print(book.author.name) # 1 query per book → N+1 ...

The AppSignal Sample breakdown shows N+1 pattern as a long stack of near-identical sql calls in the waterfall timeline. The identical SELECT statement is repeated tens or even hundreds of times, each with a tiny duration that really adds up to a significant total.

AppSignal flags these directly in the Samples table with its built-in N+1 detection badge, so you do not have to spot the pattern yourself.

To fix the N+1 mishaps, you can almost always use select_related (for foreign keys and one-to-one relations) or prefetch_related (for many-to-many and reverse foreign keys):

Python
books = Book.objects.select_related("author").all()

Missing Database Indexes

Querying a small dataset usually does not cause issues. But, when data grows, a suboptimal query will definitely become a bottleneck. This happens when columns being filtered or ordered are not indexed. Django will complete the query without complaining, but database just has to scan the entire table to give a response.

Python
# Filtering on a column with no index becomes a full table scan orders = Order.objects.filter(status="pending").order_by("created_at")

The fix is to add an index on the relevant column. In Django, this is done directly in the model:

Python
class Order(models.Model): status = models.CharField(max_length=20, db_index=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: indexes = [ models.Index(fields=["status", "created_at"]) ]

If you frequently filter on multiple columns together, a composite index (as shown above) is more efficient than two separate single-column indexes.

Unbounded QuerySets

Fetching more rows than you actually need is another way to degrade performance. A view that calls Model.objects.all() without any limit will happily load every row in the table into memory. As the table grows, so does the response time and memory footprint.

Python
# Fine with 500 rows. A disaster with 500,000. def export_view(request): records = AuditLog.objects.all() ...

The fix depends on the use case. For paginated views, use Django's built-in Paginator or slice the QuerySet directly:

Python
recent_logs = AuditLog.objects.order_by("-created_at")[:100]

Repeated Identical Queries

Sometimes the same query is executed multiple times within a single request, not as part of an N+1 loop, but because different parts of the codebase independently ask for the same data. Django's ORM does not cache QuerySet results across calls, and each evaluation hits the database.

Next Steps and Resources

Complex SQL queries can give you headaches, but queries that you cannot measure or understand will give you migraines. Now, if we stick with the same metaphor, using a tool like AppSignal to monitor direct SQL queries and Django calls can be like popping some ibuprofen.

In this guide you’ve learned how to install AppSignal within your Django application, as well as what you can expect to get from AppSignal when performing the queries. From here, you may want to try setting up triggered alerts or uptime monitoring.

AppSignal is free to start with and can be integrated within minutes in your project. Start spotting those pesky queries in no time!

Wondering what you can do next?

Finished this article? Here are a few more things you can do:

  • Share this article on social media
Dejan Lukić

Dejan Lukić

Our guest author Dejan is an electronics and backend engineer, who is pursuing entrepreneurship with SaaS and service-based agencies and is passionate about content creation.

All articles by Dejan Lukić

Become our next author!

Find out more

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps