Урок по треку System design 7 мин

Кеширование в распределённых системах

Уровни кеша, стратегии записи, инвалидация, вытеснение, лавина и горячие ключи.

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

Где бывает кеш

  • Браузер: HTTP-заголовки Cache-Control, ETag.
  • CDN: статика, картинки, иногда целые страницы.
  • Обратный прокси: nginx кеширует ответы бэкенда.
  • В процессе приложения: самый быстрый, но у каждого экземпляра свой и они расходятся.
  • Распределённый кеш: Redis, Memcached. Общий для всех экземпляров.
  • Внутри БД: буферный кеш PostgreSQL (shared_buffers) и кеш ОС.

Стратегии чтения и записи

  • Cache-aside (lazy loading). Приложение читает кеш; при промахе идёт в БД и кладёт результат в кеш с TTL. Самая распространённая схема. При записи данные пишут в БД и удаляют ключ из кеша.
  • Read-through. То же, но в кеш ходит библиотека или сам кеш-слой.
  • Write-through. Запись идёт в кеш и синхронно в БД. Кеш всегда свежий, запись медленнее.
  • Write-behind (write-back). Запись в кеш, в БД асинхронно пачками. Быстро, но при падении кеша можно потерять данные.

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

Инвалидация

Классическая шутка: в программировании две сложные вещи, инвалидация кеша и именование. Варианты:

  • TTL: просто и надёжно, но данные могут быть устаревшими до истечения TTL.
  • Явное удаление при записи.
  • События изменений (CDC из БД через Debezium в Kafka), подписчики чистят кеш.
  • Версионирование ключей: product:42:v7, при изменении версия растёт.

Вытеснение

Когда память кончается, кеш выкидывает записи по политике: LRU (давно не использованные), LFU (редко используемые), FIFO, случайно. В Redis задаётся maxmemory-policy, например allkeys-lru или allkeys-lfu.

Типичные проблемы

  • Cache stampede (лавина, dogpile). Популярный ключ истёк, и тысячи запросов одновременно идут в БД. Решения: блокировка на перестроение (только один запрос идёт в БД), ранее обновление до истечения, отдача устаревшего значения пока строится новое.
  • Массовое истечение. Много ключей поставили с одинаковым TTL, и они истекли разом. Решение: добавлять к TTL случайный разброс (jitter).
  • Cache penetration. Запросы к несуществующим ключам всегда идут в БД. Решение: кешировать пустой результат с коротким TTL или фильтр Блума.
  • Горячий ключ. Один ключ получает огромную долю трафика и перегружает один узел Redis. Решение: локальный кеш в процессе на секунды, реплики ключа с суффиксом.

Hit rate

Доля попаданий в кеш. При hit rate 90% в БД доходит 10% чтений, при 99% только 1%. Рост с 90 до 99 процентов снижает нагрузку на БД в 10 раз.

Частые ошибки

  • Кешировать всё подряд без понимания паттерна доступа.
  • Не ставить TTL.
  • Считать кеш источником истины без персистентности.
  • Забыть, что после рестарта кеш холодный и вся нагрузка пойдёт в БД.

Что спросят на собесе

  • Какие стратегии кеширования знаете, чем cache-aside отличается от write-through.
  • Как инвалидировать кеш при изменении данных.
  • Что такое cache stampede и как с ним бороться.
  • Где в системе можно поставить кеш.
  • Что делать с горячим ключом.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.