Тестирование кэширования: как проверять Redis, CDN и браузерный кэш, чтобы пользователь видел актуальные данные

Тестирование кэширования: как проверять Redis, CDN и браузерный кэш, чтобы пользователь видел актуальные данные

Коротко:

  • Кэш работает на трёх уровнях: серверный (Redis, Memcached), сетевой (CDN) и клиентский (браузер). Баг может жить на любом из них.
  • Самый частый симптом - пользователь видит устаревшие данные после обновления. Причина почти всегда в том, что инвалидация не сработала или сработала не там.
  • Для Redis проверяют TTL, попадание в кэш (cache hit/miss), поведение при гонке и корректность ключей.
  • CDN-кэш проверяют через заголовки ответа: X-Cache, Age, Cache-Control, Vary.
  • Браузерный кэш тестируют отдельно от серверного - через DevTools, жёсткую перезагрузку и режим инкогнито.
  • Инвалидацию нужно проверять явно: обновил данные - убедился, что старый кэш не отдаётся.

Почему кэш ломает QA-сценарии незаметно

Представьте: разработчик поправил цену товара в базе данных. Тест проходит - API возвращает новое значение. Но пользователь в браузере видит старую цену ещё час. Никакой ошибки нет, статус 200, данные корректные - просто не те.

Это классический кэш-баг. Он не падает с ошибкой, не пишет в лог и не воспроизводится на свежем окружении. Именно поэтому его так легко пропустить при обычном тестировании.

Кэш ускоряет работу продукта, снижает нагрузку на базу и уменьшает задержки. Но за это приходится платить: между реальным состоянием данных и тем, что видит пользователь, появляется разрыв. Задача QA - убедиться, что этот разрыв управляемый и не ломает пользовательские сценарии.

В этой статье разберём, как строить тест-кейсы для каждого уровня, какие инструменты использовать и где чаще всего прячутся проблемы.

Три уровня кэша: что где живёт

Прежде чем писать тесты, важно понять архитектуру. Запрос пользователя проходит через несколько слоёв, и на каждом может быть свой кэш.

УровеньГде находитсяТипичные решенияЧто кэшируется
СерверныйВнутри инфраструктурыRedis, MemcachedРезультаты запросов к БД, сессии, вычисленные данные
Сетевой (CDN)Между сервером и пользователемCloudflare, Fastly, AWS CloudFrontСтатика, HTML-страницы, API-ответы
КлиентскийВ браузере пользователяВстроенный механизм браузераJS, CSS, изображения, API-ответы с нужными заголовками

Баг может жить на любом из этих уровней - или сразу на нескольких. Поэтому тестировать их нужно по отдельности, иначе непонятно, где именно что-то пошло не так.

Тестирование Redis: что проверять на серверном уровне

Redis - самое распространённое решение для серверного кэша. Данные хранятся в памяти с заданным временем жизни (TTL). Когда TTL истекает или запись явно удаляется, следующий запрос идёт в базу данных и обновляет кэш.

Основные сценарии

Cache hit и cache miss. Первый запрос к ресурсу должен идти в БД (miss), второй - возвращаться из Redis (hit). Проверяется через логи приложения или метрики Redis: команда INFO stats показывает счётчики keyspace_hits и keyspace_misses.

Корректность TTL. Запись должна исчезать из кэша ровно через заданное время. Проверяем командой TTL key_name сразу после записи и через несколько секунд. Если TTL не убывает или ключ не исчезает после истечения - это баг.

Инвалидация после обновления. Если пользователь изменил данные (например, обновил профиль), старая запись в Redis должна быть удалена или перезаписана. Сценарий: обновить данные через API, затем сразу запросить их снова и убедиться, что пришли новые значения, а не закэшированные.

Поведение при отсутствии ключа. Если ключ не найден в Redis, приложение должно корректно обратиться к БД и не вернуть пустой ответ или ошибку. Это особенно важно после деплоя на чистое окружение.

Гонка при обновлении (race condition)

Один из самых неприятных сценариев: два параллельных запроса одновременно обнаруживают, что ключ истёк, и оба идут в БД. Это называется cache stampede. Для QA важно проверить, что приложение использует механизм блокировки (например, mutex lock) и не создаёт дублирующих запросов к базе при одновременном промахе.

Воспроизвести это вручную сложно, но можно: сбросить ключ в Redis вручную командой DEL key_name и одновременно отправить несколько запросов через Postman Runner или k6. В логах не должно быть N одновременных запросов к БД по одному ключу.

