Введение в оптимизация запросов к БД на django c помощью silk

Стоит ли использовать django в 2024? Я думаю — да. DRF очень удобен, скорость разработки очень высока (особенно, если использовать generic views, django-filters), огромное количество готовых батареек сильно облегчает жизнь и встроенная админка хорошо подходит для большинства сайтов. Полностью асинхронные фреймворки (или переход на другой язык) не дадут большого выиграша, если ваш сервис много работает в БД — вы упретесь в её производительность и ограничения количества соединений с пулом бд. Далее я вкратце пробегусь по основным моментам и дам ссылки на документацию и готовые батарейки.

Django проекты могут начать тормозить с ростом нагрузки. Как правило, это связанно с ростом нагрузки на БД и проблем тут может быть несколько:

N+1

При работе со связанными таблицами необходимо явно указывать их при составлении запроса с помощью select_related для foreign key и prefect_related для many to many. Иначе для загрузки одой страницы у вас буду десятки или сотни запросов. Чтобы понять что это происходит можно использовать silk. Эта библиотека логгирует все запросы которые происходят при загрузки страницы, можно посмотреть сколько их было, сколько времени они выполнялись и план (explain) каждого запроса. Если запросов на страницу много скорее всего вы забыли сделать select/prefetch. Вот пример очень печальной ситуации:

fd5431e5de487e6446021cd5cf153141.png

А вот те же страницы после добавления select/prefetch related:

Если вам необходимо дополнительно фильтровать many to many таблицы — нужно использовать prefech объекты. Например, вы хотите сделать что-то такое:

book = Book.objects.get(pk=1)

shops = book.shop_set.filter(city_id=1)

Это можно сделать так:

book = Book.objects.prefetch_related(Prefetch("shop_set", queryset=Shop.objects.filter(city_id=1), to_attr="city_shop_set")).get(pk=1)

shops = book.city_shop_set.all()

Индексы

Во вкладке SQL можно посмотреть какие запросы долго выполнялись и их план. Возможно, необходимо создать какие-то индексы. (обратите внимание если там есть full scan)

Также, можно также явно указать какие колонки вам нужны с помощью only/defer чтобы не гонять лишние данные.

Некоторые запросы могут долго выполняться из-за каких-то аггреций, например, медленных count. Можно кешировать их или хранить их значения в самой таблице и переодически или по сигналам обновлять. Или требуется какая-то денормализация, чтобы избежать subquery.

silk также позволяет профилировать python код ваших view:

611190bd21748fe6890058fa4edacb4f.png

Репликация

Django поддерживает репликацию бд. С ростом нагрузки имеет смысл направлять select запросы, которых, как правило, намного больше в read-only реплики. Если ваш проект имеет обширую географию можно также использовать шардинг или tenants. Для django также есть готовые батарейки.

Рекурсия

Рекурсивные запросы к БД можно написать помощью common table expressions. Или, если это какие-то деревья использовать MPTT

Асинхронные view

Обычно какие-то запросы к внешним сервисам делают внутри celery, но современные версии django поддерживают асинхронные view. Стоит обратить на них внимание. Для асинхронных http запросов можно использовать aiohttp или httpx внутри таких view.

Сериализаторы

Постарайтесь избегатть to_representation и SerializerMethodField и, особенно, делать какие-то запросы внутри него.

Кеширование

Обычно, можно безболезненно кешировать многие страницы, по крайней мере, на какое-то коротное время, не стоит забывать инвалидировать кэш и при кешировании страницы обращать внимание на куки или auth хедеры, если она различается для разных пользователй. Запросы к БД тожн можно кешировать в т.ч. автоматически.

graphql

Вместо того, чтобы делать много rest запросов на страницу, чтобы получить всякие вложенные данные, можно перейти на graphql и запрашивать их все сразу.

Ну и, наконец, какие-то в принципе не очень хорошо делаются в реляционеных БД. Например, если вам требуется фасетный поиск, стоит обратить внимание на elastic/open search

© Habrahabr.ru