Экспресс-курс · No. 15
Кэш — это маленькое, быстрое место, где ты держишь уже посчитанный ответ, чтобы в следующий раз прочитать его, а не переделывать работу. Это крупнейший трюк скорости в вычислениях — и он идёт с самой знаменитой сложной задачей: знать, когда запомненный ответ устарел.
Только суть · Один образ на идею · Выучи слова
Кэш — это одна простая идея: держать копию ответа где-то быстро, чтобы не платить за его производство дважды. Пойми это — и любой кэш, что встретишь, окажется тем же трюком в другом месте.
Кэш помнит ответ
Блокнот на столе рядом с медленным шкафом документов — раз что-то нашёл, записываешь, чтобы в следующий раз прочитать заметку, а не идти через комнату снова.
Кэш — это маленькое, быстрое хранилище, что держит результат дорогой работы — запроса к базе, посчитанной страницы, скачанного файла, — чтобы следующий запрос за ним был дёшев. Дорогое случается раз; дешёвое чтение — много раз. Это весь принцип. Всё остальное — детали о том, где живёт блокнот и когда доверять тому, что на нём.
Попадание и промах — два исхода
Тянешься к заметке. Либо она там, и ты читаешь мгновенно, — либо пуста, и приходится всё же идти к шкафу.
Когда ответ в кэше — это попадание (cache hit), быстро и дёшево. Когда нет — это промах (cache miss): делаешь медленную работу и обычно сохраняешь результат, чтобы в следующий раз было попадание. Эти два слова описывают любое взаимодействие с кэшем. Вся игра кэширования — превращать промахи в попадания: следить, чтобы ответ был там, когда ты за ним тянешься.
Это работает благодаря повторению
Кофейня, запоминающая заказы завсегдатаев, — оправдано лишь потому, что одни и те же люди просят одно и то же снова и снова.
Кэширование окупается, когда одни и те же ответы нужны многократно, — а почти всё в вычислениях повторяется. Одна популярная страница, профиль того же пользователя, тот же поиск, спрошенный тысячи раз. Поскольку доступ не случаен, а скучен на нескольких горячих элементах, маленький кэш популярных может впитать огромную долю работы. Нет повторения — нет смысла кэшировать.
Кэш держит быструю копию дорогого ответа. Попадание — это прочесть заметку; промах — это поход к шкафу. Работа — превращать промахи в попадания.
Кэширование — это не одна вещь в одном месте, а паттерн, повторённый на каждом слое между пользователем и данными. Чем ближе копия к тому, кому нужна, тем она быстрее.
Чем ближе кэш, тем быстрее попадание
Запасы, что ты держишь на столе, в комнате, в здании и на складе через город, — чем ближе, тем быстрее достанешь, но тем меньше влезает.
Между пользователем и истиной есть лестница кэшей, каждый ближе и быстрее предыдущего: браузер держит файлы на твоём устройстве, CDN держит копии рядом с твоим городом, приложение держит горячие данные в памяти, а у процессора есть крошечные кэши в наносекундах. Ближние кэши быстрее, но меньше. Запрос пробует ближайший первым и отступает наружу при промахе.
Хранилища в памяти вроде Redis — рабочая лошадка
Клерк, что держит самые спрашиваемые файлы на столе под рукой, вместо того чтобы доставать каждый из подвального архива каждый раз.
Большинство приложений ставят выделенный кэш в памяти — инструмент вроде Redis или Memcached — между приложением и базой. Память драматически быстрее диска, так что держать горячие результаты там превращает медленный запрос к базе в почти мгновенное чтение. Когда говорят «добавь кэш», чтобы ускорить бэкенд, обычно имеют в виду именно это: быстрое хранилище в памяти перед медленным источником истины.
CDN кэширует веб рядом с пользователем
Популярные книги, что лежат в местных библиотеках повсюду, чтобы никому не пришлось слать запрос в единственный центральный архив.
CDN (content delivery network) — это кэширование, применённое к географии: оно держит копии твоих страниц, картинок и файлов на серверах по всему миру, обслуживая каждого пользователя с ближнего. Поэтому глобальный сайт грузится быстро везде, а не только рядом с исходным сервером. Это та же идея попадание/промах, что и везде, — копия просто разбросана по карте, чтобы побить расстояние.
Кэширование — паттерн, повторённый на каждом слое: браузер, CDN, хранилище в памяти, процессор. Ближе — быстрее, но меньше, а промах отступает наружу к следующему.
Кэш стоит своей сложности, только если реально ловит большинство запросов. Одно число — hit ratio — говорит, отрабатывает ли кэш своё содержание.
Hit ratio — тот балл, что важен
Вратаря судят по доле ударов, что он берёт. Тот, кто берёт 95%, преображает игру; тот, кто берёт 10%, едва её меняет.
Hit ratio (доля попаданий) — это доля запросов, обслуженных из кэша, а не из медленного источника. Hit ratio 95% значит, что лишь один запрос из двадцати доходит до базы — огромное снижение нагрузки и задержки. Hit ratio 10% значит, что кэш в основном накладные расходы. Это единственное число — то, как ты судишь, работает ли кэш, и его улучшение — главный рычаг, что ты крутишь.
Холодный кэш должен прогреться
Новый магазин с пустыми полками сперва никого не обслуживает быстро — он затоваривается по мере спроса, пока популярное не окажется всегда под рукой.
Сразу после старта кэш холодный (cold) — пуст, так что каждый запрос промахивается и идёт к медленному источнику. По мере реального трафика он наполняется популярными ответами и становится тёплым (warm), а hit ratio растёт. Поэтому производительность может выглядеть плохо сразу после рестарта или деплоя, а потом устаканиться. Некоторые системы прогревают кэш намеренно, загружая известно-горячие данные до прихода пользователей.
Кэш меняет память на скорость
Аренда стола побольше, чтобы под рукой влезло больше файлов, — это стоит места, но к шкафу ходишь куда реже.
Кэширование не бесплатно: ты тратишь память (что ограничена и стоит денег), чтобы сэкономить время. Кэш побольше держит больше и попадает чаще, но всё закэшировать нельзя — так что искусство в кэшировании правильных вещей: горячих, дорогих, многократно спрашиваемых ответов, что дают больше всего скорости на байт. Кэш — это намеренный размен, и hit ratio говорит, окупается ли он.
Hit ratio — это табель кэша. Ты тратишь память, чтобы купить скорость, — так кэшируй горячие, дорогие ответы, что зарабатывают больше всего попаданий на байт.
Запомненный ответ может устареть, пока ты ему всё ещё доверяешь. Знать, когда перестать доверять копии, — центральная, по-настоящему сложная задача кэширования.
Устаревшие данные — это цена кэширования
Тот номер телефона, что ты черкнул на стикере, — золото, ровно до тех пор, пока друг не переедет и заметка тихо не станет неверной.
Оборотная сторона запоминания ответа в том, что настоящий ответ может измениться, а твоя копия — нет. Устаревшее (stale) — это закэшированное значение, что больше не совпадает с истиной: старая цена, удалённый пост, остаток на складе час назад. Любой кэш рискует отдать устаревшее, и решить, сколько устаревания ты терпишь, — первый настоящий вопрос проектирования любого кэша.
TTL: пусть истекает по таймеру
Молоко с датой «годно до» — после неё ты не доверяешь ему без проверки, даже если выглядит нормально.
Простейший инструмент свежести — TTL (time to live): каждая запись кэша получает срок, и после него запись считается исчезнувшей, вынуждая свежий запрос. Короткий TTL — свежее данные, но больше промахов; длинный TTL — больше попаданий, но больше устаревания. Выбор TTL — это выбор твоей точки на размене «свежесть против скорости» — под каждый вид данных, по тому, как быстро он реально меняется.
Почему это знаменито сложно
Знать миг, когда факт изменился где-то ещё в мире, чтобы порвать заметку ровно в нужный момент, — легко сказать, бесит делать.
Есть ходячая шутка, что в информатике лишь две сложные задачи, и инвалидация кэша — знать, когда закэшированное значение надо выбросить — одна из них. Сложна она потому, что кэш часто не знает, что лежащие данные изменились; изменение случилось где-то ещё. Следующий раздел — это набор стратегий, что люди используют против ровно этой задачи.
Любой закэшированный ответ может устареть. TTL истекает по таймеру — а знать точно, когда инвалидировать, это одна из по-настоящему сложных задач вычислений.
Две силы решают, что в кэше и можно ли ему доверять: как ты обновляешь его, когда истина меняется, и как освобождаешь место, когда он полон. У обеих есть именованные стратегии, что стоит знать.
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 — имена для этих двух работ.
Мощь кэширования идёт с острыми краями. Классические сбои не экзотичны — они предсказуемы, у них есть имена, и знать их — половина того, чтобы их избежать.
Stampede, когда горячий ключ истекает
Популярный товар распродаётся, и в тот же миг сотня покупателей разом ломится к стойке спросить его — захлёстывая единственного клерка, что пополняет запас.
Когда популярная запись кэша истекает, каждый запрос, что её хотел, промахивается в один и тот же миг и все бьют в медленный источник вместе — это stampede (или thundering herd, «гром стада»). База, защищённая часами, вдруг принимает всю толпу разом и может прогнуться. Лекарства известны — дать одному запросу обновить, пока другие ждут, или разнести сроки истечения, — но это надо предвидеть; stampede появляется ровно тогда, когда трафик выше всего.
Устаревшие чтения: быстро, уверенно и неверно
Прочитать цену со старого стикера и уверенно её назвать — число выдано мгновенно, и это неверное число.
Кэш с радостью отдаст устаревшее значение быстро и с полной уверенностью. Обычно это нормально — слегка старый счётчик просмотров никому не вредит. Но для данных, где правильность критична — баланс банка, остатки, права доступа, — устаревшее чтение может быть настоящим багом. Навык — знать, какие данные терпят устаревание, а какие должны быть всегда свежими, и никогда не кэшировать второй вид небрежно.
Некоторое вообще не стоит кэшировать
Ты не держишь ксерокопию документа, что переписывают каждую минуту, или чьего-то приватного письма на общем столе, — копия неверна или ей там не место.
Не всё уместно в кэше. Данные, что меняются постоянно, дают плохой hit ratio и частое устаревание. Чувствительные или попользовательские данные рискуют утечь, если общий кэш вручит копию одного пользователя другому. А редко спрашиваемые данные просто тратят место. Знать, что не кэшировать — быстро меняющееся, чувствительное или непопулярное, — так же важно, как знать, что кэшировать.
Классические укусы кэширования именованы: stampede, когда горячий ключ истекает, устаревшие чтения, выданные с ложной уверенностью, и кэширование того, что никогда не следовало.
Кэширование — силовой инструмент: огромная скорость за немного памяти, оплаченная риском выдать прошлое. Применённое намеренно, это один из лучших разменов в вычислениях.
Кэшируй горячее, дорогое и медленно меняющееся
Ты запоминаешь привычные заказы завсегдатаев и дорогу, что объясняешь десять раз в день, — а не разовые просьбы или то, что меняется поминутно.
Идеальная цель кэша — часто читаемое, дорогое в производстве и редко меняемое: эта комбинация даёт больше всего скорости при наименьшем риске устаревания. Популярная страница, построенная из медленного запроса, пересчитываемая одинаково весь день, — идеальна. Постоянно меняющееся, редко читаемое, чувствительное значение — наоборот. Большинство побед кэширования идут от поиска той горстки ответов, что подходят под первое описание, и запоминания ровно их.
Реши бюджет устаревания заранее
Договориться заранее, насколько устаревшее приемлемо, — виджет погоды может быть на минуты старым; баланс банка не может быть на секунды старым.
Прежде чем что-то кэшировать, реши, сколько устаревания оно терпит, потому что этот выбор гонит всё остальное — 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, подогнанный под то, как быстро оно реально меняется. - Ничего попользовательского или секретного не сидит в общем кэше случайно. - Твои горячие ключи не застемпедят источник разом при истечении. - Ты кэшируешь горячие, дорогие, медленно меняющиеся ответы — а остальное пропускаешь.
Кэширование меняет немного памяти на огромную скорость, рискуя выдать прошлое. Сделанное хорошо, оно намеренно: правильные ответы и бюджет устаревания, что ты выбрал нарочно.