Инструменты для тестирования Redis

  • redis-cli - стандартный клиент для ручных проверок. Команды GET, TTL, DEL, KEYS, INFO stats.
  • RedisInsight - GUI-клиент от Redis Ltd, удобен для визуального просмотра ключей и мониторинга.
  • Логи приложения - большинство фреймворков пишут, был ли hit или miss. Настройте уровень логирования для кэш-операций на тестовом окружении.

Проверка CDN-кэша: заголовки как главный инструмент

CDN кэширует ответы на своих серверах по всему миру. Когда пользователь запрашивает ресурс, CDN отдаёт его из ближайшей точки присутствия, не обращаясь к origin-серверу. Это быстро, но создаёт риск: если инвалидация не настроена, пользователь получает устаревший контент.

Что читать в заголовках ответа

Весь смысл проверки CDN - в HTTP-заголовках. Открываем DevTools (вкладка Network) или используем curl с флагом -I и смотрим на ответ.

ЗаголовокЧто означаетНа что обратить внимание
X-Cache: HITОтвет отдан из CDN-кэшаУбедиться, что данные актуальны
X-Cache: MISSCDN обратился к originНормально для первого запроса
Age: 3600Сколько секунд ответ хранится в CDNЕсли Age большой - данные могут быть старыми
Cache-Control: max-age=86400Максимальное время храненияПроверить, что значение соответствует требованиям
Vary: Accept-EncodingCDN хранит разные версии по заголовкуЕсли Vary: Cookie - персональные данные могут кэшироваться

Тест-кейсы для CDN

Первый запрос - MISS, повторный - HIT. Запросить ресурс дважды. Первый раз заголовок X-Cache должен быть MISS (или BYPASS), второй - HIT. Если оба раза MISS, кэширование не работает.

Инвалидация после обновления контента. Обновить файл или данные на origin, затем запросить ресурс. Если CDN не получил сигнал на инвалидацию, он отдаст старую версию. Проверить можно по заголовку Age: если он больше нуля сразу после обновления - кэш не сбросился.

Персонализированные ответы не кэшируются. Страницы с данными конкретного пользователя (корзина, профиль, баланс) не должны попадать в CDN-кэш. Проверяем: авторизоваться под двумя разными пользователями и убедиться, что каждый видит свои данные, а не чужие из кэша. В заголовке должно быть Cache-Control: private или no-store.

Заголовок Vary не ломает кэш для анонимных запросов. Если сервер возвращает Vary: Cookie, CDN будет хранить отдельную версию для каждого набора cookies. Это правильно для авторизованных запросов, но может полностью отключить кэширование для публичных страниц.

Cache-Control тестирование: ключевые директивы

Директива Cache-Control управляет поведением на всех уровнях сразу. Неправильное значение - и либо всё кэшируется слишком долго, либо не кэшируется вообще.

  • no-store - не кэшировать нигде. Подходит для чувствительных данных.
  • no-cache - кэшировать, но каждый раз проверять актуальность у сервера (через ETag или Last-Modified).
  • max-age=N - хранить N секунд без проверки.
  • s-maxage=N - то же, но только для CDN и прокси. Браузер игнорирует.
  • private - только для браузера, CDN не должен кэшировать.
  • stale-while-revalidate=N - отдавать устаревший ответ, пока фоном обновляется. Важно проверить, что обновление действительно происходит.

Частая ошибка: разработчики ставят no-cache, думая, что это отключает кэширование. На самом деле no-cache разрешает хранить ответ, но требует его ревалидации. Если нужно полностью запретить хранение - нужен no-store.

Браузерный кэш QA: как не запутаться в слоях

Браузер хранит ресурсы локально и решает, когда идти за новой версией, основываясь на заголовках ответа. Для QA это создаёт отдельную проблему: тест может проходить на свежей вкладке и падать у пользователя, у которого ресурс закэширован с прошлой недели.

Как изолировать браузерный кэш при тестировании

Прежде всего - не путать жёсткую перезагрузку (Ctrl+Shift+R) с обычной (F5). При жёсткой перезагрузке браузер игнорирует кэш и запрашивает всё заново. При обычной - использует кэшированные ресурсы, если они ещё актуальны.

Для чистого теста используйте режим инкогнито или отдельный профиль браузера. Это исключает влияние накопленного кэша на результат.

В DevTools (вкладка Network) есть чекбокс

Как выстроить полный цикл проверки инвалидации

