Экспресс-курс · No. 15

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

Только суть · Один образ на идею · Выучи слова

§ 01

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

Кэш помнит ответ

Блокнот на столе рядом с медленным шкафом документов — раз что-то нашёл, записываешь, чтобы в следующий раз прочитать заметку, а не идти через комнату снова.

Кэш — это маленькое, быстрое хранилище, что держит результат дорогой работы — запроса к базе, посчитанной страницы, скачанного файла, — чтобы следующий запрос за ним был дёшев. Дорогое случается раз; дешёвое чтение — много раз. Это весь принцип. Всё остальное — детали о том, где живёт блокнот и когда доверять тому, что на нём.

Попадание и промах — два исхода

Тянешься к заметке. Либо она там, и ты читаешь мгновенно, — либо пуста, и приходится всё же идти к шкафу.

Когда ответ в кэше — это попадание (cache hit), быстро и дёшево. Когда нет — это промах (cache miss): делаешь медленную работу и обычно сохраняешь результат, чтобы в следующий раз было попадание. Эти два слова описывают любое взаимодействие с кэшем. Вся игра кэширования — превращать промахи в попадания: следить, чтобы ответ был там, когда ты за ним тянешься.

Это работает благодаря повторению

Кофейня, запоминающая заказы завсегдатаев, — оправдано лишь потому, что одни и те же люди просят одно и то же снова и снова.

Кэширование окупается, когда одни и те же ответы нужны многократно, — а почти всё в вычислениях повторяется. Одна популярная страница, профиль того же пользователя, тот же поиск, спрошенный тысячи раз. Поскольку доступ не случаен, а скучен на нескольких горячих элементах, маленький кэш популярных может впитать огромную долю работы. Нет повторения — нет смысла кэшировать.

Кэш держит быструю копию дорогого ответа. Попадание — это прочесть заметку; промах — это поход к шкафу. Работа — превращать промахи в попадания.

§ 02

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

Чем ближе кэш, тем быстрее попадание

Запасы, что ты держишь на столе, в комнате, в здании и на складе через город, — чем ближе, тем быстрее достанешь, но тем меньше влезает.

Между пользователем и истиной есть лестница кэшей, каждый ближе и быстрее предыдущего: браузер держит файлы на твоём устройстве, CDN держит копии рядом с твоим городом, приложение держит горячие данные в памяти, а у процессора есть крошечные кэши в наносекундах. Ближние кэши быстрее, но меньше. Запрос пробует ближайший первым и отступает наружу при промахе.

Хранилища в памяти вроде Redis — рабочая лошадка

Клерк, что держит самые спрашиваемые файлы на столе под рукой, вместо того чтобы доставать каждый из подвального архива каждый раз.

Большинство приложений ставят выделенный кэш в памяти — инструмент вроде Redis или Memcached — между приложением и базой. Память драматически быстрее диска, так что держать горячие результаты там превращает медленный запрос к базе в почти мгновенное чтение. Когда говорят «добавь кэш», чтобы ускорить бэкенд, обычно имеют в виду именно это: быстрое хранилище в памяти перед медленным источником истины.

CDN кэширует веб рядом с пользователем

Популярные книги, что лежат в местных библиотеках повсюду, чтобы никому не пришлось слать запрос в единственный центральный архив.

CDN (content delivery network) — это кэширование, применённое к географии: оно держит копии твоих страниц, картинок и файлов на серверах по всему миру, обслуживая каждого пользователя с ближнего. Поэтому глобальный сайт грузится быстро везде, а не только рядом с исходным сервером. Это та же идея попадание/промах, что и везде, — копия просто разбросана по карте, чтобы побить расстояние.

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

§ 03

Кэш стоит своей сложности, только если реально ловит большинство запросов. Одно число — hit ratio — говорит, отрабатывает ли кэш своё содержание.

Hit ratio — тот балл, что важен

Вратаря судят по доле ударов, что он берёт. Тот, кто берёт 95%, преображает игру; тот, кто берёт 10%, едва её меняет.

Hit ratio (доля попаданий) — это доля запросов, обслуженных из кэша, а не из медленного источника. Hit ratio 95% значит, что лишь один запрос из двадцати доходит до базы — огромное снижение нагрузки и задержки. Hit ratio 10% значит, что кэш в основном накладные расходы. Это единственное число — то, как ты судишь, работает ли кэш, и его улучшение — главный рычаг, что ты крутишь.

