How to speed up Django querysets

Table of Contents

Introduction

It may happen that your web application can be slow and analysing the code and the performance, you realize that the bottleneck is the number of database hits.
If you are looking for a solution to speed up your queryset in a Django application, you are in the right place!

In this article we will see how to speed up Django querysets using the select_related method.

Speed up Django queryset using select_related method

The Django documentation describes this method in this way:

Returns a QuerySet that will “follow” foreign-key relationships, selecting additional related-object data when it executes its query

Django documentation

Let’s see now how it is possible to use this method.

Models creation and addition of records

Suppose to have these two simple models and for simplicity each album has only one artist:

class Artist(models.Model):
    name = models.CharField(max_length=10)
class Album(models.Model):
    name = models.CharField(max_length=30)
    artist = models.ForeignKey(Artist, on_delete=models.CASCADE)

With this simple piece of code we can create 100 albums and 100 artists where each artist is associated to an album.

for idx in range(100):
    artist_name = "artist_{}".format(idx)
    artist_obj = Artist.objects.create(name=artist_name)
    album_name = "album_{}".format(idx)
    Album.objects.create(name=album_name, artist=artist_obj)

Slow approach

Suppose that we need to print each album with its artist.

from django.db import connection
print("Initial number of queries: {}".format(len(connection.queries)))
album_qs = Album.objects.all()
for album in album_qs:
    artist = album.artist
    print("Album name: {} - Artist name: {}".format(album.name, artist.name))
print("Final number of queries: {}".format(len(connection.queries)))

Analysing the code it is possible to noticed that an initial queryset is done on Album model.
After this, a loop is performed to print each album and the linked artist.

Using connection.queries, it is possible to check how many queries django is doing.
The output of the code snippet above is the following:

Initial number of queries: 0
Album name: album_0 - Artist name: artist_0
Album name: album_1 - Artist name: artist_1
Album name: album_2 - Artist name: artist_2
Album name: album_3 - Artist name: artist_3
Album name: album_4 - Artist name: artist_4
Album name: album_5 - Artist name: artist_5
...
Album name: album_95 - Artist name: artist_95
Album name: album_96 - Artist name: artist_96
Album name: album_97 - Artist name: artist_97
Album name: album_98 - Artist name: artist_98
Album name: album_99 - Artist name: artist_99
Final number of queries: 101

The first query is done when all Albums are retrieved, but what about the other 100?
Basically, everytime access to artist variable is requested, there is an additional database hit.

artist = album.artist

Speed up Django queryset

It is possible to improve this code using the select_related method in this way:

print("Initial number of queries: {}".format(len(connection.queries)))
album_qs = Album.objects.all().select_related("artist")
for album in album_qs:
    artist = album.artist
    print("Album name: {} - Artist name: {}".format(album.name, artist.name))
print("Final number of queries: {}".format(len(connection.queries)))

The only difference is on the second line.
Using select_related, Django ‘pre-loads’ all artists for each album.

Notice anything different now?
Exactly, the modification done has decreased the number of database hits from 101 to 1!

Initial number of queries: 0
Album name: album_0 - Artist name: artist_0
Album name: album_1 - Artist name: artist_1
Album name: album_2 - Artist name: artist_2
Album name: album_3 - Artist name: artist_3
Album name: album_4 - Artist name: artist_4
Album name: album_5 - Artist name: artist_5
...
Album name: album_95 - Artist name: artist_95
Album name: album_96 - Artist name: artist_96
Album name: album_97 - Artist name: artist_97
Album name: album_98 - Artist name: artist_98
Album name: album_99 - Artist name: artist_99
Final number of queries: 1

Conclusion

With this brief use case we have seen how it is possible to decrease the number of database accesses using the select_related Django querysets method.
Django offers other methods for speeding up queries that are just as effective. In the next posts I will analyze others.

As always, if you have any doubts or if you are in trouble, I invite you to write me a comment. If not take a look at the latest posts!

Leave a Comment

Your email address will not be published. Required fields are marked *