CDN и кеширование нового выпуска
Сервер выбрал r2, но один посетитель ещё видит прежнюю главную. Это не обязательно ошибка переключения: ответ мог прийти из браузерного кеша или CDN. Теперь добавим к release-lab этот слой и разберём, какие правила позволяют выпускать новую версию без исчезающих ресурсов и бесконечного ручного очищения.
CDN получает содержимое от origin и отдаёт его через свои узлы. Origin в нашем стенде — Nginx с выбранным публичным деревом. CDN не делает дерево полным и не исправляет неправильный код 404. Он добавляет хранение ответов и собственные условия повторного обращения. Поэтому после изменения нужно различать состояние origin и состояние внешнего ответа.
Кеш хранит ответ на определённых условиях
HTTP-кеширование связано с ключом, свежестью и возможностью проверки актуальности. Заголовок Cache-Control описывает политику, однако конфигурация CDN может добавлять правила площадки. Один и тот же URL способен возвращать разные ответы по заголовкам или авторизации; в таких случаях важно корректно определить, что можно разделять между посетителями. Для нашего публичного статического дерева начнём с простой модели без персонализации. Стандарт HTTP Caching, RFC 9111.
HTML изменяется под прежним URL, потому что старые адреса должны сохраняться. Если дать такому ответу очень долгую свежесть, посетители смогут видеть r1 после публикации r2 без обращения к серверу. Для начального стенда выберем повторную проверку HTML. Она позволяет хранить ответ, но требует проверки актуальности перед использованием согласно выбранной политике. Это отличается от полного запрета сохранения.
Учебное правило
HTML: Cache-Control: no-cache
Ресурсы с неизменяемым именем: public, max-age=31536000, immutable
health.json: Cache-Control: no-store
Закрытый staging: не общий публичный кеш
Год в примере — срок для ресурса, имя которого больше не меняет смысл, а не рекомендация для всех файлов. no-cache не означает «ответ никогда не хранится». no-store относится к запрету сохранения в поддерживающем правило кеше. Фактическое соблюдение и дополнительные правила проверяют у выбранного CDN. Статья не утверждает, что конкретная площадка уже подключена к ProfessorWeb.
Изменилось содержимое — изменилось имя ресурса
Пусть HTML r1 использовал /assets/site.r1.css, а r2 — /assets/site.r2.css. Оба файла могут долго храниться, потому что новый выпуск не подменяет содержание старого имени. В рабочем генераторе вместо читаемого номера часто применяют хеш содержимого. Смысл тот же: ссылка определяет конкретные байты, а не постоянно меняющийся «последний стиль».
Новый HTML выбирает новое имя. Старый HTML, уже открытый или сохранённый, всё ещё способен запросить прежний ресурс. Поэтому публичный результат нового выпуска должен сохранять необходимые прежние имена на переходный срок либо обслуживать их из общего неизменяемого хранилища. Просто оставить старый каталог под releases недостаточно, если Nginx читает только current/public. Важно, доступен ли файл через публичный URL после смены ссылки. Значение расширения immutable описано отдельно в RFC 8246.
Выпуск r2
index.html → /assets/site.r2.css
assets/site.r2.css → новый стиль
assets/site.r1.css → сохранённый прежний стиль
Такая схема предотвращает одну распространённую гонку: старый HTML пришёл до переключения, а CSS — после него. Она не гарантирует согласованность любого клиентского состояния, например service worker со своей стратегией. Если такой компонент используется, его версия и кеши включаются в отдельный план. В нашем базовом стенде service worker отсутствует.
Настроить ответы origin
На изолированном статическом Nginx выделим неизменяемые ресурсы в общее хранилище /srv/release-lab/shared/assets. Перед выбором HTML туда добавляют новые файлы, не заменяя содержание старых имён. Общую политику HTML задают там, где страницы действительно обслуживаются. Следите за наследованием add_header: собственные заголовки в дочернем блоке могут потребовать повторения других необходимых значений в зависимости от версии и настроек. После будущего применения читают фактический ответ каждого класса ресурсов.
location ^~ /assets/ {
alias /srv/release-lab/shared/assets/;
add_header Cache-Control "public, max-age=31536000, immutable";
}
Фрагмент подходит только тогда, когда все попавшие под него ресурсы имеют неизменяемые имена. Если там лежит logo.svg, содержание которого заменяют на месте, правило слишком широкое. Тогда меняют генерацию имён или сужают маршрут. Завершающий слеш у alias соответствует префиксу; файлы выдаются обычным статическим обработчиком, а отсутствующий получает ошибку. Также не стоит долгим сроком кешировать отсутствие нового файла: сначала полностью подготавливают выпуск, затем показывают ссылки на него. Документация заголовков Nginx.
Повторная проверка HTML зависит и от валидаторов, например ETag или Last-Modified. Если разные байты ошибочно получают одинаковый валидатор, сервер способен подтвердить сохранённый старый ответ. Поэтому при подготовке выпуска учитывают, как формируются эти признаки, и сравнивают реальное содержание изменённой страницы. Один заголовок no-cache не исправляет ошибочную идентичность версии. На простом стенде особенно полезна проверка изменения с тем же размером текста: она показывает, нельзя ли спутать два результата только по метаданным файла.
Порядок публикации и очистки
Сначала кандидат становится доступен на origin и проходит проверку. Затем выполняют необходимую очистку HTML на CDN или ожидают повторной проверки по выбранным правилам. Если очистить кеш до готовности origin, внешний узел может сохранить прежний или ошибочный ответ. Большая массовая очистка также способна увеличить нагрузку на источник. Для статической библиотеки полезно понимать затронутые адреса, а не очищать всё при каждой правке слова.
После действия сравнивают внешний ответ с origin. Используют заголовки выбранного CDN, возраст ответа и индикатор выпуска, отмечая точку наблюдения. Одного собственного заголовка не всегда достаточно: разные слои способны добавлять или сохранять его вместе с прежним ответом. Свежий health.json подтверждает свой маршрут, но не сообщает автоматически номер каждого сохранённого HTML. Поэтому проверяют значимые страницы непосредственно.
Закрытый staging требует отдельной политики. Для него используем Cache-Control: private, no-store в ответах, включая ошибки и собственные обработчики. private запрещает хранение в общем кеше по правилам HTTP, а no-store дополнительно ограничивает сохранение; пароль и noindex решают другие задачи. На CDN исключают тестовое имя из общего кеширования и проверяют фактический ответ после аутентификации. Перенос production-правила public на закрытое окружение без анализа способен нарушить границу доступа. Поэтому различие хранится в конфигурации окружения, а не в HTML артефакта.
Возврат также проходит через кеш
При откате origin снова выбирает r1, но CDN может продолжать отдавать HTML r2. Старые ресурсы должны оставаться доступными в обе стороны. У r1 внутри собственного дерева ещё нет site.r2.css, поэтому простая смена корня этого не гарантирует. Наше общее хранилище сохраняет оба ресурса независимо от current: прежний HTML получает старый CSS, ещё сохранённый новый HTML — новый CSS. Без общего хранилища пришлось бы подготовить отдельный возвратный артефакт с прежним HTML и обоими наборами ресурсов. Перезаписывать принятый r1 ради этого нельзя.
План возврата включает необходимую очистку или повторную проверку HTML и внешнее наблюдение. Команда смены ссылки не считается окончанием инцидента, если посетитель всё ещё получает неприемлемый результат через внешний слой. Очистку общего хранилища выполняют отдельно после срока поддержки сохранённых страниц, а копия и план восстановления теперь включают необходимые ресурсы из shared/assets.
Клиентские кеши нельзя гарантированно отменить серверной очисткой CDN. Если прежде браузеру был обещан долгий срок свежести меняющегося URL, исправление может потребовать времени или нового адреса ресурса. Именно поэтому неизменяемые имена вводят до возникновения проблемы. Они позволяют расширять кеширование без зависимости от ручного вмешательства у каждого посетителя.
В результате урока политика соответствует назначению ответа: изменяемый HTML проверяется, неизменяемые ресурсы сохраняются долго, индикатор не должен скрывать выпуск за копией. Следующий урок перейдёт к прикладному upstream и разберёт ситуации, когда внешний сервер получает 502 или не дожидается ответа с 504.