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

Кеширование

Уровни кэширования: от L1 до CDN, и где не тот уровень кусается

Суть Каждый уровень — от кэша CPU до CDN — меняет latency на расстояние и стоимость. Кэширование не на том уровне, двойное кэширование или кэш перед быстрым источником делают систему хуже, а не быстрее.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на junior-высоте — поверхность
◷ 16 min

Команда ставит Redis перед запросом в Postgres, чтобы «ускорить». Latency становится хуже. Запрос был выборкой по primary key из тёплого buffer pool — Postgres отвечал за ~0.3 мс из RAM. Новый путь сериализует ключ, делает сетевой round trip в Redis (~1 мс в том же регионе), десериализует и только потом, может быть, попадает. Они обернули источник со скоростью RAM в кэш со скоростью сети. Кэш стал тем самым узким местом, которое должен был убрать.

Иерархия — это лестница latency

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

Порядки величины важнее точных цифр. L1 примерно в 200 раз быстрее RAM; RAM примерно в 1000 раз быстрее SSD; SSD примерно в 100 раз быстрее сетевого хопа через континент. Добавляя уровень кэша, ты делаешь ставку, что фронтящийся слой медленнее кэша с большим запасом. Зафронтишь то, что и так быстрее, — и ты добавил latency, а не убрал.

УровеньТипичный доступРазмер / охватКому принадлежит
L1 кэш CPU~1 нс~32–64 КБ / ядроЖелезо
L2 кэш CPU~4 нс~256 КБ–1 МБ / ядроЖелезо
L3 кэш CPU~10–40 нс~8–64 МБ / сокетЖелезо
RAM~100 нсГБ / машинаОС + твой процесс
Page cache ОС~100 нс (попадание в RAM)свободная RAMЯдро
Кэш приложения (Redis/memcached)~0.2–1 мс (в регионе)ГБ–ТБ, общийТы
SSD~100 мксТБ / машинаОС
Reverse proxy / edge CDN~мс (edge), 100+ мс (далеко)глобально, на POPТы / провайдер

Для чего на самом деле каждый уровень

Аппаратные кэши (L1/L2/L3) и RAM — не твои в управлении: CPU и ОС крутят их автоматически. Но они всё равно формируют твой код: кэш-дружелюбный паттерн доступа (последовательный, с локальностью) может работать на порядок быстрее, чем погоня по указателям, потому что нужная cache line уже загружена. Поэтому плоский массив обходит связный список при итерации даже при одинаковом big-O.

Page cache ОС — самый недооценённый уровень. Когда ты читаешь файл, ядро держит его страницы в свободной RAM; второе чтение — это попадание в память, а не в диск. Поэтому твоя база быстра на горячих данных без всяких усилий с твоей стороны — buffer pool Postgres плюс page cache означают, что «дисковое» чтение обычно оказывается чтением из RAM. Сеньор знает это до того, как тянуться к Redis: если рабочий набор влезает в RAM, база уже является кэшем в RAM.

Кэши приложения (Redis, memcached) — первый уровень, которым ты управляешь сам. Это общее, доступное по сети хранилище в RAM: любой инстанс приложения может попасть в один и тот же кэш, чего локальная in-process память не умеет. Цена — сетевой round trip: обычно меньше миллисекунды в том же регионе, но 100+ мс, если клиент и кэш на разных континентах. Reverse proxy и CDN выталкивают закэшированные ответы ближе к пользователю, так что попадание вообще не доходит до твоего origin; вся ценность CDN географическая — отдать французскому пользователю из POP в Париже за миллисекунды вместо пересечения Атлантики.

Почему это работает

«Просто добавь Redis» — рефлекс, который пропускает главный вопрос: какова реальная latency источника? Если данные уже лежат в памяти твоего процесса, или в page cache ОС, или в тёплом buffer pool базы, источник работает со скоростью RAM, и сетевой кэш может только замедлить. Redis заслуживает место, когда источник по-настоящему дорогой — агрегат с кучей джойнов, внешний API, скан холодного диска, — а не когда он фронтит выборку по горячему ключу.

Hit ratio — единственное число, оправдывающее кэш

Кэш, который промахивается, платит обе цены: и lookup в кэше, и выборку из источника. Поэтому средняя latency кэшируемого пути — это hit_rate × cache_latency + miss_rate × (cache_latency + origin_latency). Посчитай — и низкий hit ratio строго хуже, чем без кэша, потому что на каждом промахе ты платишь налог кэша впустую. Поэтому «повысить hit ratio» не всегда цель: кэширование холодных, редко переиспользуемых данных может поднять твою общую latency, вытеснив по-настоящему горячие ключи ради одноразовых.

