awesome-everything EN
↑ Обратно к восхождению

Кеширование

Инвалидация кэша: чтение кода и сниппетов

Суть Читай реальные cache-aside-сниппеты — баг в дизайне ключа, purge, гоняющийся с читателем, и стампида на фиксированном TTL — и выбирай фикс наивысшего рычага, который сеньор делает первым.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

Баги инвалидации прячутся в строке 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. Сначала прочитай ключ и путь записи, назови разрыв, затем выбери наименьший фикс, который его закрывает.

Продолжить восхождение ↑Инвалидация кэша: воспроизведи и закрой гонку
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.