Cache busting

18 августа 2026 · ~12 мин чтения

веб кэш http деплой производительность

Cache busting

Cache busting (сброс кэша через смену адреса) — техника, при которой файлу
при каждом изменении дают новый URL, чтобы браузеры и промежуточные кэши
гарантированно скачали свежую версию, а не отдали сохранённую старую.

История

Проблема, которую решает cache busting, ровесница самого веба. Уже в HTTP/1.0
(RFC 1945, май 1996 года) появился заголовок Expires — сервер мог сказать
браузеру: «храни этот файл у себя и не спрашивай меня до такой-то даты». В
HTTP/1.1 (RFC 2616, июнь 1999-го) добавили более гибкий Cache-Control с
директивой max-age. Кэширование резко снижало нагрузку на сервера и ускоряло
сайты — но породило обратную беду: если файл закэширован на год вперёд, как
доставить пользователю исправление уже сегодня?

Ответ индустрия нащупала в середине 2000-х, когда сайты превратились в тяжёлые
приложения с большими JS- и CSS-файлами. Ключевая фигура здесь — Стив Саудерс
(Steve Souders), руководитель направления производительности в Yahoo, позже в
Google. В книге «High Performance Web Sites» (O'Reilly, 2007) он сформулировал
правило «Add an Expires Header»: статике — максимальный срок кэширования, а
свежесть обеспечивать сменой имени файла. Сам приём он называл «revving» (от
revision — ревизия): style.css превращается в style.v2.css.

Вехи дальше:

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

Что это такое

Идея опирается на одно простое свойство HTTP-кэшей: ключом кэша является
URL
. Браузер не заглядывает внутрь файла и не сравнивает содержимое — он
смотрит только на адрес. Если адрес совпал и срок хранения не вышел, файл
берётся с диска, сервер даже не узнает о запросе.

Отсюда два следствия. Плохое: если ты выложил новый widgets.js по старому
адресу, пользователь со свежим кэшем будет гонять старый код, пока кэш не
протухнет — час, день, год, как настроено. Хорошее: если изменить адрес хотя бы
на символ, для кэша это совершенно новый объект, и браузер обязан сходить за
ним на сервер.

Cache busting — это сознательная эксплуатация второго следствия. Каждой версии
файла — свой уникальный URL. Приёма три:

Важная пара для различения — cache busting vs инвалидация кэша.
Инвалидация — это «найти все копии старого файла и объявить их недействительными»:
задача, знаменитая своей сложностью (шутку «в информатике есть только две
трудные задачи — инвалидация кэша и придумывание имён» приписывают Филу
Карлтону из Netscape). Cache busting — хитрый обход этой задачи: мы никого не
инвалидируем, старые копии продолжают лежать по старым адресам, просто на них
больше никто не ссылается. Они умрут сами, когда истечёт срок.

Вторая пара — cache busting vs ревалидация (ETag/Last-Modified). Ревалидация —
это когда браузер при каждом обращении переспрашивает сервер: «у меня версия с
таким-то отпечатком, она ещё актуальна?» — и получает либо короткое «304, всё
то же», либо новый файл. Свежесть гарантирована, но каждый ресурс стоит одного
сетевого запроса. Cache busting убирает и эти запросы: раз URL уникален для
содержимого, проверять нечего.

Аналогии из жизни

Номер выпуска журнала. Ты не просишь в киоске «свежий Коммерсантъ вообще»,
ты покупаешь «номер 152». Новый выпуск получает новый номер, и перепутать его
со вчерашним невозможно — само имя гарантирует свежесть. Так и хешированный
файл: имя однозначно определяет содержимое. Где ломается: журнал приходит
к тебе сам, по расписанию, а браузер ничего не скачивает, пока страница не
попросит новый адрес. Если HTML со ссылкой на «номер 152» сам застрял в кэше,
ты так и будешь читать про номер 151 — про это ниже, это главные грабли.

Наклейки с датой на заготовках в ресторане. Повар не нюхает каждый
контейнер (не «ревалидирует»), он смотрит на маркировку: дата свежая — берём.
Быстро и без проверок, как immutable-кэш. Где ломается: наклейку можно
переклеить, не меняя содержимого, — и в вебе так же: поднять ?v=9 без
реальных изменений в файле значит заставить всех пользователей заново скачать
то же самое. Обратная ошибка хуже: поменять содержимое, оставив старую
наклейку, — и никто в мире не узнает, что файл другой.

Номер автобусного маршрута. Если маршрут «7» поехал по другим улицам, а
номер оставили, пассажиры по привычке уезжают не туда. Транспортники в таких
случаях вводят «7к» — новое имя для нового поведения, и путаницы нет. Где
ломается:
человек может сесть в «семёрку» по привычке, не глядя на табличку;
браузер, наоборот, абсолютно буквален — он идёт ровно по той ссылке, что
написана в HTML, ни привычек, ни интуиции у него нет. Аналогия занижает
надёжность механизма: в вебе «пересадка на 7к» срабатывает у ста процентов
пассажиров мгновенно.

Как это работает

Разберём канонический продакшен-цикл со сборщиком (Webpack, Vite — неважно).

Шаг 1. Сборка. Сборщик берёт исходный widgets.js, считает хеш его
содержимого (обычно усечённый MD5 или xxHash — криптостойкость тут не нужна,
нужна только чувствительность к изменениям) и кладёт результат в
dist/widgets.9f8a3c1d.js. Изменился хоть один байт исходника — хеш другой,
имя другое.

Шаг 2. Перепрошивка ссылок. Сборщик проходит по HTML-шаблонам и всем
местам, где файл упоминается, и подставляет новое имя:
<script src="/assets/widgets.9f8a3c1d.js">. Часто это делается через
манифест — JSON-карту вида {"widgets.js": "widgets.9f8a3c1d.js"}, которую
читает серверный шаблонизатор.

Шаг 3. Заголовки. Тут ключевая асимметрия, на которой всё держится:

HTML-документ:      Cache-Control: no-cache
                    (хранить можно, но перед использованием — проверить)

Хешированная статика: Cache-Control: max-age=31536000, immutable
                      (год не трогать и даже не проверять)

HTML — единственная точка входа со стабильным адресом, поэтому он всегда
свежий (ценой одного лёгкого запроса-проверки). А вся тяжёлая статика, на
которую он ссылается, кэшируется намертво — её свежесть гарантирована самим
именем.

Шаг 4. Деплой в правильном порядке. Сначала выкладываем новые файлы
статики (старые не удаляем!), потом — новый HTML. Если сделать наоборот,
возникает окно, в котором свежий HTML просит widgets.9f8a3c1d.js, а его на
сервере ещё нет — пользователь получает 404 и сломанную страницу. Старые файлы
держат ещё какое-то время, потому что у кого-то в открытой вкладке живёт старый
HTML, ссылающийся на них.

Шаг 5. Первый визит пользователя после релиза. Браузер проверяет HTML
(запрос-проверка, сервер отвечает новой версией), видит незнакомое имя скрипта,
скачивает его, кэширует на год. Все последующие визиты: HTML проверился одним
дешёвым запросом, статика поднялась с диска за миллисекунды, сеть не тронута.

С query-параметром (widgets.js?v=8) схема та же, только версию проставляет
человек или CMS, а не хеш-функция. Это работает, но держится на дисциплине:
забыл поднять номер — изменение молча не доехало.

Query-string — способ с оговорками

Часть промежуточных кэшей исторически игнорировала или не кэшировала URL с ?, а некоторые CDN умеют настраиваться «не учитывать query при построении ключа» — тогда ?v=8 вообще перестаёт что-либо ломать. Если контролируешь сборку — клади версию в имя файла. Query-вариант оправдан, когда файлы нельзя переименовывать (чужая CMS, ручной деплой).

Это не очистка кэша, а отказ от борьбы с ним

Cache busting никого не «сбрасывает». Старые копии спокойно доживают свой срок во всех кэшах планеты — просто на них больше не указывает ни одна ссылка. Знаменитая трудная задача инвалидации решается тем, что её перестают решать.

Где встречается в обычной жизни

Где встречается в IT и бизнесе

Кто пользуется

Практически весь крупный веб, различия только в деталях:

Точных публичных цифр «сколько процентов сайтов используют cache busting» я не
встречал — честно, не знаю; косвенно масштаб виден по тому, что контент-хеш в
имени файла — дефолт всех основных сборщиков.

Альтернативы и конкуренты

На практике это не конкуренты, а слои: хеши в именах + immutable для статики,
no-cache с ревалидацией для HTML, purge для CDN-инцидентов.

Когда НЕ стоит использовать

Связанные понятия

Литература и источники

Где встретилось у меня

Вчера дорабатывал сайт жилого комплекса: виджеты каталога и генплана прошли
восемь итераций за день, и каждая выкатка на тестовый сервер шла с новым
номером версии в URL скриптов и стилей — иначе проверяющий браузер раз за
разом подтягивал бы закэшированную старую версию, и было бы непонятно, что
именно тестируешь. Классический ручной cache busting через ?v=.

Краткое резюме