Холодный кэш должен прогреться

Новый магазин с пустыми полками сперва никого не обслуживает быстро — он затоваривается по мере спроса, пока популярное не окажется всегда под рукой.

Сразу после старта кэш холодный (cold) — пуст, так что каждый запрос промахивается и идёт к медленному источнику. По мере реального трафика он наполняется популярными ответами и становится тёплым (warm), а hit ratio растёт. Поэтому производительность может выглядеть плохо сразу после рестарта или деплоя, а потом устаканиться. Некоторые системы прогревают кэш намеренно, загружая известно-горячие данные до прихода пользователей.

Кэш меняет память на скорость

Аренда стола побольше, чтобы под рукой влезло больше файлов, — это стоит места, но к шкафу ходишь куда реже.

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

Hit ratio — это табель кэша. Ты тратишь память, чтобы купить скорость, — так кэшируй горячие, дорогие ответы, что зарабатывают больше всего попаданий на байт.

§ 04

Запомненный ответ может устареть, пока ты ему всё ещё доверяешь. Знать, когда перестать доверять копии, — центральная, по-настоящему сложная задача кэширования.

Устаревшие данные — это цена кэширования

Тот номер телефона, что ты черкнул на стикере, — золото, ровно до тех пор, пока друг не переедет и заметка тихо не станет неверной.

Оборотная сторона запоминания ответа в том, что настоящий ответ может измениться, а твоя копия — нет. Устаревшее (stale) — это закэшированное значение, что больше не совпадает с истиной: старая цена, удалённый пост, остаток на складе час назад. Любой кэш рискует отдать устаревшее, и решить, сколько устаревания ты терпишь, — первый настоящий вопрос проектирования любого кэша.

TTL: пусть истекает по таймеру

Молоко с датой «годно до» — после неё ты не доверяешь ему без проверки, даже если выглядит нормально.

Простейший инструмент свежести — TTL (time to live): каждая запись кэша получает срок, и после него запись считается исчезнувшей, вынуждая свежий запрос. Короткий TTL — свежее данные, но больше промахов; длинный TTL — больше попаданий, но больше устаревания. Выбор TTL — это выбор твоей точки на размене «свежесть против скорости» — под каждый вид данных, по тому, как быстро он реально меняется.

Почему это знаменито сложно

Знать миг, когда факт изменился где-то ещё в мире, чтобы порвать заметку ровно в нужный момент, — легко сказать, бесит делать.

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

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

§ 05

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

Cache-aside: приложение наполняет кэш

Сначала смотришь в блокнот; если пусто, идёшь к шкафу, читаешь файл и записываешь ответ, прежде чем продолжить.

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

Write-through и write-around: держать записи согласованными

Когда факт меняется, можно обновить заметку на столе в тот же миг, что и мастер-файл, — или пропустить заметку и дать перечитать её свежей в следующий раз.

Когда данные пишутся, ты выбираешь, как кэш поспевает. Write-through обновляет кэш и источник вместе, так что кэш никогда не устаревает (ценой медленных записей). Write-around пишет только в источник и даёт кэшу промахнуться и перезагрузить позже, избегая кэширования данных, что никто не перечитывает. Эти имена описывают, куда садится запись и когда кэш о ней узнаёт, — а что выбрать, зависит от твоего баланса чтений и записей.

Вытеснение: что выбросить, когда полно

Маленький стол, что полон, — чтобы добавить новый файл, надо убрать один, так что убираешь тот, что не трогал вечность, а не тот, что используешь ежечасно.

У кэша ограниченное место, так что, наполнившись, он должен вытеснить (evict) что-то. Самая частая политика — LRU (least recently used): выбрось то, что дольше всех не трогали, на ставке, что скоро оно не понадобится. Есть и другие — LFU выбрасывает реже всего используемое. Вытеснение — это как маленький кэш автоматически держит горячие элементы и сбрасывает холодные, удерживая hit ratio, не разрастаясь вечно.

Инвалидация решает, когда закэшированное значение неверно; вытеснение решает, что выбросить, когда место кончилось. Cache-aside, write-through, LRU — имена для этих двух работ.

§ 06

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

Stampede, когда горячий ключ истекает

Популярный товар распродаётся, и в тот же миг сотня покупателей разом ломится к стойке спросить его — захлёстывая единственного клерка, что пополняет запас.

