Кэширование в масштабе
Уровни кэша, паттерны чтения и записи, стратегии инвалидации, негативное кэширование, защита от cache stampede и наблюдаемость кэша.
7 вопросов
JuniorТеорияОчень частоНазовите основные паттерны кэширования и как каждый обрабатывает чтение и запись.
Назовите основные паттерны кэширования и как каждый обрабатывает чтение и запись.
cache-aside: приложение проверяет кэш, при промахе читает БД и наполняет кэш само (самый частый). read-through: кэш прозрачно подгружает из БД при промахе. write-through: пишем в кэш, затем синхронно в БД — согласованно, но медленнее. write-back: пишем в кэш, сбрасываем в БД асинхронно — быстро, но теряем данные при сбое.
Типичные ошибки
- ✗Считать
cache-asideиread-throughодним и тем же — в cache-aside кэш наполняет приложение, в read-through сам кэш. - ✗Называть
write-backбезопасным для критичных данных; асинхронный сброс при сбое теряет записи, не дошедшие до БД. - ✗Думать, что
write-throughускоряет запись — он добавляет запись в кэш в синхронный путь, и запись замедляется.
Уточняющие вопросы
- →Когда вы выберете write-back несмотря на риск потери данных при сбое?
- →Как cache-aside обрабатывает запись и какую устареваемость это вызывает?
MiddleТеорияОчень частоКак держать кэшированные данные свежими и чем различаются инвалидации TTL-only, event-based и tag-based?
Как держать кэшированные данные свежими и чем различаются инвалидации TTL-only, event-based и tag-based?
TTL-only даёт записям истечь, поэтому чтения могут устареть вплоть до TTL, зато записи проще. Event-based удаляет или обновляет ключ при изменении данных. Tag-based группирует ключи под тегом и сбрасывает весь тег разом. Тяжело инвалидировать L1 и L2 вместе.
Типичные ошибки
- ✗Полагаться на TTL-only для данных, которым нужна свежесть, и удивляться, что чтения устаревшие до истечения TTL
- ✗Инвалидировать только L2 и забыть, что копия L1 на каждом инстансе продолжает отдавать старое значение
- ✗Обновлять ключ при записи, но не обрабатывать удаление, из-за чего устаревшие записи висят после удаления записи
Уточняющие вопросы
- →Как распространить инвалидацию на L1-кэш каждого инстанса?
- →Когда допустима устарелость вплоть до TTL как приемлемый компромисс?
JuniorТеорияЧастоКакие уровни кэша идут от браузера до БД и что каждый обменивает на что?
Какие уровни кэша идут от браузера до БД и что каждый обменивает на что?
Кэш браузера, затем CDN/edge, затем L1 in-process кэш (на каждый инстанс, самый быстрый, но не общий и может устареть между инстансами), затем общий L2 распределённый кэш вроде Redis, затем БД как источник истины. Каждый уровень меняет латентность на hit-rate и согласованность.
Типичные ошибки
- ✗Считать L1 in-process кэш общим для инстансов; он на каждый инстанс и может расходиться между ними.
- ✗Принимать L2 распределённый кэш за источник истины; им является БД, а кэш всегда может быть холодным.
- ✗Игнорировать компромисс латентность-против-согласованности и считать, что больше уровней кэша всегда лучше.
Уточняющие вопросы
- →Когда вы пропустите L1 кэш и будете читать прямо из
Redis? - →Как удержать L1 кэши двух инстансов от расхождения?
MiddleТеорияЧастоЧто такое cache stampede при паттерне cache-aside и как его предотвратить?
Что такое cache stampede при паттерне cache-aside и как его предотвратить?
Cache stampede — это когда горячий ключ протухает и множество параллельных запросов разом промахиваются и бьют по базе (thundering herd). Смягчай коалесингом singleflight (одно перестроение, остальные ждут), lock на перестроение, ранним refresh и разносом TTL.
Типичные ошибки
- ✗Думают, что один большой TTL решает проблему — он лишь сдвигает stampede к единому синхронному истечению, а не предотвращает его.
- ✗Путают stampede с пробитием кэша; коалесинг перестраивает существующий ключ, а negative-caching защищает отсутствующий.
- ✗Считают, что
singleflightна инстанс достаточно при масштабе — он коалесит внутри процесса, но не между репликами сервиса.
Уточняющие вопросы
- →Чем
singleflightна инстанс отличается от распределённого Redis-lock для коалесинга? - →Что такое вероятностное раннее истечение и когда оно лучше фиксированного раннего refresh?
JuniorТеорияИногдаЧто такое негативное кэширование и когда его стоит применять?
Что такое негативное кэширование и когда его стоит применять?
Негативное кэширование сохраняет ОТСУТСТВИЕ данных — например 404 из запроса к каталогу — с КОРОТКИМ TTL, чтобы повторные запросы по отсутствующему ключу отдавались из кэша, а не проваливались в БД (cache penetration). Кэшируйте только устойчивые негативы, не временные 5xx.
Типичные ошибки
- ✗Кэшировать ошибки
5xx, из-за чего временный сбой залипает в кэше надолго после восстановления сервиса - ✗Использовать обычный длинный TTL для негативов, держа
404в кэше уже после создания элемента - ✗Совсем не делать негативное кэширование, позволяя каждому промаху по горячему отсутствующему ключу бить в БД
Уточняющие вопросы
- →Как подобрать негативный TTL относительно позитивного для того же ключа?
- →Как негативное кэширование взаимодействует с режимом отказа cache penetration от случайных неизвестных ключей?
MiddleТеорияИногдаКакие метрики ты отслеживаешь, чтобы настраивать многоуровневый кэш?
Какие метрики ты отслеживаешь, чтобы настраивать многоуровневый кэш?
Отслеживай hit rate, miss rate, eviction rate и латентность поиска по уровням. Низкий hit rate — неверные TTL или ключи; растущий eviction rate — кэш мал для рабочего набора. Следи за памятью Redis и вытеснениями по maxmemory на L2. Нельзя настроить кэш, который не измеряешь.
Типичные ошибки
- ✗Следить только за hit rate и игнорировать eviction rate, упуская слишком маленький кэш
- ✗Считать низкий hit rate безвредным, а не сигналом неверных TTL или ключей
- ✗Не следить за памятью
Redisи вытеснениями поmaxmemory, и L2 тихо буксует
Уточняющие вопросы
- →О чём обычно говорит высокий hit rate вместе с растущей латентностью?
- →Как бы ты настроил алерт на резкое падение hit rate кэша?
SeniorДизайнРедкоСпроектируйте многоуровневый кэш на Go для read-heavy каталога товаров: обновления должны отражаться за секунды, запросы несуществующих SKU часты, а горячие товары всплеском бьют по БД, когда их ключи истекают.
Спроектируйте многоуровневый кэш на Go для read-heavy каталога товаров: обновления должны отражаться за секунды, запросы несуществующих SKU часты, а горячие товары всплеском бьют по БД, когда их ключи истекают.
Сложите L1 in-process кэш плюс L2 Redis кэш, оба перед БД по схеме cache-aside. На обновление продукта делайте event-based инвалидацию, удаляющую ключ в обоих уровнях, с короткими TTL, чтобы дрейф самоисцелялся. Негативно кэшируйте отсутствующие SKU с коротким TTL, чтобы остановить penetration. Сливайте перестройки горячих ключей через singleflight плюс ранний refresh, чтобы убить stampede, и экспортируйте метрики hit, miss, eviction и латентности.
Типичные ошибки
- ✗Инвалидировать только по TTL, из-за чего обновление продукта невидимо весь TTL вместо сброса ключа по событию
- ✗Пропустить негативное кэширование, из-за чего повторные запросы отсутствующих SKU все проникают в БД
- ✗Дать истечению горячего ключа вызвать stampede, потому что перестройки не слиты за один flight
Уточняющие вопросы
- →Куда вы публикуете событие обновления и как надёжно инвалидируете оба уровня?
- →Как подобрать TTL, чтобы уровни L1 и L2 не отдавали конфликтующие версии?