Python Дайджест: как ускорить Django проект до (почти) максимума

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Завершение цикла статей про техническое оживление Python Дайджест. В первых трех частях рассказано как был совершен переход с Python 3.4 на Python 3.11 и Django 4, отформатирована вся кодовая база с pre-commit, настроена автоматизация задач на основе Github Actions. В заключительной части расскажу как получить "быстрый" сайт.


Содержание цикла статей


  • 1 часть: Как обновиться с Python 3.4 до Python 3.11, если pip уже сломан
  • 2 часть: Как актуализировать всю кодовую базу с помощью pre-commit
  • 3 часть: Как сделать CI для OpenSource проекта с Github Actions
  • 4 часть: Как ускорить Django проект до (почти) максимума
    — вы здесь —

Состояние после трех частей


  • Приложение для сбора новостей про Python работает на публичном домене.
  • Изменения автоматически (с помощью CI) проверяются на аккуратность, прохождение тестов и выкатываются на сервер.
  • Бэкапы делаются и сохраняются на внешнем диске.
  • PageSpeed показывает значения на уровне 70-80.

Задача (часть 4) — вернуть привычные 95-100 баллов на тесте PageSpeed


В Python Дайджест собираются разрозненные ссылки про Python. Контент обновляется не часто — несколько раз в сутки. Нет большой нагрузки на запись или чтение, при этом есть аудитория. Более 5 лет назад я использовал проект как место для экспериментов с кодом: организация кода, CI/CD, оптимизации SQL в Django, ускорение загрузки сайта, функции отложенной публикации без очередей и cron. Было много всякого испробовано и пригодилось в работе.


Отличной скорости для условно статического сайта можно добиться через пред-генерацию html страниц, а затем очень быструю отдачу через nginx/CDN (или сделать Google AMP-only сайт). Когда-то и такой эксперимент для Django приложения сделаю, но сейчас хочется оставаться динамическим сайтом.


PageSpeed — является простым в использовании мерилом качества сайта. Сервис проверяет скорость загрузки, избыточность данных, даже дизайн немного проверяет. Разобравшись от чего зависят несколько десятков параметров, можно заметно и свой проект улучшить.


PageSpeed показывает качество сайта в баллах, чем ближе к 100, тем лучше. Когда-то анализ Python Дайджест показывал около 100 баллов, а сейчас скатился до 70-80. Это же подтверждается ощущением «медленного» сайта.


План работ


Взяв отчет PageSpeed за основу, начал разбираться:


  • Много времени уходит на первое отображение страницы.
  • Загружается лишняя и устаревшая css/js статика.
  • Контент не виден на первом экране при открытии с мобилки.
  • Более старые выпуски новостей грузятся дольше.

Смотря на такой список, можно догадаться, что статика устарела и грузится синхронно, сжатие перестало работать, запросы в БД имеют проблему N+1. С этого и начнем.


Как ускорил загрузку внешней статики


Одна из причин снижения оценки — устаревшие версии bootstrap 3 и jquery, которые грузятся синхронно. Это замедляет первичное отображения страницы. Чтобы ускорить отображение, можно воспользоваться стандартным «костылем» и просить браузер предзагружать статику асинхронно. Выглядит в верстке это так:


<head>
....
 <link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
 <noscript><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css"></noscript>
...
</head>

Как сжал статику стилей и JavaScript


Сайт Python Дайджест сделан с использованием Bootstrap. Это стабильный фреймворк для создания веб-сайтов, который существует уже больше 10 лет. Для работы с фреймворком достаточно подключить несколько .css файлов в html, нет необходимости тащить весь современный JavaScript для создания сайта.


Редкий сайт использует стандартный Bootstrap. Фреймворк легко расширяется за счет тем, которые порождают дополнительную статику. Все это нужно грузить быстро: сжимать, удалять неиспользуемые настройки. В Django экосистеме для таких задач хорошо использовать пакет django-compressor.


Пакету можно указать список файлов для сжатия (в виде html «импортов»), пакет их превратит в единый файл и сожмет для раздачи в виде кэша. Ограничение у пакета — сжимает локальные файлы. Автоматически скачать и сжать лежащее в CDN не умеет.


{% compress css %}
 {% block styles %}{% endblock %}
 <link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
 <link rel="stylesheet" type="text/css" href="{% static 'css/vs.css' %}">
{% endcompress %}

В дополнение можно взять whitenoise с brotli, который будет сжимать всю статику, а не только ту, что указывается в шаблонах. Как это работает, можно изучить на примере cookiecutter-django.


Как ускорил работы с внешними изображениями


У материалов могут быть изображения на каком-то внешнем сервере. Не всегда такой внешний сервер быстрый на отдачу, поэтому давно придумал автоматически заменять внешние изображения на локальные копии.


Для этого использую пакет django-remdow. Пакет предоставляет фильтры для шаблона по работе с изображениями, в том числе img_local.


img_local найдет в строке, которая содержит html теги, ссылки на изображения, скачает их на локальный сервер, а затем подменит в результирующей html строке ссылки на локальные.


{{ '<img src="http://placehold.it/350x150">'|img_local }}

Пакет берет на себя скачивание изображений, а при необходимости вы можете дожать копии.

Как ускорил локальные изображения


Изображения — тяжелые объекты для сайта. Если есть возможность какие-то не отдавать — так стоит и сделать. Те, что обязательны — максимально сжать: по размеру (высота, ширина), по формату (jpeg, webp), а дальше указать «время жизни» для объекта, чтобы не отдавать лишний раз (ниже будет про это)


В верстке, зачастую, предполагается конкретный размер изображения, например, логотип сайта. Поэтому можно не стесняться и превращать большие изображения в миниатюры. В Python для этого хорошо подходит sorl-thumbnail