Когда популярная запись кэша истекает, каждый запрос, что её хотел, промахивается в один и тот же миг и все бьют в медленный источник вместе — это stampede (или thundering herd, «гром стада»). База, защищённая часами, вдруг принимает всю толпу разом и может прогнуться. Лекарства известны — дать одному запросу обновить, пока другие ждут, или разнести сроки истечения, — но это надо предвидеть; stampede появляется ровно тогда, когда трафик выше всего.

Устаревшие чтения: быстро, уверенно и неверно

Прочитать цену со старого стикера и уверенно её назвать — число выдано мгновенно, и это неверное число.

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

Некоторое вообще не стоит кэшировать

Ты не держишь ксерокопию документа, что переписывают каждую минуту, или чьего-то приватного письма на общем столе, — копия неверна или ей там не место.

Не всё уместно в кэше. Данные, что меняются постоянно, дают плохой hit ratio и частое устаревание. Чувствительные или попользовательские данные рискуют утечь, если общий кэш вручит копию одного пользователя другому. А редко спрашиваемые данные просто тратят место. Знать, что не кэшировать — быстро меняющееся, чувствительное или непопулярное, — так же важно, как знать, что кэшировать.

Классические укусы кэширования именованы: stampede, когда горячий ключ истекает, устаревшие чтения, выданные с ложной уверенностью, и кэширование того, что никогда не следовало.

§ 07

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

Кэшируй горячее, дорогое и медленно меняющееся

Ты запоминаешь привычные заказы завсегдатаев и дорогу, что объясняешь десять раз в день, — а не разовые просьбы или то, что меняется поминутно.

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

Реши бюджет устаревания заранее

Договориться заранее, насколько устаревшее приемлемо, — виджет погоды может быть на минуты старым; баланс банка не может быть на секунды старым.

Прежде чем что-то кэшировать, реши, сколько устаревания оно терпит, потому что этот выбор гонит всё остальное — TTL, нужна ли активная инвалидация, безопасно ли кэшировать вообще. Сделай это явно под каждый вид данных. «Насколько неверным ему позволено быть и как долго?» — вопрос, что превращает кэширование из источника загадочных багов в управляемый, намеренный размен.

Прежде чем добавить кэш
  • Горячие ли данные — спрашивают ли их достаточно часто, чтобы дать реальный hit ratio? - Дорого ли их производить, чтобы попадание реально экономило ощутимую работу? - Насколько устаревшими они могут быть — какой TTL, и нужна ли активная инвалидация? - Что станет, когда горячая запись истечёт — не вызовет ли это stampede? - Безопасно ли кэшировать — не попользовательские ли это секретные данные в общем кэше? - Как я узна́ю, что работает — меряю ли я hit ratio?
Слова, которыми ты теперь владеешь
  • cache hit / miss — ответ найден в кэше или нет. - hit ratio — доля запросов, обслуженных из кэша; тот балл, что важен. - cold / warm кэш — пустой и промахивающийся против наполненного и попадающего. - TTL / stale — таймер истечения и копия, что больше не совпадает с истиной. - инвалидация / вытеснение — выбросить неверное и сбросить данные ради места (LRU). - cache-aside / write-through — приложение наполняет лениво или записи обновляют сразу. - stampede / thundering herd — рывок к источнику, когда горячий ключ истекает.
Признаки, что ты кэшируешь хорошо
  • Можешь назвать hit ratio своих важных кэшей. - У каждого кэшируемого — намеренный TTL, подогнанный под то, как быстро оно реально меняется. - Ничего попользовательского или секретного не сидит в общем кэше случайно. - Твои горячие ключи не застемпедят источник разом при истечении. - Ты кэшируешь горячие, дорогие, медленно меняющиеся ответы — а остальное пропускаешь.

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

Конец экспресс-курса · 7 глав · выучи слова

Дальше — практика: найди самую медленную повторяющуюся операцию в чём-то, что ты построил, — запрос, вызов API, посчитанную страницу — и поставь перед ней маленький кэш с явным TTL. Потом смотри, как меняются hit ratio и задержка. Размен становится реальным в тот миг, как ты его меряешь. Но держи одну мысль выше прочих: кэширование — это просто запоминание дорогого ответа вместо переделки работы, — и каждая сложная часть на деле один вопрос, заданный честно: когда то, что я запомнил, перестало быть правдой?