Tips and tricks for optimizing the performance of Django ORM
Tags: django, productivity, programming, python
Published: 2023-05-03 13:59:29

Django is a popular web framework for building complex, data-driven applications, and its Object-Relational Mapping (ORM) system is a crucial component for interacting with databases. However, as your application grows in complexity and scale, it’s essential to ensure that your ORM queries are optimized for performance. In this article, we’ll explore some tips and tricks for optimizing the performance of Django ORM so that your application can handle larger volumes of data and operate more efficiently. These tips will help you get the most out of your database queries and keep your application running smoothly.

###[**Only**](https://docs.djangoproject.com/en/4.2/ref/models/querysets/#only)()

![without only()](https://cdn-images-1.medium.com/max/2044/1*oEd2jTiinH9Bt8XjfKqyFw.png)

In the above code, we are only using two fields. Still, when Django ORM translates Python code into SQL, it retrieves all fields from the model as a result, even if some data is not necessary. To optimize the query and prevent unnecessary data retrieval, developers can utilize the .only() method and explicitly indicate the specific fields that are required.

![with only()](https://cdn-images-1.medium.com/max/2984/1*srEeM7pqWHQAIdh7nMEm_Q.png)

### [**Prefetch_related**](https://docs.djangoproject.com/en/4.2/ref/models/querysets/#prefetch-related) / [**select_related**](https://docs.djangoproject.com/en/4.2/ref/models/querysets/#select-related)

When Django evaluates a QuerySet, the relationship fields are not included in the query, so when you access them, Django runs the DB again to get the values. Accessing a related field in a loop results in N+1 queries. You can check the queries executed on the database using tools like [django-debug-toolbar](https://pypi.org/project/django-debug-toolbar/) and [django-silk](https://pypi.org/project/django-silk/).

![without select related](https://cdn-images-1.medium.com/max/2836/1*JgF57II-arxoDlIX4ODKzw.png)

In the above code, the first query is executed on the option queryset, and then for every option, three queries are executed: question, division, and concern. To reduce queries, we can add question and division in select_related and concern in prefetch_related.

![with select related](https://cdn-images-1.medium.com/max/2836/1*0kDgFKltS9t6zxFUkI0VUA.png)

This code will execute only 2 queries on the database regardless of the number of options: one for options and the second for concerns. In the previous version, if there were 100 options, it would perform 301 (3N + 1) queries on the database.

One important thing to understand is that after adding select_related and prefetch_related, you cannot use ORM functions such as first(), **last(), or *order_by()*, as these will run the queries again. If you want the first object of concern, use option.concerns.all()[0] instead of option.concerns.all(). Use *Prefetch()* only when you want additional filtering and order_by; otherwise, simple prefetch_related(“concern”) is enough.

The basic difference between select_related and prefetch_related is that select_related performs a join with each lookup and gets the results back in the same query, but it extends the “select” to include the column of all joined tables. On the other hand, prefetch_related performs a separate query for each table to be joined. It filters each of these tables with a WHERE IN clause. The select_related can be used for one-to-one and one-to-many relations if the related field exists in the table where the query is being executed. However, if the related field exists in the other table, we can use prefetch_related. It’s also used for many-to-many relations.

### **Queryset Evaluation and Caching**

Take a look at the following simple example to understand ORM evaluation:

![Evaluation](https://cdn-images-1.medium.com/max/2296/1*o8W456K9_h6ceSyxc65ZkA.png)

It might look like it performed two database queries, but it actually performed only a single query, which was executed on the last line, “*len(active_staff)*”.

Creating a queryset doesn’t perform any database activity, not even stacking the filters. A queryset result is only fetched from the database when asked by performing certain actions, like iteration, len, slicing, repr, list, etc. This is called evaluation.

Caching allows you to avoid making multiple database queries when reusing the same queryset. The first time you run a QuerySet, Django saves the results in a temporary storage place called cache. Then, whenever you run that same QuerySet again, Django will use the cached results instead of running another query to the database. This makes the process of retrieving data faster and more efficient.

![without caching](https://cdn-images-1.medium.com/max/2440/1*mua-uaQepv6KYPNmp5VmJQ.png)

In the above example, the same query is executed twice on the database. To avoid this problem, save the QuerySet and reuse it:

![with cache](https://cdn-images-1.medium.com/max/2764/1*E0qrckAc-mboukrhwAVVqg.png)

### [**In_bulk**](https://docs.djangoproject.com/en/4.2/ref/models/querysets/#in-bulk)()

This function will make your life easier if you need a dict map of the queryset. It allows you to retrieve a dictionary of objects from the database based on a list of primary key values or the defined key in the function argument.

Instead of:

![without in-bulk](https://cdn-images-1.medium.com/max/2368/1*bVjuwBi3c4NR2qrRYjPbIQ.png)

Use this:

![with in-bulk](https://cdn-images-1.medium.com/max/2224/1*emQjs_1SfdBPIRRkncP0LQ.png)

The code is much cleaner and also provides a performance boost by leveraging the lazy nature of Django ORM.

---

For those who are passionate about writing neat and organized code and haven’t had the chance to go through “Clean Code” by Robert C. Martin, this article provides a helpful summary of the book’s first half and key takeaways. It’s worth reading if you’re looking to enhance your coding skills and principles.

{% embed https://dev.to/mubtadaali/lessons-from-clean-code-13e9 %}

---

I hope that you have learned something fresh and insightful. If our interests align, please consider subscribing. You can also reach out to me on [LinkedIn](https://www.linkedin.com/in/mubtada-ali-naqvi-3a8660a9/).

Thank you for your time and attention.