Инвалидация - самое уязвимое место в работе с кэшем. Данные обновились, но пользователь продолжает видеть старую версию. Именно здесь чаще всего возникают баги, которые сложно воспроизвести и ещё сложнее объяснить бизнесу.

Проблема в том, что инвалидацию часто проверяют случайно: обновили данные, посмотрели в браузере, увидели новое значение и решили, что всё работает. Но это не тест - это наблюдение. Полноценная проверка требует явного контроля каждого уровня.

Последовательность шагов для проверки инвалидации

Шаг 1. Зафиксируйте исходное состояние. Запросите данные и убедитесь, что они попали в кэш. Для Redis - проверьте наличие ключа через GET. Для CDN - убедитесь, что заголовок X-Cache возвращает HIT. Для браузера - откройте DevTools и убедитесь, что ресурс отдаётся из disk cache или memory cache.

Шаг 2. Выполните изменение через API или интерфейс. Обновите данные так, как это делает реальный пользователь или администратор. Не меняйте данные напрямую в базе - это обходит логику приложения и может не запустить инвалидацию.

Шаг 3. Немедленно проверьте каждый уровень отдельно. Сначала убедитесь, что Redis вернул новое значение. Затем проверьте CDN-ответ через curl или DevTools. Наконец, откройте страницу в браузере без жёсткой перезагрузки - именно так её увидит обычный пользователь.

Шаг 4. Проверьте граничный случай: обновление прямо перед истечением TTL. Если TTL истекает через 5 секунд, а данные обновились 4 секунды назад - что произойдёт? Некоторые реализации перезаписывают ключ с новым TTL, другие ждут естественного истечения. Оба варианта могут быть корректными, но поведение должно быть предсказуемым и задокументированным.

Хороший признак: если после обновления данных через API вы видите новое значение на всех трёх уровнях без жёсткой перезагрузки и без ручного сброса кэша - инвалидация настроена правильно. Если хотя бы один уровень отстаёт - это дефект, даже если он кажется незначительным.

Сценарии, которые чаще всего пропускают

Стандартные тест-кейсы покрывают happy path: данные обновились, кэш сбросился, пользователь видит актуальное. Но реальные баги живут в нестандартных ситуациях.

Кэш при ошибках сервера

Что происходит, если origin-сервер вернул 500? Некоторые CDN по умолчанию кэшируют ошибочные ответы. Пользователь будет видеть страницу с ошибкой даже после того, как сервер восстановился. Проверяется просто: временно сломайте endpoint на тестовом окружении, запросите ресурс через CDN, восстановите endpoint и проверьте, когда CDN начнёт отдавать корректный ответ.

Правильное поведение: ответы с кодами 5xx не должны кэшироваться. Это контролируется директивой Cache-Control: no-store на стороне сервера или настройками CDN.

Кэш при редиректах

Редиректы (301, 302) тоже могут кэшироваться. Постоянный редирект 301 браузер запоминает навсегда - пока не очистить кэш вручную. Если URL изменился, а старый редирект закэширован у пользователей, они будут попадать не туда. Проверяйте заголовки редиректов так же, как заголовки обычных ответов.

Кэш при A/B-тестировании

Если продукт использует A/B-тесты или feature flags, кэш может сломать эксперимент. Пользователь из группы A получает закэшированный ответ группы B. Или наоборот. Проверьте, что ответы для разных групп либо не кэшируются, либо CDN корректно использует заголовок Vary для разделения версий.

Кэш после деплоя

После каждого деплоя статические файлы (JS, CSS) должны обновляться у пользователей. Если файлы отдаются с длинным max-age без версионирования в URL, пользователь будет запускать старый JavaScript с новым API. Проверьте, что при деплое URL статики меняется (например, через хэш в имени файла) или CDN-кэш сбрасывается принудительно.

Практический чеклист для тестирования кэша

Ниже собраны проверки, которые стоит включить в тест-план для любого проекта, где есть хотя бы один уровень кэширования.

ОбластьЧто проверитьИнструмент
RedisTTL убывает и ключ исчезает вовремяredis-cli, TTL команда
RedisПосле обновления данных старый ключ удалёнredis-cli, логи приложения
RedisПри cache miss приложение не возвращает ошибкуPostman, логи
CDNПервый запрос MISS, повторный HITcurl -I, DevTools Network
CDNПерсональные страницы не кэшируютсяДва разных аккаунта, DevTools
CDNПосле обновления контента Age сбрасываетсяcurl -I, заголовок Age
CDNОтветы 5xx не кэшируютсяВременная поломка endpoint
БраузерБез жёсткой перезагрузки пользователь видит актуальные данныеDevTools, обычная навигация
БраузерПосле деплоя статика обновляется без очистки кэшаDevTools, версионирование URL
БраузерРедиректы не кэшируются дольше нужногоDevTools, заголовки ответа

