Завершение цикла статей про техническое оживление Python Дайджест. В первых трех частях рассказано как был совершен переход с Python 3.4 на Python 3.11 и Django 4, отформатирована вся кодовая база с pre-commit, настроена автоматизация задач на основе Github Actions. В заключительной части расскажу как получить "быстрый" сайт.  В Python Дайджест собираются разрозненные ссылки про Python. Контент обновляется не часто — несколько раз в сутки. Нет большой нагрузки на запись или чтение, при этом есть аудитория. Более 5 лет назад я использовал проект как место для экспериментов с кодом: организация кода, CI/CD, оптимизации SQL в Django, ускорение загрузки сайта, функции отложенной публикации без очередей и cron. Было много всякого испробовано и пригодилось в работе. Отличной скорости для условно статического сайта можно добиться через пред-генерацию html страниц, а затем очень быструю отдачу через nginx/CDN (или сделать Google AMP-only сайт). Когда-то и такой эксперимент для Django приложения сделаю, но сейчас хочется оставаться динамическим сайтом. PageSpeed — является простым в использовании мерилом качества сайта. Сервис проверяет скорость загрузки, избыточность данных, даже дизайн немного проверяет. Разобравшись от чего зависят несколько десятков параметров, можно заметно и свой проект улучшить.  PageSpeed показывает качество сайта в баллах, чем ближе к 100, тем лучше. Когда-то анализ Python Дайджест показывал около 100 баллов, а сейчас скатился до 70-80. Это же подтверждается ощущением «медленного» сайта. Взяв отчет PageSpeed за основу, начал разбираться: Смотря на такой список, можно догадаться, что статика устарела и грузится синхронно, сжатие перестало работать, запросы в БД имеют проблему N+1. С этого и начнем. Одна из причин снижения оценки — устаревшие версии bootstrap 3 и jquery, которые грузятся синхронно. Это замедляет первичное отображения страницы. Чтобы ускорить отображение, можно воспользоваться стандартным «костылем» и просить браузер предзагружать статику асинхронно. Выглядит в верстке это так: Сайт Python Дайджест сделан с использованием Bootstrap. Это стабильный фреймворк для создания веб-сайтов, который существует уже больше 10 лет. Для работы с фреймворком достаточно подключить несколько .css файлов в html, нет необходимости тащить весь современный JavaScript для создания сайта. Редкий сайт использует стандартный Bootstrap. Фреймворк легко расширяется за счет тем, которые порождают дополнительную статику. Все это нужно грузить быстро: сжимать, удалять неиспользуемые настройки. В Django экосистеме для таких задач хорошо использовать пакет django-compressor. Пакету можно указать список файлов для сжатия (в виде html «импортов»), пакет их превратит в единый файл и сожмет для раздачи в виде кэша. Ограничение у пакета — сжимает локальные файлы. Автоматически скачать и сжать лежащее в CDN не умеет. В дополнение можно взять whitenoise с brotli, который будет сжимать всю статику, а не только ту, что указывается в шаблонах. Как это работает, можно изучить на примере cookiecutter-django. У материалов могут быть изображения на каком-то внешнем сервере. Не всегда такой внешний сервер быстрый на отдачу, поэтому давно придумал автоматически заменять внешние изображения на локальные копии. Для этого использую пакет django-remdow. Пакет предоставляет фильтры для шаблона по работе с изображениями, в том числе img_local.  img_local найдет в строке, которая содержит html теги, ссылки на изображения, скачает их на локальный сервер, а затем подменит в результирующей html строке ссылки на локальные. Изображения — тяжелые объекты для сайта. Если есть возможность какие-то не отдавать — так стоит и сделать. Те, что обязательны — максимально сжать: по размеру (высота, ширина), по формату (jpeg, webp), а дальше указать «время жизни» для объекта, чтобы не отдавать лишний раз (ниже будет про это) В верстке, зачастую, предполагается конкретный размер изображения, например, логотип сайта. Поэтому можно не стесняться и превращать большие изображения в миниатюры. В Python для этого хорошо подходит sorl-thumbnail И на уровне nginx тоже можно включить сжатие, а также указать «срок годности» для статики в 365 дней. Дайджест обновляется не каждую секунду, поэтому спокойно можно кэшировать расчеты, содержание страницы да и саму страницу. В Django есть несколько Middleware, которые помогают с этим: А также есть @cache_page, который можно использовать для кэширования страниц. Его можно обернуть в mixin и подмешивать в View класс. Это позволит делать 0 запросов в БД, если страница запрашивается часто. В Дайджесте нет развесистой структуры базы данных, все весьма плоско — есть выпуски, есть категории новостей, есть сами новости, есть дополнительная метаинформация. Категорий меньше 10, выпусков уже почти 500, новостей тысячи. В такой комбинации можно натолкнуться на проблему N+1 запроса и извлекать данные в цикле, а не пачками. В Django стандартным средством для отладки является django-debug-toolbar, который позволяет находить тормоза кода, запросов и прочего. Так что включаю пакет и логгирование SQL в настройках проекта. django-debug-toolbar хорошо дополнить инструментами django-silk и sentry. Их легко и приятно использовать для отладки на рабочем компьютере, не требуют больших изменений в коде для запуска. После активации django-debug-toolbar открывал поочередно страницы сайта, и по тем же методам, что и в Django Admin с миллионами записей — 11 практик оптимизаций для начинающих чистил запросы: Для работы с SQL хорошо использовать современные инструменты от DBA. Например, Explain PostgreSQL от Тензор. Это инструмент, который по EXPLAIN запроса может определить не оптимальность выполнения: отсутствие или переизбыток индексов в таблице, недостаточность рабочей памяти у БД и в целом не оптимальность структуры хранения данных. И в итоге получили 100 в PageSpeed. Теперь все переходы на сайте выглядят «мгновенным». При этом его можно динамически менять.  Что не стал делать или отложил на будущее НЛО прилетело и оставило здесь промокод для читателей нашего блога:
— 15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS. CTO https://sapronov.me/