FAILURE DRILL

All playbooks

Failure drill

Redis Failure Drill: Stop a Cache-Miss Stampede Before It Hits PostgreSQL

A safe drill: disable Redis in a bounded way, watch reads flood PostgreSQL, and verify shedding, warmup, and cache recovery.

The full text is in Russian.

RedisPostgreSQLHighloadObservabilityDistributed Systems

Это гипотетическое учение для сервиса с cache-aside: Redis ускоряет горячее чтение, а PostgreSQL остаётся источником истины. Сбой кэша здесь опасен не сам по себе. Опасна положительная обратная связь: промахи переводят трафик в базу, очередь растит задержку, клиенты повторяют запросы, а восстановившийся Redis получает синхронный прогрев тех же ключей.

Учение должно ответить на один вопрос: умеет ли система потерять кэш, не потеряв базу. Оно не воспроизводит внутренний кейс и не использует вымышленные пороги. Все границы ниже заполняются значениями из нагрузочного испытания конкретной системы.

Цель и проверяемая гипотеза

Проверяем путь горячего чтения целиком: приложение, Redis, загрузчик из PostgreSQL, пул соединений и клиентские повторы. Гипотеза считается рабочей, если при недоступном или холодном Redis:

  • скорость и параллелизм обращений к PostgreSQL ограничены независимо от мгновенного числа входящих запросов;
  • один популярный ключ не запускает параллельную загрузку на каждом запросе;
  • база сохраняет полезную пропускную способность и не уходит в неограниченную очередь;
  • допустимые чтения получают явно устаревший либо упрощённый результат, остальные — быстрый контролируемый отказ;
  • возврат Redis не создаёт вторую волну нагрузки.

До учения нужно определить, чем является кэш. Если PostgreSQL выдерживает весь пользовательский поток без Redis, это кэш задержки. Если не выдерживает, Redis уже стал кэшем ёмкости и фактической зависимостью доступности. Google SRE рекомендует различать эти случаи: без такого ответа автоматический переход к базе способен превратить локальный отказ в каскадную перегрузку.

Границы безопасности

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

Обязательные ограничения:

  • инъекция действует на выбранные экземпляры приложения или тестовый префикс ключей, а не на общий Redis-кластер;
  • FLUSHALL, массовое удаление рабочих ключей и перезапуск общего кластера не используются;
  • сценарий не касается записей, платежных решений, прав доступа, лимитов и других путей, где устаревший результат опасен;
  • размер пула PostgreSQL, предел параллельных резервных загрузок и отсечение входящего трафика фиксируются до старта;
  • стоп-условия заданы по насыщению базы, ожиданию соединения, хвосту задержки, ошибкам и неожиданному влиянию на соседние операции;
  • у ведущего учения есть одна команда снятия инъекции, а у наблюдателя — право остановить сценарий без обсуждения.

Порог остановки берут ниже известной границы нелинейной деградации. Если такая граница неизвестна, сначала проводят отдельное нагрузочное испытание: учение не должно одновременно искать предел базы и проверять процедуру аварии.

Подготовка

Перед стартом прогревают выделенный набор ключей и сохраняют исходный профиль: частоту запросов, долю попаданий и промахов, задержку Redis, число загрузок из PostgreSQL на логический ключ, состояние пула, запросы и ожидания базы. В PostgreSQL нужны не только CPU и соединения, но и wait_event, частота критичных запросов, чтение страниц и задержка ввода-вывода; соответствующие представления собраны в официальном разделе мониторинга.

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

На стороне приложения заранее должны существовать управляемые защитные механизмы:

  • короткий ограниченный тайм-аут Redis и размыкатель цепи с редкими пробными запросами;
  • локальное объединение одновременных загрузок одного ключа, например через singleflight;
  • общий ограничитель параллельных резервных загрузок из PostgreSQL;
  • разрешённая предметной областью выдача устаревшего значения либо дешёвый деградированный ответ;
  • случайный разброс TTL и ограниченный прогрев после восстановления.

singleflight действует только внутри одного процесса. Аренда через SET NX PX способна уменьшить дубли между экземплярами при холодном Redis; такой приём показан в официальном примере cache-aside. Но при полном отказе Redis эта координация тоже недоступна, а истёкшая аренда допускает второго загрузчика. Поэтому блокировка кэша не является последней защитой PostgreSQL и не охраняет бизнес-инварианты.

