Кэширование в масштабе
Кэш ставят, чтобы снять нагрузку с базы и внешних API: вместо дорогого запроса к источнику истины сервис отдаёт уже посчитанный ответ из памяти или из Redis. Пока трафик небольшой, кэш кажется бесплатным ускорением. В масштабе он становится отдельной распределённой системой со своими отказами — рассинхроном между репликами, устаревшими данными и всплесками к базе ровно в тот момент, когда ключ истёк.
Главная ловушка темы — относиться к кэшу как к словарю в памяти, а не как к слою с собственной семантикой согласованности. Локальный L1-кэш быстрый, но не общий: один экземпляр обновил данные, остальные ещё отдают старое. Запись через кэш с асинхронным сбросом в базу теряет данные при падении процесса. Ключ без TTL и без инвалидации отдаёт устаревшее бесконечно. А самый коварный отказ — cache stampede: горячий ключ истёк, сотни параллельных запросов промахнулись разом и ударили в базу одновременно. Эта тема разбирает кэширование по слоям — от иерархии уровней до метрик, по которым его настраивают.
Карта темы
- Уровни кэша — иерархия от браузера и CDN через L1 в процессе к L2 в Redis и базе как источнику истины, с разменом задержки, hit-rate и согласованности между репликами.
- Паттерны кэширования — cache-aside, read-through, write-through и write-back и их компромиссы между согласованностью и скоростью записи.
- Инвалидация кэша — TTL, событийная и тег-based инвалидация и почему «протухание» — одна из двух по-настоящему трудных задач.
- Негативное кэширование — кэширование отсутствия данных с коротким TTL против penetration, без кэширования временных ошибок.
- Cache stampede — thundering herd при истечении горячего ключа и его гашение через coalescing, lease, ранний refresh и разнесённые TTL.
- Наблюдаемость кэша — hit/miss/eviction rate, задержка и память Redis: кэш нельзя настроить, не измеряя.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Держать только L1-кэш в памяти процесса как «общий» | Каждый экземпляр видит своё — рассинхрон и устаревшие данные между репликами |
| Считать любой слой кэша бесплатным ускорением | Кэш — это распределённая система со своими отказами и согласованностью |
| Писать в кэш с асинхронным сбросом в базу без оглядки | Write-back теряет данные при падении процесса до сброса |
| Жить только на TTL для меняющихся данных | Клиент получает устаревшее всё время до истечения ключа |
| Не инвалидировать ключ при изменении данных | Грязные данные отдаются, пока не истечёт TTL |
| Не кэшировать отсутствие данных | Cache penetration — каждый запрос несуществующего ключа уходит в базу |
Кэшировать временные ошибки 5xx как негативный результат | Залипший сбой отдаётся клиенту весь срок TTL |
| Оставлять горячему ключу один общий момент истечения | Cache stampede — все промахи бьют в базу разом при истечении |
| Не измерять hit/miss/eviction rate кэша | Не видно ни неверных TTL, ни вытеснения, ни роста памяти Redis |
Значение для собеседований
Кэширование — обязательная тема на senior-уровне Go-интервью в части system design, и проверяют не «знаете ли вы Redis», а понимаете ли вы кэш как слой со своей семантикой согласованности и отказами. Интервьюер смотрит, держите ли вы в голове, что кэш в памяти процесса не общий между репликами, что у каждого паттерна записи свой размен скорости и надёжности, и что истечение горячего ключа — это не мелочь, а отдельный режим отказа.
Что обычно проверяют:
- Из каких уровней складывается иерархия кэша (браузер, CDN, L1 в процессе, L2 в Redis, база) и что размещается на каждом по задержке и согласованности.
- Чем отличаются cache-aside, read-through, write-through и write-back — особенно по согласованности с базой и риску потери данных.
- Какие есть стратегии инвалидации (TTL, событийная, тег-based) и почему инвалидация трудна.
- Зачем нужно негативное кэширование с коротким TTL и почему нельзя кэшировать
5xx. - Что такое cache stampede и как его гасят — coalescing/singleflight, lease, ранний или вероятностный refresh, разнесённые TTL.
- Какие метрики кэша нужно отслеживать и о чём говорит низкий hit rate.
Типичный неверный ответ: «положим всё в Redis с TTL и забудем — кэш сам разрулит». Это запускает разбор того, что один TTL не решает ни инвалидацию при изменении данных, ни penetration на несуществующие ключи, ни stampede при истечении горячего ключа, и что без метрик hit/miss такой кэш невозможно ни настроить, ни заметить, когда он перестал помогать.