Сеньорская формулировка: кэш оправдан только когда origin_latency велика, hit ratio высокий, и данные терпят небольшую несвежесть. Промахнёшься хоть по одному из трёх — и ты добавляешь движущиеся части, новый домен отказа и проблему инвалидации в обмен на ничто. Самые дорогие кэши в проде — те, у которых hit ratio 40%, а убрать их все боятся.

Выбери лучший вариант

Read-эндпоинт делает джойн по 3 таблицам за 80 мс, его зовут 50 раз/сек, а данные меняются пару раз в день. Куда ставить кэш?

Сеньорский провал: не тот уровень и двойное кэширование

Опасные баги — не промахи кэша, а кэши, которые врут. Двойное кэширование — классика: значение кэшируется в Redis и ответ кэшируется на reverse proxy/CDN, у каждого свой TTL и своя инвалидация. Запись чистит Redis, но CDN ещё 5 минут отдаёт старый HTML по своему TTL — или наоборот, CDN очистили, а proxy перед ним продолжает отдавать свою устаревшую копию. Получается stale-on-stale: два независимых уровня рассинхронизированы, а cf-cache-status в DevTools показывает тебе только один из них. Дебаг — это обход запроса слой за слоем (X-Cache, Cache-Status, логи proxy, попадания в Redis), чтобы найти, какая копия врёт.

Провал не того уровня — это Hook: фронтить быстрый источник медленным кэшем. Он же проявляется как кэширование на уровне, который не видит событие инвалидации, — кэширование персонализированных данных на CDN (ключ по URL, пользователей не различает, поэтому течёт данные одного пользователя другому) или кэширование на edge данных, меняющихся на каждый запрос. Правило, которое сеньор усваивает: кэшируй настолько близко к потребителю, насколько позволяет волатильность данных, и никогда не давай двум уровням кэшировать одно и то же с независимыми сроками жизни. Один владелец на каждый закэшированный факт, один путь инвалидации.

Викторина

Ты оборачиваешь выборку Postgres по primary key (тёплая, ~0.3 мс из buffer pool) в Redis. Что будет с latency?

Викторина

Уровень кэша с hit ratio 35% фронтит источник на 80 мс, lookup в кэше — 1 мс. По сравнению с «без кэша» средняя latency:

Расставь шаги по порядку

Расставь уровни кэша от самого быстрого доступа к самому медленному:

  1. 1 L1 кэш CPU (~1 нс)
  2. 2 L3 кэш CPU (~10–40 нс)
  3. 3 RAM / page cache ОС (~100 нс)
  4. 4 SSD (~100 мкс)
  5. 5 Кэш приложения по сети — Redis в регионе (~1 мс)
  6. 6 Edge CDN для далёкого пользователя (100+ мс)
Вспомните перед уходом
  1. 01
    Объясни, почему добавление кэша может ухудшить latency, опираясь на числа access-latency.
  2. 02
    Что такое двойное кэширование и почему оно порождает баги stale-on-stale, которые трудно дебажить?
Итог

Кэширование — это лестница уровней: L1/L2/L3, RAM, page cache ОС, кэши приложения вроде Redis, reverse proxy и CDN, каждый больше, дешевле и медленнее предыдущего. Числа latency — это вся суть: примерно 1 нс на L1, 100 нс на RAM, 100 мкс на SSD, ~1 мс на round trip в Redis в регионе, 100+ мс на далёкий хоп CDN, с порядками величины между ступенями. Кэш окупается только когда фронтит по-настоящему более медленный источник, hit ratio высокий, и данные терпят несвежесть — потому что промах платит и lookup в кэше, и выборку источника, так что низкий hit ratio хуже, чем без кэша. Сеньорские провалы — кэширование не на том уровне (фронтить источник со скоростью RAM кэшем со скоростью сети или кэшировать персонализированные данные на CDN с ключом по URL) и двойное кэширование, где два уровня держат один факт с независимыми TTL и расходятся в баги stale-on-stale, всплывающие, только когда обходишь запрос слой за слоем. Кэшируй настолько близко к потребителю, насколько позволяет волатильность данных, давай каждому закэшированному факту одного владельца и один путь инвалидации, и проверяй реальную latency источника прежде, чем тянуться к Redis.

Продолжить восхождение ↑Уровни кэша: тест с выбором ответа
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.