--- title: "How to search in a huge table on Django admin" url: "/2020/02/17/how-to-search-in-a-huge-table-on-django-admin" date: 2020-02-17T17:08:00-04:00 lastmod: 2020-04-25T12:35:53-03:00 tags : [ "python", "django", "programming" ] ---
Hello everyone! We all know that the Django admin is a super cool tool for Django. You can check your models, and add/edit/delete records from the tables. If you are familiar with Django, I'm sure you already know about it. I was given a task: Our client wanted to search in a table by one field. It seems easy enough, right? Well, the tricky part is that the table has **523.803.417 records**. Wow. **523.803.417 records**. At least the model was not that complex: On `models.py`: ```python class HugeTable(models.Model): """Huge table information""" search_field = models.CharField(max_length=10, db_index=True, unique=True) is_valid = models.BooleanField(default=True) def __str__(self): return self.search_field ``` So for Django admin, it should be a breeze, right? **WRONG.** ## The process First, I just added the search field on the admin.py: On `admin.py`: ```python class HugeTableAdmin(admin.ModelAdmin): search_fields = ('search_field', ) admin.site.register(HugeTable, HugeTableAdmin) ``` And it worked! I had a functioning search field on my admin. ![2020-02-14-154646](/2020-02-14-154646.png) Only one problem: It took **3mins+** to load the page and **5mins+** to search. But at least it was working, right? ## WTF? First, let's split the issues: 1. Why was it taking +3mins just to load the page? 2. Why was it taking +5mins to search if the search field was indexed? I started tackling the first one, and found it quite easily: Django was getting only 100 records at a time, but **it had to calculate the length for the paginator and the "see more" button on the search bar** ![2020-02-14-153605](/2020-02-14-153605.png) So near, yet so far ## Improving the page load A quick look at the Django docs told me how to deactivate the "see more" query: [ModelAdmin.show_full_result_count](https://docs.djangoproject.com/en/2.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.show_full_result_count) > Set show_full_result_count to control whether the full count of objects should be displayed on a filtered admin page (e.g. 99 results (103 total)). If this option is set to False, a text like 99 results (Show all) is displayed instead. On `admin.py`: ```python class HugeTableAdmin(admin.ModelAdmin): search_fields = ('search_field', ) show_full_result_count = False admin.site.register(HugeTable, HugeTableAdmin) ``` That fixed one problem, but how about the other? It seemed I needed to do my paginator. Thankfully, I found an _awesome_ post by Haki Benita called ["Optimizing the Django Admin Paginator"](https://hakibenita.com/optimizing-the-django-admin-paginator) that explained exactly that. Since I didn't need to know the records count, I went with the "Dumb" approach: On `admin.py`: ```python from django.core.paginator import Paginator from Django.utils.functional import cached_property class DumbPaginator(Paginator): """ Paginator that does not count the rows in the table. """ @cached_property def count(self): return 9999999999 class HugeTableAdmin(admin.ModelAdmin): search_fields = ('search_field', ) show_full_result_count = False paginator = DumbPaginator admin.site.register(HugeTable, HugeTableAdmin) ``` And it worked! The page was loading blazingly fast :) But the search was still **ultra slow**. So let's fix that. ![2020-02-14-153840](/2020-02-14-153840.png) ## Improving the search I checked A LOT of options. I almost went with [Haystack](https://haystacksearch.org/), but it seemed a bit overkill for what I needed. I finally found this super cool tool: [djangoql](https://github.com/ivelum/djangoql/). It allowed me to search the table by using _sql like_ operations, so I could search by `search_field` and make use of the indexation. So I installed it: On `settings.py`: ```python INSTALLED_APPS = [ ... 'djangoql', ... ] ``` On `admin.py`: ```python from django.core.paginator import Paginator from django.utils.functional import cached_property from djangoql.admin import DjangoQLSearchMixin class DumbPaginator(Paginator): """ Paginator that does not count the rows in the table. """ @cached_property def count(self): return 9999999999 class HugeTableAdmin(DjangoQLSearchMixin, admin.ModelAdmin): show_full_result_count = False paginator = DumbPaginator admin.site.register(HugeTable, HugeTableAdmin) ``` And it worked! By performing the query: ```python search_field = "my search query" ``` I get my results in around 1 second. ![2020-02-14-154418](/2020-02-14-154418.png) ## Is it done? Yes! Now my client can search by `search_field` on a table of 523.803.417 records, very easily and very quickly. I'm planning to post more Python/Django things I'm learning by working with this client, so you might want to stay tuned :)