Современный frontend состоит из HTML-манифеста и набора chunk-файлов. Если index.html обновился раньше загрузки ассетов на все origin или старые файлы удалены сразу после релиза, пользователь получает смесь поколений. Географические узлы CDN и service worker делают симптом непостоянным.
Сохраните проблемный HTML и имена загруженных chunk, затем запросите их с нескольких точек с заголовками Age, ETag, Cache-Control, Via и идентификатором релиза. Не начинайте с purge everything: сначала нужно понять, устарел HTML, asset, origin или клиентский service worker.
Что проверить в первую очередь
Для проблемы «CDN одновременно отдаёт пользователям разные версии JavaScript» сначала зафиксируйте один воспроизводимый пример: точное время, идентификатор объекта, пользователя или операции, входные данные, версию приложения и фактический результат. Отдельно запишите ожидаемое поведение: каждая версия HTML ссылается на существующие immutable-ассеты с уникальными хешами, а переключение релиза происходит атомарно и не требует опасной глобальной очистки. Это не формальность. Без исходной точки легко принять временное совпадение за исправление, изменить сразу несколько условий и потерять возможность доказать настоящую причину сбоя.
Проверку начинайте с чтения состояния и журналов. Не запускайте массовый перерасчёт, повторную отправку, удаление записей или изменение прав на рабочей системе, пока не подготовлены резервная копия и способ отката. Секреты, токены, персональные данные и содержимое заказов в диагностические выгрузки не включайте.
- Проверьте, содержит ли имя каждого JS-файла content hash.
- Сверьте Cache-Control для HTML и хешированных assets.
- Убедитесь, что новый asset загружен до публикации HTML.
- Проверьте несколько origin и правила балансировщика.
- Исключите старый service worker и browser cache в чистом профиле.
Почему возникает проблема
Видимый симптом обычно находится в конце цепочки. Пользователь видит неверный статус или отказ, хотя первичная ошибка могла произойти в API, фоновой задаче, кеше, очереди, внешнем сервисе либо при проверке доступа. Основной риск этого сценария: HTML и chunk-файлы становятся несовместимыми, интерфейс падает только у части аудитории, а повторная загрузка не всегда исправляет состояние. Поэтому исправление только интерфейса или ручная правка итоговой записи часто скрывает проблему, но не устраняет её.
Разбирайте события по хронологии и ищите первую точку, где фактические данные перестают соответствовать бизнес-правилу. В распределённой системе одинаково важны успешный ответ, повтор запроса, задержка события, параллельное выполнение и восстановление после временного отказа.
- Одинаковое имя app.js перезаписывается при каждом релизе и кешируется на разных сроках.
- HTML опубликован до завершения загрузки chunks.
- Старые assets удаляются сразу, хотя CDN ещё отдаёт старый HTML.
- Два origin содержат разные каталоги сборки.
- Service worker использует cache-first без версии и продолжает выдавать предыдущий app shell.
Пошаговая диагностика
Создайте безопасный тестовый сценарий, максимально похожий на проблемный, но не затрагивающий реальные списания, рассылки и клиентские данные. Присвойте операции единый correlation ID и проследите его через входящий запрос, бизнес-логику, базу, очередь и внешние интеграции. Для каждого этапа фиксируйте вход, результат, код ответа, время выполнения и номер версии записи.
Сначала подтвердите сам факт расхождения, затем последовательно исключайте уровни. Сравните рабочий и ошибочный случаи по одним и тем же полям. Проверьте часовой пояс, формат идентификаторов, порядок событий, права сервисной учётной записи и актуальность конфигурации на каждом экземпляре приложения. Если результат зависит от повторного запроса, задержки или конкретного узла, это важная часть причины, а не случайный шум.
- Сравните checksum тела одного URL с разных CDN POP.
- Посмотрите X-Cache, Age, ETag и Last-Modified.
- Сопоставьте release ID внутри HTML и JS.
- Проверьте manifest сборки на отсутствующие файлы.
- Обойдите CDN прямым запросом к каждому origin с правильным Host.
Как публиковать frontend без смешения версий
Хешированные assets являются неизменяемыми и загружаются первыми. HTML с коротким кешем публикуется последним и переключает пользователей на новый набор, а несколько предыдущих наборов сохраняются достаточно долго.
Надёжная реализация хранит бизнес-состояние явно и не пытается восстановить его только по экрану, случайному логу или последнему webhook. У каждого значимого действия должен быть стабильный идентификатор, понятный владелец, версия правила и проверяемый переход статуса. Повторная доставка одного события не должна создавать второе действие, а запоздавшее событие не должно возвращать объект в невозможное состояние.
- Имя JS и CSS содержит content hash, а Cache-Control допускает долгий immutable-кеш.
- HTML и manifest имеют короткий TTL и управляемую revalidation.
- Релиз загружается в отдельный каталог с полным списком файлов.
- Проверка существования всех ссылок выполняется до переключения.
- Service worker версионирует cache и удаляет старые данные только после активации нового набора.
Как исправить проблему
Исправление разделите на небольшие обратимые изменения. Сначала устраните подтверждённую первопричину, затем восстановите повреждённые данные отдельной контролируемой процедурой. Не смешивайте выпуск нового кода и массовую коррекцию истории в одном непрозрачном запуске: для них нужны разные отчёты, критерии успеха и планы отката.
Если затронуты существующие записи, сначала сформируйте dry-run: список объектов, старое значение, предлагаемое новое значение и основание для изменения. Обновление должно быть идемпотентным, ограниченным точной выборкой и сопровождаться аудитом. Для финансовых данных, прав доступа и персональной информации предпочтительны компенсирующие записи, а не переписывание истории.
- Включите content hashing и перестаньте перезаписывать один app.js.
- Измените порядок деплоя: assets, проверка, затем HTML.
- Сохраняйте предыдущие chunks на период жизни старого HTML и открытых вкладок.
- Синхронизируйте origin через атомарную ссылку current или версионированный bucket.
- Исправьте стратегию service worker и добавьте понятное обновление приложения.
Безопасный порядок внедрения
- Сохраните конфигурацию, связанные записи и необходимые журналы; отдельно проверьте, что резервную копию действительно можно восстановить.
- Воспроизведите проблему на тестовом объекте и сохраните результат до изменения, чтобы после выпуска сравнить одинаковые сценарии.
- Внесите минимальное изменение под системой контроля версий, опишите причину, ожидаемый эффект, ограничения и точный способ отката.
- Прогоните нормальный сценарий, ошибочный ввод, повтор одного запроса, два параллельных запроса и временную недоступность зависимости.
- Выпустите изменение на ограниченную долю трафика или один процесс, если архитектура это позволяет, и сравните метрики со старой версией.
- Только после стабильного наблюдения выполните контролируемое исправление исторических данных и сохраните итоговый отчёт.
Как проверить результат
Один успешный пример недостаточен. Проверьте основную операцию повторно, крайние значения, одновременные действия, перезапуск процесса и восстановление после краткого сбоя сети. Результат подтверждайте не только экраном пользователя, но и состоянием базы, очереди, внешнего сервиса и журналом аудита. Особое внимание уделите тому, что система делает при повторной доставке уже обработанного события.
Критерии приёмки сформулируйте до выпуска. Каждый пункт должен давать однозначный ответ «выполнено» или «не выполнено», а не субъективную оценку. Если тест невозможно повторить автоматически, оставьте короткий регрессионный чек-лист с тестовыми данными и ожидаемыми статусами.
- Каждый asset из нового HTML существует на всех origin до переключения.
- Старый HTML продолжает загружать свои chunks после нового релиза.
- Запрос одного хешированного URL даёт одинаковую checksum во всех регионах.
- Открытая старая вкладка не падает при ленивой загрузке chunk.
- Service worker обновляется без бесконечного цикла reload и смешения cache.
Типичные ошибки при исправлении
- Делать purge everything при каждом деплое вместо корректного версионирования.
- Назначать immutable для index.html.
- Удалять предыдущий каталог сразу после публикации.
- Диагностировать только из одного региона.
- Забывать, что service worker является отдельным уровнем кеша.
Опаснее всего исправление, которое убирает заметный симптом ценой отключения проверки, ослабления прав или потери аудита. Такое изменение может сделать интерфейс «зелёным», но увеличить ущерб при следующем сбое. Если временный обход всё же необходим, ограничьте его срок, пользователей и область действия, добавьте мониторинг и заранее назначьте дату удаления.
Как предотвратить повторение
Профилактика строится вокруг явных контрактов, идемпотентных обработчиков, наблюдаемости и повторяемых релизов. Система должна не только корректно работать сейчас, но и быстро показывать нарушение правила после обновления, роста нагрузки или отказа интеграции. Ошибку, которая уже привела к потере времени, денег, данных или заявок, стоит закрепить автоматическим тестом.
Настройте мониторинг полного пользовательского пути, а не только доступности отдельных серверов. Техническая метрика должна быть связана с бизнес-результатом: заказ завершён, доступ выдан, файл восстановлен, событие обработано один раз, данные изолированы. Порог оповещения задавайте по нормальному профилю нагрузки и проверяйте, что уведомление содержит достаточно контекста для первого решения.
- Ошибки ChunkLoadError и 404 по хешированным assets.
- Количество разных checksum для одного URL.
- Возраст HTML на CDN и доля revalidated-ответов.
- Расхождение release ID между HTML, manifest и runtime.
- Ошибки активации service worker после релиза.
Что подготовить для технического разбора
- Короткое описание ожидаемого и фактического поведения с точной последовательностью действий.
- Время возникновения, идентификатор тестового объекта и версии всех затронутых компонентов.
- Обезличенные фрагменты журналов до и после ошибки с единым correlation ID.
- Список последних изменений, результаты уже выполненных проверок и условия, при которых симптом исчезает.
- Безопасный доступ к тестовой среде либо минимальный пример, не содержащий паролей, токенов и персональных данных.
Частые вопросы
Нужно ли очищать весь CDN?
При правильных хешированных именах assets не нужно; обычно достаточно управлять коротким кешем HTML.
Почему ошибка только у части пользователей?
Разные POP, origin, открытые вкладки и service worker могут хранить разные поколения.
Сколько хранить старые chunks?
Не меньше разумного времени жизни старого HTML и пользовательской сессии, с запасом на кеши и открытые вкладки.
Когда нужна помощь специалиста
Если CDN смешивает версии JavaScript, я могу проверить заголовки, origin, manifest, порядок деплоя и service worker, затем настроить атомарные релизы. Для оценки нужны проблемный URL, response headers и список файлов одной сборки.