Как диагностировать и оптимизировать медленные эндпоинты в Django

Ответ

Оптимизация медленного эндпоинта — это пошаговый процесс, который начинается с диагностики для поиска узкого места.

1. Диагностика (поиск узкого места)

  • Профилирование запросов: Используйте django-debug-toolbar. Он наглядно показывает количество и время выполнения SQL-запросов, время рендеринга шаблонов и другие метрики для каждого запроса.
  • Анализ логов: Проверьте логи веб-сервера (Nginx/Gunicorn) и базы данных на предмет ошибок или медленных запросов (slow query log).
  • Профилирование кода: Для поиска медленных функций в Python-коде используйте встроенный cProfile или py-spy.

2. Оптимизация

В зависимости от найденной проблемы, применяются следующие техники:

  • Оптимизация ORM-запросов:

    • Решение проблемы N+1: используйте select_related (для ForeignKey, OneToOneField) и prefetch_related (для ManyToManyField, reverse ForeignKey) для сокращения числа запросов к БД.
    • Загружайте только нужные поля с помощью only() и defer().
    • Используйте values() или values_list() для получения данных в виде словарей/кортежей без создания экземпляров моделей.
  • Кеширование:

    • Кешируйте результаты тяжелых вычислений или запросов к БД с помощью django.core.cache.
    • Используйте декоратор @cache_page для кеширования ответа всего представления.
      
      from django.views.decorators.cache import cache_page

    @cache_page(60 * 15) # Кешировать на 15 минут def my_slow_view(request):

    ... ресурсоемкая логика ...

    return HttpResponse(...)
  • Асинхронные задачи:

    • Выносите долгие операции (отправка email, обработка изображений, запросы к внешним API) в фоновые задачи с помощью Celery и Redis/RabbitMQ, чтобы не блокировать HTTP-ответ.
  • Оптимизация базы данных:

    • Убедитесь, что для полей, по которым происходит фильтрация и сортировка, созданы индексы.

Ответ 18+ 🔞

А, ну вот, опять этот эндпоинт ползёт, как черепаха в сиропе, да? Ну что ж, придётся его разобрать по косточкам, блядь. Это не магия, а пошаговый разбор полётов, который начинается с одного — найти, где у нас собака зарыта, то есть узкое место, сука.

1. Диагностика (ищем, где тормозит)

  • Профилирование запросов: Первым делом — django-debug-toolbar, наш верный друг. Эта мартышлюшка покажет тебе на блюдечке, сколько SQL-запросов летит, сколько времени каждый из них сосёт, и сколько шаблоны рендерятся. Если там запросов, как грязи после дождя, — пиши пропало, это оно.
  • Анализ логов: Залезь в логи веб-сервера (Nginx/Gunicorn) и, главное, в логи базы. Особенно в slow query log. Там могут лежать такие перлы, от которых волосы дыбом встают, блядь. Нашёл запрос, который выполняется две секунды? Вот он, корень зла!
  • Профилирование кода: Если с базой вроде всё ок, а всё равно тормозит — значит, твой Python-код ебёт мозги процессору. Бери cProfile или py-spy и смотри, какая функция сидит на процессоре, как репейник на собаке.

2. Оптимизация (бьём по найденным точкам)

Ну, нашли слабое звено. Теперь вмазываем по нему конкретно.

  • Оптимизация ORM-запросов (здесь чаще всего пиздец):
    • Проблема N+1: Классика жанра, блядь. В цикле на каждый объект лезет в базу за связанными данными. Лечится select_related (для ForeignKey, OneToOneField) и prefetch_related (для ManyToManyField, reverse ForeignKey). Вместо сотни запросов — один-два, красота!
    • Не тащи всё подряд: Зачем тебе все поля модели, если нужны только id и name? Используй only() и defer(). А если экземпляры моделей вообще не нужны, бери values() или values_list() — получишь словари или кортежи, это легче, сука.
  • Кеширование (волшебная таблетка):

    • Если данные не меняются каждую секунду — кешируй, блядь! Результат тяжёлого запроса или вычисления — в кеш. Используй django.core.cache.
    • А можно и весь ответ вьюхи закешировать, если он для всех одинаковый. Декоратор @cache_page — твой друг.
      
      from django.views.decorators.cache import cache_page

    @cache_page(60 * 15) # Кешируем на 15 минут, и пусть все идут лесом def my_slow_view(request):

    ... тут твоя ресурсоёмкая логика, которая теперь не будет ебать сервер каждый раз ...

    return HttpResponse(...)
  • Асинхронные задачи (разгружаем главный поток):
    • Отправка писем, обработка картинок, долгие запросы к сторонним API — это всё, блядь, выноси в фон! Пусть Celery с Redis/RabbitMQ этим занимаются. А пользователю сразу отдаём ответ "Принято!", и не парим никому мозги.
  • Оптимизация базы данных (фундамент):
    • И последнее, но не по важности. Поля, по которым ты filter() и order_by() гоняешь, — они проиндексированы, блядь? Индексы — это как полоса для разгона для запроса. Без них он будет ползти, как Герасим за Муму, в рот меня чих-пых. Проверь и добавь, если чего не хватает.

Вот и весь сказ. Диагностика, потом точечный удар. Ничего сложного, просто не надо писать код, как слон в посудной лавке.