Как документировать баги, связанные с кэшем

Баги кэша сложно воспроизводить, потому что они зависят от состояния системы в конкретный момент. Разработчик открывает задачу, делает запрос - и видит актуальные данные, потому что кэш уже сбросился. Баг кажется нерепродуцируемым.

Чтобы этого избежать, в баг-репорте нужно фиксировать не только шаги, но и состояние кэша в момент воспроизведения. Укажите значение заголовка Age, результат команды TTL для конкретного ключа, время между обновлением данных и наблюдаемым эффектом. Приложите скриншот вкладки Network с заголовками ответа.

Полезно также указать, на каком уровне был обнаружен баг. Если проблема в Redis - разработчику нужно смотреть в логику инвалидации на сервере. Если в CDN - в настройки правил кэширования. Если в браузере - в заголовки, которые возвращает API. Это сразу сокращает время на диагностику.

Ещё один важный момент: если баг воспроизводится только при определённом TTL или только в первые N секунд после обновления - укажите это явно. Временные зависимости в кэш-багах часто оказываются ключом к пониманию причины.

Кэш и авторизация: где чаще всего утекают чужие данные

Авторизованные запросы и кэш плохо сочетаются, если настройки выставлены небрежно. Самый опасный сценарий: пользователь А запрашивает свою страницу профиля, CDN кэширует ответ, пользователь Б получает тот же закэшированный ответ с чужими данными. Это не теоретический риск - подобные инциденты случались у крупных сервисов.

Проверяйте этот сценарий явно. Авторизуйтесь под первым пользователем, откройте персональную страницу (профиль, корзина, история заказов). Затем откройте тот же URL в другом браузере или в режиме инкогнито под вторым пользователем. Каждый должен видеть только свои данные.

Параллельно смотрите на заголовки ответа. Для любого персонализированного контента должен быть Cache-Control: private или Cache-Control: no-store. Если видите public или вообще нет директивы - это потенциальная уязвимость, даже если CDN сейчас ведёт себя корректно.

Отдельно проверьте поведение при выходе из аккаунта. После logout сессионные данные должны исчезать, а не оставаться в кэше. Нажмите кнопку выхода, затем вернитесь на страницу профиля кнопкой «назад» в браузере. Если страница открылась с данными предыдущего пользователя из кэша - это баг.

Инструменты для проверки заголовков: что использовать кроме DevTools

DevTools удобны для разовых проверок, но при системном тестировании нужны инструменты, которые позволяют быстро сравнивать заголовки, сохранять результаты и встраивать проверки в пайплайн.

ИнструментДля чего удобенПример использования
curl -IБыстрая проверка заголовков из терминалаcurl -I https://example.com/api/product/1
PostmanПроверка заголовков с тест-скриптамиpm.response.headers.get('X-Cache') === 'HIT'
k6Нагрузочная проверка поведения кэша под нагрузкойПараллельные запросы для проверки cache stampede
RedisInsightВизуальный просмотр ключей и TTL в RedisМониторинг ключей в реальном времени при тестировании
Wireshark / mitmproxyПерехват трафика для глубокого анализаПроверка, что браузер не отправляет лишние запросы

Для автоматизации проверок заголовков в CI хорошо подходит Postman с Newman или простые скрипты на curl. Например, можно добавить шаг в пайплайн, который после деплоя проверяет, что статические файлы возвращают X-Cache: HIT при повторном запросе и что API профиля возвращает Cache-Control: private.

Как тестировать stale-while-revalidate и условные запросы

Два механизма, которые часто остаются без внимания при проверке: stale-while-revalidate и условные запросы через ETag или Last-Modified.

stale-while-revalidate позволяет браузеру или CDN отдавать устаревший ответ, пока фоном запрашивается свежая версия. Это ускоряет загрузку, но создаёт окно, в котором пользователь видит старые данные. Для QA важно проверить два момента: что фоновое обновление действительно происходит (следующий запрос уже отдаёт свежие данные) и что окно устаревания не превышает допустимого для бизнеса времени.

