Коротко:
- Кэш работает на трёх уровнях: серверный (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: MISS | CDN обратился к origin | Нормально для первого запроса |
Age: 3600 | Сколько секунд ответ хранится в CDN | Если Age большой - данные могут быть старыми |
Cache-Control: max-age=86400 | Максимальное время хранения | Проверить, что значение соответствует требованиям |
Vary: Accept-Encoding | CDN хранит разные версии по заголовку | Если 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. Наконец, откройте страницу в браузере без жёсткой перезагрузки - именно так её увидит обычный пользователь.
Вакансии для QA-инженеров
Шаг 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-кэш сбрасывается принудительно.
Практический чеклист для тестирования кэша
Ниже собраны проверки, которые стоит включить в тест-план для любого проекта, где есть хотя бы один уровень кэширования.
| Область | Что проверить | Инструмент |
|---|---|---|
| Redis | TTL убывает и ключ исчезает вовремя | redis-cli, TTL команда |
| Redis | После обновления данных старый ключ удалён | redis-cli, логи приложения |
| Redis | При cache miss приложение не возвращает ошибку | Postman, логи |
| CDN | Первый запрос MISS, повторный HIT | curl -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, совпадают ли правила инвалидации с продакшном.