{% load thumbnail %}
{% thumbnail object.image "350" as im %}
 <link rel="image_src" href="http://pythondigest.ru{{ im.url }}">
 <meta name="twitter:image" content="http://pythondigest.ru{{ im.url }}"/>
 <meta property="og:image" content="http://pythondigest.ru{{ im.url }}" />
{% endthumbnail %}

И на уровне nginx тоже можно включить сжатие, а также указать «срок годности» для статики в 365 дней.


    gzip_disable "msie6";

    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

    location ~* ^(/media|/static) {
        # by default reed from root/<folder>
         access_log        off;
         log_not_found     off;
         expires           365d;
    }

Как уменьшил отдаваемый размер с GZip и кэшем


Дайджест обновляется не каждую секунду, поэтому спокойно можно кэшировать расчеты, содержание страницы да и саму страницу.


В Django есть несколько Middleware, которые помогают с этим:


MIDDLEWARE = [
    "django.middleware.gzip.GZipMiddleware",
    "django.middleware.cache.UpdateCacheMiddleware",
    ....
    "django.middleware.cache.FetchFromCacheMiddleware",
]

А также есть @cache_page, который можно использовать для кэширования страниц. Его можно обернуть в mixin и подмешивать в View класс. Это позволит делать 0 запросов в БД, если страница запрашивается часто.


В Дайджесте нет развесистой структуры базы данных, все весьма плоско — есть выпуски, есть категории новостей, есть сами новости, есть дополнительная метаинформация. Категорий меньше 10, выпусков уже почти 500, новостей тысячи. В такой комбинации можно натолкнуться на проблему N+1 запроса и извлекать данные в цикле, а не пачками.


Как анализировал и оптимизировал SQL


В Django стандартным средством для отладки является django-debug-toolbar, который позволяет находить тормоза кода, запросов и прочего. Так что включаю пакет и логгирование SQL в настройках проекта.


django-debug-toolbar хорошо дополнить инструментами django-silk и sentry. Их легко и приятно использовать для отладки на рабочем компьютере, не требуют больших изменений в коде для запуска.


  • django-silk предоставляет middleware для сбора и визуализации статистики по HTTP запросам (время выполнения, количество запросов в БД).
  • sentry интегрируется с Django глубже и умеет отслеживать весь путь выполнения запроса с опорой на конкретные строчки кода и используемых библиотек. Sentry — это SaaS, для микропроектов есть бесплатные тарифы, для корпоративного использования стоит посчитать собственную инсталляцию (Sentry версий <10 запускается легко, >=10 — заметно сложнее).

После активации django-debug-toolbar открывал поочередно страницы сайта, и по тем же методам, что и в Django Admin с миллионами записей — 11 практик оптимизаций для начинающих чистил запросы:


  • Убирал N+1 запросы с помощью prefetch_related и select_related.
  • Оставлял только необходимые поля моделей в запросах с помощью only и defer.
  • Улучшал условия в filter и exclude для QuerySet.
  • Предрассчитывал запросы и использовал их как кэш результатов.
  • Добавил недостающих индексов для таблиц.

Для работы с SQL хорошо использовать современные инструменты от DBA. Например, Explain PostgreSQL от Тензор. Это инструмент, который по EXPLAIN запроса может определить не оптимальность выполнения: отсутствие или переизбыток индексов в таблице, недостаточность рабочей памяти у БД и в целом не оптимальность структуры хранения данных.


Что получил в итоге


И в итоге получили 100 в PageSpeed. Теперь все переходы на сайте выглядят «мгновенным». При этом его можно динамически менять.



Что не стал делать или отложил на будущее


  • С помощью django-distill можно сгенерировать статический сайт и его раздавать через nginx.
  • Добавить поддержку Яндекс.Турбо и Google AMP, а также PWA версию сайта.
  • Добавить модуль nginx pagespeed для автоматической оптимизации по рекомендациям PageSpeed



Выводы


  • Если вы можете свести свое приложение к статичному, без большого количества пишущей нагрузки, то сжатие данных и кэши позволят получить очень быстрый сайт. Поисковикам это нравится.
  • Самые первые (простые и быстрые) решения дают максимальное улучшение, дальше каждый процент выгрызается тяжело.
  • Достойный мониторинг, инструменты APM (Application Performance Monitoring), Tracing System, позволяют быстрее находить изъяны проекта, но можно начать и с простых.
  • Performance management позволяет лучше разобраться в проекте.



НЛО прилетело и оставило здесь промокод для читателей нашего блога:
— 15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.

Источник: https://habr.com/ru/companies/first/articles/728792/


Интересные статьи

Интересные статьи

Меня зовут Михаил Сахнюк и я разрабатываю фронтенд уже более пяти лет. Сейчас я фронтенд разработчик в Miro. В статье рассмотрим:• как оптимизировать веб-приложение и ускорить его загрузку;• поче...
Постоянно читая статьи на тему того, как %user% буквально на коленке научил свою кофеварку выгуливать собаку, начинаешь чувствовать себя немного неуютно. Складывается ощущение, что вокруг тебя взрослы...
На сегодняшний день существует несколько тысяч языков программирования, каждый из которых создавался с определенной целью, пытаясь изменить и улучшить недостатки своих предшественников. Так, например,...
Привет! Меня зовут Ксения Кайшева, я пишу приложения под Android в компании 65apps. Сегодня расскажу о новой возможности, которая позволяет централизованно описывать зави...
Python — особенный язык в плане итераций и их реализации, в этой статье мы подробно разберём устройство итерируемых объектов и пресловутого цикла for. Особенности, с которыми вы часто можете сто...