When we observe a slow system, our first instinct might be to label it as failing. This presumption is widespread and highlights a fundamental truth: performance is synonymous with an application's maturity and readiness for production.
In web applications, where milliseconds can determine the success or failure of a user interaction, the stakes are incredibly high. Performance is not just a technical benchmark, but a cornerstone of user satisfaction and operational efficiency.
Performance encapsulates a system's responsiveness under varying workloads, quantified by metrics such as CPU and memory utilization, response times, scalability, and throughput.
In this article, we will explore how AppSignal can monitor and enhance the performance of Django applications.
Let's get started!
Django Performance Monitoring Essentials
Achieving optimal performance in Django applications involves a multifaceted approach. This means developing applications that run efficiently and maintain their efficiency as they scale. Key metrics are crucial in this process, providing tangible data to guide our optimization efforts. Let's explore some of these metrics.
Key Metrics for Performance Monitoring
- Response Time: This is perhaps the most direct indicator of user experience. It measures the time taken for a user's request to be processed and the response sent back. In a Django application, factors like database queries, view processing, and middleware operations can influence response times.
- Throughput: Throughput refers to the number of requests your application can handle within a given timeframe.
- Error Rate: The frequency of errors (4xx and 5xx HTTP responses) can indicate code, database query, or server configuration issues. By monitoring error rates, you can quickly identify and fix problems that might otherwise degrade user experience.
- Database Performance Metrics: These include the number of queries per request, query execution time, and the efficiency of database connections.
- Handling Concurrent Users: When multiple users access your Django application simultaneously, it is critical to be able to serve all of them efficiently without delays.
What We Will Build
In this article, we'll construct a Django-based e-commerce store ready for high-traffic events, integrating AppSignal to monitor, optimize, and ensure it scales seamlessly under load. We'll also demonstrate how to enhance an existing application with AppSignal for improved performance (in this case, the Open edX learning management system).
Project Setup
Prerequisites
To follow along, you'll need:
- Python 3.12.2
- An AppSignal-supported operating system
- An AppSignal account
- Basic Django knowledge
Prepare the Project
Now let's create a directory for our project and clone it from GitHub. We'll install all the requirements and run a migration:
Now visit 127.0.0.1:8000. You should see something like this:
This Django application is a simple e-commerce store that provides product lists, details, and a checkout page for users. After successfully cloning and installing the app, use the Django
createsuperuser
management command to create a superuser.
Now let's create a couple of products in our application. First, we'll shell into our Django application by running:
Create 3 categories and 3 products:
Now close the shell and run the server. You should see something like the following:
Install AppSignal
We'll install AppSignal and opentelemetry-instrumentation-django in our project.
Before installing these packages, log in to AppSignal using your credentials (you can sign up for a free 30-day trial). After selecting an organization, click on Add app at the top right of the navigation bar. Choose Python as your language and you'll receive a push-api-key.
Make sure your virtual environment is activated and run the following commands:
Provide the app name to the CLI prompt. After installation, you should see a new file called __appsignal__.py
in your project.
Now let's create a new file called .env
in the project root and add APPSIGNAL_PUSH_API_KEY=YOUR-KEY
(remember to change the value to your actual key). Then, let's change the content of the __appsignal__.py
file to the following:
Next, update the manage.py
file to read like this:
We've imported AppSignal and started it using the configuration from __appsignal.py
.
Please note that the changes we made to
manage.py
are for a development environment. In production, we should changewsgi.py
orasgi.py
. For more information, visit AppSignal's Django documentation.
Project Scenario: Optimizing for a New Year Sale and Monitoring Concurrent Users
As we approach the New Year sales on our Django-based e-commerce platform, we recall last year's challenges: increased traffic led to slow load times and even some downtime. This year, we aim to avoid these issues by thoroughly testing and optimizing our site beforehand. We'll use Locust to simulate user traffic and AppSignal to monitor our application's performance.
Creating a Locust Test for Simulated Traffic
First, we'll create a locustfile.py
file that simulates simultaneous users navigating through critical parts of our site: the homepage, a product detail page, and the checkout page. This simulation helps us understand how our site performs under pressure.
Create the locustfile.py
in the project root:
In locustfile.py
, users primarily visit the product detail page, followed by the checkout page, and occasionally return to the homepage. This pattern aims to mimic realistic user behavior during a sale.
Before running Locust, ensure you have a product with the smartphone slug in the Django app. If you don't, go to /admin/store/product/ and create one.
Defining Acceptable Response Times
Before we start, let's define what we consider an acceptable response time. For a smooth user experience, we aim for:
- Homepage and product detail pages: under 1 second.
- Checkout page: under 1.5 seconds (due to typically higher complexity).
These targets ensure users experience minimal delay, keeping their engagement high.
Conducting the Test and Monitoring Results
With our Locust test ready, we run it to simulate the 500 users and observe the results in real time. Here's how:
- Start the Locust test by running
locust -f locustfile.py
in your terminal, then open http://localhost:8089 to set up and start the simulation. Set the Number of Users to 500 and set the host to http://127.0.0.1:8000 - Monitor performance in both Locust's web interface and AppSignal. Locust shows us request rates and response times, while AppSignal provides deeper insights into our Django app's behavior under load.
After running Locust, you can find information about the load test in its dashboard:
Now, go to your application page in AppSignal. Under the Performance section, click on Actions and you should see something like this:
- Mean: This is the average response time for all the requests made to a particular endpoint. It provides a general idea of how long it takes for the server to respond. In our context, any mean response time greater than 1 second could be considered a red flag, indicating that our application's performance might not meet user expectations for speed.
- 90th Percentile: This is the response time at the 90th percentile. For example, for
GET store/
, we have 7 ms, which means 90% of requests are completed in 7 ms or less. - Throughput: The number of requests handled per second.
Now let's click on the Graphs under Performance:
We need to prepare our site for the New Year sales, as response times might exceed our targets. Here's a simplified plan:
- Database Queries: Slow queries often cause performance issues.
- Static Assets: Ensure static assets are properly cached. Use a CDN for better delivery speeds.
- Application Resources: Sometimes, the solution is as straightforward as adding more RAM and CPUs.
Database Operations
Understanding Database Performance Impact
When it comes to web applications, one of the most common sources of slow performance is database queries. Every time a user performs an action that requires data retrieval or manipulation, a query is made to the database.
If these queries are not well-optimized, they can take a considerable amount of time to execute, leading to a sluggish user experience. That's why it's crucial to monitor and optimize our queries, ensuring they're efficient and don't become the bottleneck in our application's performance.
Instrumentation and Spans
Before diving into the implementation, let's clarify two key concepts in performance monitoring:
- Instrumentation
- Spans
Instrumentation is the process of augmenting code to measure its performance and behavior during execution. Think of it like fitting your car with a dashboard that tells you not just the speed, but also the engine's performance, fuel efficiency, and other diagnostics while you drive.
Spans, on the other hand, are the specific segments of time measured by instrumentation. In our car analogy, a span would be the time taken for a specific part of your journey, like from your home to the highway. In the context of web applications, a span could represent the time taken to execute a database query, process a request, or complete any other discrete operation.
Instrumentation helps us create a series of spans that together form a detailed timeline of how a request is handled. This timeline is invaluable for pinpointing where delays occur and understanding the overall flow of a request through our system.
Implementing Instrumentation in Our Code
With our PurchaseProductView, we're particularly interested in the database interactions that create customer records and process purchases. By adding instrumentation to this view, we'll be able to measure these interactions and get actionable data on their performance.
Here's how we integrate AppSignal's custom instrumentation into our Django view:
See the full code of the view after the modification.
In this updated view, custom instrumentation is added to measure the performance of database queries when retrieving or creating a customer and creating a purchase record.
Now, after purchasing a product in the Slow events section of the Performance dashboard, you should see the purchase
event, its performance, and how long it takes to run the query.
purchase
is the event we added to our view.
Using AppSignal with an Existing Django App
In this section, we are going to see how we can integrate AppSignal with Open edX, an open-source learning management system based on Python and Django.
Monitoring the performance of learning platforms like Open edX is highly important, since a slow experience directly impacts students' engagement with learning materials and can have a negative impact (for example, a high number of users might decide not to continue with a course).
Integrate AppSignal
Here, we can follow similar steps as the Project Setup section. However, for Open edX, we will follow Production Setup and initiate AppSignal in wsgi.py
. Check out this commit to install and integrate AppSignal with Open edX.
Monitor Open edX Performance
Now we'll interact with our platform and see the performance result in the dashboard.
Let's register a user, log in, enroll them in multiple courses, and interact with the course content.
Actions
Going to Actions, let's order the actions based on their mean time and find slow events:
As we can see, for 3 events (out of the 34 events we tested) the response time is higher than 1 second.
Host Metrics
Host Metrics in AppSignal show resource usage:
Our system isn't under heavy load — the load average is 0.03 — but memory usage is high.
We can also add a trigger to get a notification when resource usage reaches a specific condition. For example, we can set a trigger for when memory usage gets higher than 80% to get notified and prevent outages.
When we hit the condition, you should get a notification like the following:
Celery Tasks Monitoring
In Open edX, we use Celery for async and long-running tasks like certificate generation, grading, and bulk email functionality. Depending on the task and the number of users, some of these tasks can run for a long time and cause performance issues in our platform.
For example, if thousands of users are enrolled in a course and we need to rescore them, this task can take a while. We might get complaints from users that their grade is not reflected in the dashboard because the task is still running. Having information on Celery tasks, their runtime, and their resource usage gives us important insights and possible points of improvement for our application.
Let's use AppSignal to trace our Celery tasks in Open edX and see the result in the dashboard. First, make sure the necessary requirements are installed. Next, let's set our tasks to trace Celery performance like in this commit.
Now, let's run a couple of tasks in the Open edX dashboard to reset attempts and rescore learners' submissions:
We'll go to the Performance dashboard in AppSignal -> Slow events and we will see something like:
By clicking on Celery, we'll see all the tasks that have run on Open edX:
This is great information that helps us see if our tasks are running longer than expected, so we can address any possible performance bottlenecks.
And that's it!
Wrapping Up
In this article, we saw how AppSignal can give us insights into our Django app's performance.
We monitored a simple e-commerce Django application, including metrics like concurrent users, database queries, and response time.
As a case study, we integrated AppSignal with Open edX, revealing how performance monitoring can be instrumental in enhancing the user experience, particularly for students using the platform.
Happy coding!
P.S. If you'd like to read Python posts as soon as they get off the press, subscribe to our Python Wizardry newsletter and never miss a single post!