Инъекция и ход по времени

T−подготовка: подтвердить исходное состояние

Ведущий фиксирует версию сервиса и конфигурации, проверяет стоп-условия и выполняет контрольное чтение. Наблюдатель подтверждает, что Redis прогрет, PostgreSQL находится в безопасном диапазоне, а синтетический поток не смешан с пользовательскими метриками.

T+0: сделать Redis недоступным для выделенного сегмента

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

Ожидаемый порядок: растут ошибки Redis, затем срабатывает размыкатель; доля попаданий выделенного сегмента падает; загрузки из базы увеличиваются, но упираются в общий предел. Лишние запросы должны получить устаревший ответ, деградацию или ранний отказ до захвата соединения PostgreSQL.

T+1: создать горячий промах

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

Если защита работает, число загрузчиков заметно меньше числа ожидающих запросов. Если каждый ожидающий после локального тайм-аута самостоятельно идёт в базу, singleflight лишь отложил лавину. Ожидание должно завершаться деградацией или отказом, а не обходом общего лимита.

T+2: выполнить действия дежурного

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

  1. запрещает дополнительные повторы Redis и проверяет быстрый отказ размыкателя;
  2. снижает допуск резервных загрузок и не увеличивает пул соединений;
  3. включает разрешённый устаревший или упрощённый ответ для некритичных чтений;
  4. отсекает низкоприоритетный трафик, если база приближается к стоп-условию;
  5. сохраняет диагностическую отметку времени и причины каждого изменения.

Масштабировать приложение вслепую нельзя: новые экземпляры принесут новые пулы, локальные singleflight-группы и прогрев, усилив нагрузку на PostgreSQL.

T+3: вернуть Redis и прогреть управляемо

Инъекцию снимают, но деградированный режим не выключают сразу. Сначала подтверждают устойчивую доступность Redis и отсутствие вытеснения ключей из-за памяти. Затем прогревают ограниченную очередь популярных тестовых ключей с разбросом TTL. Долю обычного трафика увеличивают ступенчато, наблюдая не только долю попаданий, но и фактическую скорость загрузок из базы.

Ожидаемые сигналы и разбор

Ключевая цепочка должна читаться на одной временной шкале:

  • Redis: ошибки и задержка команд, состояние соединений, попадания и промахи, память, вытеснения и истечения ключей;
  • приложение: открытие размыкателя, число лидеров и ожидающих объединённого запроса, отказы ограничителя, устаревшие ответы, повторы и запросы в полёте;
  • PostgreSQL: ожидание пула, активные запросы, задержка и частота загрузчика, блокировки, I/O и влияние на параллельные записи;
  • пользовательский слой: успешные, деградированные и отклонённые результаты раздельно, а не один общий процент ошибок.

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

Восстановление, критерии успеха и сброс

Сценарий завершён только после возврата Redis, разгрузки пула PostgreSQL и стабилизации хвостов задержки. Успех означает, что ни одно стоп-условие не пересечено, резервная загрузка осталась ограниченной, горячий ключ не породил запрос на каждого читателя, а прогрев не вызвал второй пик. Дежурный должен по сигналам отличить отказ Redis от медленного PostgreSQL и выполнить действия в согласованном порядке.

При сбросе удаляют только синтетические ключи, возвращают выделенный сегмент к обычному маршруту, снимают временные переключатели и останавливают генератор. Затем сверяют конфигурацию с исходной, проверяют отсутствие фонового прогрева и сохраняют графики, хронологию, сработавшие стоп-условия и конкретные задачи с владельцами.

Когда учение не проводить

Учение откладывают во время инцидента, миграции схемы, резервного копирования под нагрузкой или при исчерпанном запасе PostgreSQL. Его нельзя запускать, если выделенный сегмент нельзя изолировать, панели не отделяют резервные загрузки от обычных запросов, неизвестно, какие данные разрешено отдавать устаревшими, или Redis хранит не только копии, но и авторитетные сессии, блокировки либо лимиты. В последнем случае это другой отказ с другими инвариантами, и сценарий «просто прочитать из базы» описывает систему неверно.