django-silk is a library that lets you profile your Django application’s performance, showing you exactly where time is being spent, down to the database queries and view functions.
Here’s a simple Django app and a urls.py to demonstrate:
# myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse
import time
def slow_view(request):
time.sleep(0.5) # Simulate some work
return HttpResponse("This view was slow!")
def fast_view(request):
return HttpResponse("This view was fast!")
# myproject/urls.py
from django.contrib import admin
from django.urls import path
from myapp.views import slow_view, fast_view
urlpatterns = [
path('admin/', admin.site.urls),
path('slow/', slow_view),
path('fast/', fast_view),
]
To use django-silk, you’ll need to install it and add it to your INSTALLED_APPS:
pip install django-silk
Then, in your settings.py:
INSTALLED_APPS = [
# ... other apps
'silk',
'myapp', # your app
]
MIDDLEWARE = [
# ... other middleware
'silk.middleware.SilkyMiddleware',
]
After running migrations (python manage.py migrate), you can access the Silk dashboard by visiting /silk/ in your browser (e.g., http://127.0.0.1:8000/silk/). When you navigate to /slow/ in your application, you’ll see an entry appear in the Silk dashboard. Clicking on it reveals a detailed breakdown of the request.
The most surprising thing about django-silk is that it profiles everything by default, including static file serving and requests from your browser’s favicon.ico. You can and should filter these out.
The core of what django-silk does is intercepting requests and responses via Django’s middleware. For each request, it starts a timer and records all subsequent actions: view function execution, template rendering, and database queries. It attaches a unique request ID to each request, allowing you to trace all associated database queries and other events within that specific request’s context.
You can control what django-silk profiles using configuration in your settings.py. For instance, to ignore requests for static files and the favicon, you’d add:
SILKY_IGNORE_URLS = [
r'^/static/.*',
r'/favicon.ico$',
]
SILKY_ENABLE_PROFILER = True # Ensure profiling is enabled
SILKY_PROFILER_ARGUMENTS = {
'మెమరీ': False, # Disable memory profiling for less overhead
}
The SILKY_IGNORE_URLS setting is a list of regular expressions. Any URL matching one of these patterns will not be profiled, significantly reducing noise in your dashboard. SILKY_ENABLE_PROFILER must be True to activate detailed profiling. SILKY_PROFILER_ARGUMENTS allows fine-grained control over the underlying cProfile module, here disabling memory profiling to reduce overhead.
The dashboard provides several views:
- Requests: A list of all profiled requests, sortable by time, URL, and duration.
- SQL: A consolidated view of all SQL queries executed across all requests, showing their frequency and total execution time.
- Models: An overview of model usage, indicating which models were accessed and how often.
- View Functions: Breakdown of time spent within your view functions.
When you click into a specific request, you get a detailed view:
- Summary: Overall request duration, HTTP method, status code, and the originating IP.
- View: The specific view function that handled the request and how long it took.
- SQL Queries: A list of all database queries made during that request, including their individual execution times and the query itself. This is often the most valuable section for identifying performance bottlenecks.
- Templates: If templates were rendered, this section shows which ones and the time spent rendering them.
- Other: Any other intercepted events, such as external HTTP requests made by your application.
The most important metric in django-silk is the "Time Spent" column for individual SQL queries within a request. A query that takes 100ms, when repeated 10 times in a loop within a view, will make that view incredibly slow, and django-silk will clearly highlight this. You can then click on a query to see its exact SQL statement and the stack trace leading to its execution.
To optimize a slow query identified by django-silk, you would typically analyze the query itself and the context in which it’s executed. If a query appears frequently or takes too long, you might need to:
- Add database indexes: If
silkshows a query scanning large tables without an index, adding one can dramatically speed it up. For example, if a querySELECT ... FROM myapp_mymodel WHERE user_id = 5is slow, adding an index onmyapp_mymodel.user_idcan help. This is done via Django’sdb_index=Trueon model fields or explicitMeta.indexes. - Optimize ORM usage: Avoid N+1 query problems by using
select_relatedorprefetch_related. Ifsilkshows many similar queries for related objects, these methods are your solution. For example, ifmyapp_user.posts.all()in a loop triggers many queries, changing it tomyapp_user.posts.all().select_related('author')(if 'author' is a ForeignKey on the Post model) ormyapp_user.posts.all().prefetch_related('comments')can reduce queries. - Denormalize or cache: For read-heavy operations, consider denormalizing data or implementing caching mechanisms.
If you see a spike in requests that are all extremely slow and consuming high CPU, it’s often due to a poorly optimized database query that django-silk will pinpoint.
The next thing you’ll likely want to tackle is understanding how to manage the amount of data django-silk collects, especially in production, as it can consume significant disk space and I/O.