Инвалидация кэша: чтение кода и сниппетов
Читай реальные cache-aside-сниппеты — баг в дизайне ключа, purge, гоняющийся с читателем, и стампида на фиксированном TTL — и выбирай фикс наивысшего рычага, который сеньор делает первым.
Баги инвалидации прячутся в строке cache key и в порядке двух строк на пути записи. Прочитай каждый сниппет, найди открытый им разрыв несвежести и выбери фикс, который сеньор сделает первым.
Отработай цикл, который ты запускаешь в каждом инциденте консистентности кэша: прочитать дизайн ключа и путь записи, предсказать, где протекает устаревшее, и потянуться за фиксом наивысшего рычага — а не за самым большим молотком.
Сниппет 1 — cache key
// GET /products?category=shoes&sort=price&page=2&utm_source=newsletter
function cacheKey(req) {
return "products:" + req.url; // весь URL, со строкой запроса целиком
}
async function handler(req, res) {
const k = cacheKey(req);
let body = await redis.get(k);
if (!body) {
body = await db.queryProducts(req.query);
await redis.set(k, body, "EX", 300);
}
res.send(body);
}
Каталог товаров меняется редко, но hit rate почти ноль, а origin занят. Что не так с этим ключом и какой фикс?
Сниппет 2 — purge на записи
async function updateUser(id, patch) {
const row = await db.users.update(id, patch); // БД теперь свежая
await redis.del("user:" + id); // сбрасываем запись кэша
return row;
}
// в другом месте, путь чтения (cache-aside):
async function getUser(id) {
const k = "user:" + id;
let u = await redis.get(k);
if (!u) {
u = await db.users.find(id); // может прочитать строку до обновления
await redis.set(k, u, "EX", 3600); // ...и записать её обратно
}
return u;
}
Это проходит все локальные тесты, но в продакшене откатывает правки пользователя до часа. Дефект и самый дешёвый production-патч?
Сниппет 3 — TTL
const TTL = 600; // 10 минут, одинаково для каждой записи
async function cacheConfig(tenantId) {
const k = "config:" + tenantId;
let cfg = await redis.get(k);
if (!cfg) {
cfg = await db.loadConfig(tenantId); // дорого: ~400 мс, джоин 5 таблиц
await redis.set(k, cfg, "EX", TTL);
}
return cfg;
}
После выкатки на весь флот, прогревшей конфиги многих тенантов разом, p99 латентности резко скачет каждые 10 минут. Что происходит и первый фикс?
Три классических бага инвалидации, все прочитанные прямо из кода: cache key, вмешивающий нерелевантные параметры, разносит hit rate, поэтому строй ключи только из влияющих на ответ параметров, нормализованных; delete-on-write гоняется с перезаполняющим SET параллельного читателя, дешевле всего патчится delayed double-delete (лизы или write-through для сильного фикса); а фиксированный TTL, прогретый в одном окне, стампидит origin на каждом истечении, гасится jitter плюс single-flight. Сначала прочитай ключ и путь записи, назови разрыв, затем выбери наименьший фикс, который его закрывает.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.