Как сократить передачу бандла с 7,76 МиБ до 15 КБ: использование предыдущей сборки как словаря
Когда бандл весит 7,76 МиБ после brotli, а релизы выходят дважды в день, каждый новый деплой превращается в испытание для пользовательских каналов. Хеш в имени файла меняется — значит, кеш промахивается, и клиенты скачивают одни и те же мегабайты снова и снова. Мы нашли способ сократить передачу до

Когда бандл весит 7,76 МиБ после brotli, а релизы выходят дважды в день, каждый новый деплой превращается в испытание для пользовательских каналов. Хеш в имени файла меняется — значит, кеш промахивается, и клиенты скачивают одни и те же мегабайты снова и снова. Мы нашли способ сократить передачу до 15 КБ, используя предыдущую сборку как словарь для новой. Рассказываю, как это работает на практике и какие подводные камни ждут при внедрении.
Как работает Compression Dictionary Transport и почему дельты экономят трафик
Compression Dictionary Transport (RFC 9842) — это механизм, который позволяет браузеру использовать ранее загруженный ресурс как словарь для сжатия нового. Идея проста: если клиент уже скачал вчерашнюю версию бандла, сервер может отправить не весь файл, а только разницу между вчерашней и сегодняшней сборкой. Эта дельта весит в разы меньше — в нашем случае 15 КБ вместо 7,76 МиБ. Экономия достигает 99,8%, и это не предел.
На практике это работает так: сервер хранит несколько предыдущих версий бандла и при запросе новой версии проверяет, есть ли у клиента одна из них. Если есть, сервер отправляет только изменённые части, используя старую версию как словарь. Браузер, получив дельту, восстанавливает полный файл, объединяя её со словарём. Для пользователя всё прозрачно — он просто получает обновлённый бандл, но трафик сокращается в сотни раз.
Мы внедрили этот подход на нашем портале, где в среднем два релиза в день. До внедрения половина клиентов качала бандл заново по несколько раз в сутки, потому что хеш в имени файла менялся при каждом обновлении. Теперь вместо полной загрузки уходит лишь небольшая дельта, что особенно важно для пользователей с медленным интернетом или лимитированным трафиком.
Почему дельты только к предыдущей сборке хватает лишь на 43% визитов
Казалось бы, хранить одну предыдущую версию — достаточно. Но наши логи показали, что дельта только к предыдущей сборке покрывает лишь 43% визитов. Почему так мало? Дело в том, что пользователи обновляют страницу не сразу после релиза — кто-то заходит раз в день, кто-то раз в неделю. Если клиент пропустил несколько релизов, его словарь устарел, и дельта к предыдущей версии ему не подходит.
Чтобы увеличить покрытие, нужно хранить несколько предыдущих версий. Мы проанализировали свои логи и выяснили, что окно в 5–7 версий покрывает около 90% визитов. Но хранить много словарей — значит тратить память на сервере и усложнять логику. Поэтому мы оптимизировали окно под свои метрики: посчитали, сколько версий нужно, чтобы покрыть большинство клиентов, и остановились на пяти.
Важно понимать, что окно словарей зависит от частоты релизов и поведения пользователей. Если вы выпускаете обновления раз в неделю, а пользователи заходят ежедневно, окно может быть меньше. Если релизы ежедневные, а аудитория неактивная — больше. Методика расчёта проста: соберите логи посещений, посмотрите, какие версии бандла запрашивают клиенты, и определите, сколько предыдущих версий покрывает, скажем, 95% запросов.
Как отдавать дельты из nginx без reload на каждый релиз
Техническая реализация оказалась не такой сложной, как казалось. Мы используем nginx, и нам не хотелось перезагружать его при каждом релизе. Решение — динамическая генерация дельт через модуль nginx, который вычисляет разницу между запрошенной версией и версией, указанной в заголовке клиента. Этот модуль работает в связке с словарями, которые хранятся в памяти.
Клиент отправляет заголовок с указанием доступного словаря, например, Dictionary-ID: v123. Сервер проверяет, есть ли такая версия в окне, и если есть — генерирует дельту на лету. Если версии нет — отдаёт полный бандл. Всё происходит без перезагрузки nginx, потому что модуль обращается к словарям по идентификатору, а не по заранее заданному списку.
Один из нюансов — формат дельты. Мы используем алгоритм zstd, который поддерживает словарное сжатие и встроен в большинство современных браузеров. Для старых браузеров, которые не поддерживают RFC 9842, мы отдаём полный бандл. Это означает, что часть трафика остаётся прежней, но для большинства пользователей экономия значительная.
Что ломается на откатах, канарейках и бампах зависимостей
Не всё гладко в реальном мире. Откаты — это кошмар для системы словарей. Если вы откатили релиз, а клиент уже получил новую версию и использует её как словарь, старая версия может не подойти для генерации дельты. В итоге клиент получит полный бандл, что нивелирует экономию. Мы решили эту проблему, добавив проверку: если словарь клиента не совпадает ни с одной версией в окне, отдаём полный файл.
Канарейки — это отдельная история. При выкатке на часть пользователей новой версии, дельты генерируются от предыдущей, но если канареечная версия отличается от стабильной, словарь может не подойти. Здесь помогает хранение нескольких версий в окне, но нужно быть осторожным с тем, какие версии помечать как словари.
Бампами зависимостей мы называем ситуации, когда обновление библиотеки приводит к значительным изменениям в бандле. Если изменения затрагивают большую часть кода, дельта может быть больше, чем ожидалось, и экономия снижается. В таких случаях мы временно отключаем словарное сжатие для конкретного релиза или увеличиваем окно словарей.
Кого затронет и как
Разработчики фронтенда и DevOps-инженеры получат главную выгоду: снижение нагрузки на серверы и сокращение трафика для пользователей. Пользователи с медленным интернетом заметят ускорение загрузки страниц, а те, кто платит за трафик, — уменьшение расходов. Для бизнеса это экономия на инфраструктуре и улучшение пользовательского опыта, что напрямую влияет на конверсию.
В российском сегменте, где многие пользователи сидят в регионах с нестабильным интернетом, такая оптимизация особенно актуальна. Мы уже видим положительные отзывы от пользователей, которые раньше жаловались на долгую загрузку после обновлений.
Что будет дальше
Мы планируем расширить использование Compression Dictionary Transport на другие ресурсы, не только на бандлы. Например, на изображения и шрифты, которые также обновляются нечасто. Также рассматриваем возможность автоматического подбора окна словарей на основе аналитики, чтобы не настраивать его вручную.
В ближайшее время ожидается более широкая поддержка RFC 9842 в браузерах, что сделает эту технологию ещё более привлекательной. Мы советуем всем, кто работает с большими бандлами, присмотреться к этому подходу — экономия трафика и ускорение загрузки стоят того.
Итог
Compression Dictionary Transport — это рабочий инструмент, который позволяет сократить передачу бандла с 7,76 МиБ до 15 КБ при непрерывной поставке. Главное — правильно рассчитать окно словарей и учесть особенности откатов и канареек. Если вы хотите ускорить загрузку и сэкономить трафик, попробуйте внедрить эту технологию — результаты вас приятно удивят.