Условные запросы работают иначе. Браузер хранит ETag или дату Last-Modified и при следующем запросе отправляет их серверу. Если данные не изменились, сервер возвращает 304 Not Modified без тела ответа - браузер использует закэшированную версию. Если изменились - возвращает 200 с новыми данными.

Как проверить условные запросы: откройте DevTools, вкладку Network. Загрузите страницу, затем обновите её обычным F5 (не жёсткой перезагрузкой). Для статических ресурсов должны появиться запросы со статусом 304. Если все запросы возвращают 200 - сервер не поддерживает условные запросы и каждый раз отдаёт полный ответ. Это не баг безопасности, но лишняя нагрузка на сервер и медленная загрузка для пользователя.

Для API-ответов условные запросы используются реже, но встречаются. Если API возвращает заголовок ETag, проверьте: отправьте запрос с заголовком If-None-Match и значением ETag из предыдущего ответа. Если данные не менялись, должен прийти 304. Если данные обновились - 200 с новым телом и новым ETag.

Когда кэш-баг критичен, а когда допустим

Не каждое расхождение между реальным состоянием данных и тем, что видит пользователь, является критическим дефектом. Важно уметь оценивать риск, чтобы правильно расставлять приоритеты.

Критические ситуации: пользователь видит чужие персональные данные, цена в корзине отличается от реальной, статус заказа или платежа устарел, форма отправляется с закэшированным CSRF-токеном, который уже недействителен.

Допустимые задержки: счётчик просмотров статьи обновляется с задержкой в несколько минут, список рекомендаций обновляется раз в час, публичная страница с описанием продукта показывает версию 5-минутной давности.

Граница между критичным и допустимым определяется бизнес-требованиями, а не техническими предпочтениями. Задача QA - выявить расхождение и передать его команде с достаточным контекстом, чтобы можно было принять осознанное решение. Иногда правильный ответ - не «починить кэш», а «уточнить требования к актуальности данных» и зафиксировать их в документации.

Кэш в микросервисной архитектуре: дополнительные сложности

В монолитном приложении кэш обычно один, и понять, где произошёл сбой, относительно просто. В микросервисной архитектуре каждый сервис может иметь собственный Redis, собственные правила TTL и собственную логику инвалидации. Когда данные проходят через несколько сервисов, устаревшая запись может застрять на любом из них.

Типичный пример: сервис пользователей обновил email, но сервис уведомлений продолжает брать адрес из своего локального кэша. Письмо уходит на старый адрес. Ни один из сервисов не вернул ошибку, всё выглядит корректно, но пользователь не получил сообщение.

Для QA это означает, что при тестировании сценариев, затрагивающих несколько сервисов, нужно явно проверять каждый из них. Недостаточно убедиться, что данные обновились в одном месте. Составьте карту: какие сервисы читают эти данные и у каждого ли настроена инвалидация при изменении.

Ещё одна точка риска в микросервисах: событийная инвалидация через очередь сообщений (Kafka, RabbitMQ). Сервис A обновляет данные и публикует событие. Сервис B подписан на это событие и должен сбросить свой кэш. Если событие потерялось или обработалось с задержкой, кэш сервиса B останется устаревшим. Проверяйте не только факт обновления, но и время между публикацией события и фактическим сбросом кэша в зависимых сервисах.

СценарийРискКак проверить
Обновление данных в сервисе A, чтение в сервисе BСервис B отдаёт устаревшие данные из своего кэшаПроверить кэш каждого сервиса отдельно после изменения
Инвалидация через очередь сообщенийСобытие потерялось или задержалосьИзмерить задержку между обновлением и сбросом кэша
Несколько экземпляров одного сервисаОдин экземпляр сбросил кэш, другой нетЗапросить данные несколько раз подряд, сравнить ответы
Общий Redis для нескольких сервисовКонфликт ключей при одинаковых именахПроверить пространства имён ключей в redis-cli

Тестирование кэша на разных окружениях

Одна из частых проблем: кэш настроен по-разному на dev, staging и production. На dev кэширование отключено или TTL минимальный, на staging включено, но с другими правилами, на production работает полноценно. В итоге баги, связанные с кэшем, всплывают только в продакшне.

Чтобы этого избежать, QA должен знать конфигурацию кэша на каждом окружении. Запросите у команды разработки документ или хотя бы краткое описание: какой TTL установлен для основных типов данных, включена ли CDN на staging, совпадают ли правила инвалидации с продакшном.