惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Help Net Security
T
ThreatConnect
SecWiki News
SecWiki News
F
Future of Privacy Forum
AWS News Blog
AWS News Blog
C
Cisco Blogs
A
Arctic Wolf
Vercel News
Vercel News
The GitHub Blog
The GitHub Blog
Scott Helme
Scott Helme
V
V2EX
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
K
Kaspersky official blog
G
Google Developers Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
P
Privacy International News Feed
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News | PayPal Newsroom
Schneier on Security
Schneier on Security
NISL@THU
NISL@THU
Microsoft Azure Blog
Microsoft Azure Blog
量子位
The Hacker News
The Hacker News
Stack Overflow Blog
Stack Overflow Blog
Security Latest
Security Latest
M
Microsoft Research Blog - Microsoft Research
Google Online Security Blog
Google Online Security Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
I
InfoQ
Google DeepMind News
Google DeepMind News
Y
Y Combinator Blog
The Cloudflare Blog
Microsoft Security Blog
Microsoft Security Blog
Martin Fowler
Martin Fowler
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Troy Hunt's Blog
F
Fox-IT International blog
S
Security @ Cisco Blogs
博客园 - 司徒正美
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
C
Comments on: Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
L
LINUX DO - 最新话题
GbyAI
GbyAI
Project Zero
Project Zero
腾讯CDC
T
Tailwind CSS Blog

Все публикации подряд на Хабре

Почему российские компании остаются на серой Jira [Перевод] Тонус в реактивных системах Факап инженера АСУ ТП, как мы перепутали физические COM-порты на подстанции [Перевод] Как уместить DOOM в QR-код Щелевая коррозия: порча нержавейки и «ржавые» имплантаты — почему это происходит? Строим первую линию техподдержки на n8n за 250$ в месяц. Часть 2 Покопались в .cursorrules на GitHub и нашли там волка-фурри, Star Trek и 28.7% копипасты Не наступайте на наши грабли, если собираетесь использовать Temporal Как создать дебат-клуб в компании: пошаговое руководство от бизнес-тренера Как экономят на метановых автозаправках Самодельный elgato-like макропад. Часть 1, железная Всё есть код, или зачем внедрять GitOps в разработку Как получить root на Urovo DT40 Pro (CT48): Android 12 (Проверено на практике) C# мне нравится больше Java. Но в банковском enterprise мне всё равно понадобилась Java Биткоин на Московской бирже — что это? Как мы переводим миллионы iOS-пользователей на новое приложение каждые несколько месяцев Кейс. Zero Bug Policy: как мы снизили бэклог багов в 4 раза за месяц Shadow AI: 80% сотрудников уже пишут в ChatGPT. Почему мы делим задачи на красные, зелёные и серые Попытка пересмотреть ограничения рынка тяжелых БАС: нужен ли вообще кому-то легкий и дешевый электромотор Менеджер, который хакнул систему. И что AI на самом деле умножает Spec-driven development в микросервисах, часть 2: как archspec делает контекст сервисов явным Запись в Kubernetes: как контроллеры учились не перезаписывать друг друга Игровой движок 2.5D, короткие тренировки для ПК-пользователей –и еще 8 российских стартапов MCP в системе управления проектами: как поручить ИИ работу с корпоративными данными Бэклог болей: как hh работает с тем, что не нравится пользователям brec: контролируемая обратная совместимость протокола AI обнулил benchmark и пытался шантажировать инженера. И почему это решаемо Почему пластиковый корпус оказался в 3 раза дороже металлического Как спроектировать API, которое не придется переписывать через полгода Трекинг посетителей на fisheye-камерах: задача “со звездочкой” Красивый скриншот вашего кода. Большое обновление Я создаю проекты без единого созвона с командой Content Pipeline в MonoGame: почему я его не использую Гемблинг партнерки: Как выбрать, ТОП 5 в 2026 За пределами LLM, часть 2: якорная таблица Кэли, которая не является ни полем, ни моноидом Pixverse купить подписку: для чего нужна Пиксверс подписка, как выбрать тариф и оплатить в рублях Meshy AI нейросеть: как создавать 3D-модели из текста и изображений в Меши АИ на русском бесплатно Skywork AI: как использовать Скайворк АИ нейросеть на русском бесплатно, работать с промтами и создавать видео Технотекст 8: победа естественного интеллекта Capacitor: от веба к мобильным приложениям. Часть 4. Интегрируем локальный LLM в проект 20 лет видеокарт в цифрах: как росли FLOPS и TDP и кто вёл в дуэли NVIDIA vs AMD (+ открытый датасет на 13 500 GPU) Архитектура крипто-сканера для биржи: Open Interest, Funding Rate, EMA и MACD в реальном времени @tanstack/vue-table: почему я почти отказался от этого… WHERE превращает ваш LEFT JOIN в INNER JOIN. И никто вам об этом не скажет Гравитация не существует. Вы задали 454 вопроса о времени. Вот ответы с уравнениями Эйнштейна Конец бесплатного кремния: как Google AI Studio превратилась из рая для инженеров в симулятор смены аккаунтов Свой AI-агент из почты, systemd и LLM MemForge2: загрузочная флешка, которая за минуту говорит — какую планку памяти менять Лицензии важны. Разбор ошибок авторов и пользователей программ От RAG-прототипа к агенту в продакшн: путь по метрикам, а не по моде Serial Terminal: кастомный веб-терминал для последовательного порта на Web Serial API Китайский стартап GigaAI обещает робота-домработника за 1 млн рублей уже в 2027 году — правда или PR? Open-source VPN клиент Tunguska Роман за 6 недель без идеи на старте: миф или реальность? ИИ построит ваш план действий за 10 секунд Security Week 2622: эффективность Claude Mythos по версии Cloudflare Reactive Forms vs Signal Forms: Эволюция сложных форм в Angular TorFlash — приложение для Linux: поиск торрентов, скачивание и копирование на флешку в одно нажатие Как я решил проблему русской диктовки для ИИ Оверинжиниринг, потопивший немецкую подлодку или некоторые «баги» не чинятся десятилетиями Как ставить цели и не забывать о них: пошаговая система с примерами в таск-менеджере Как настроить observability в Spring Boot 3 HackTheBox. Прохождение Mini Pro Lab Puppet Обзор серверного ускорителя NVIDIA Tesla V100 16 Gb в корпусе от RTX 4090: Часть 3 — Запуск локальных моделей ИИ Редактирование текста нейросетью: как сделать диплом и курсовую более человечными Самодельный ARM ноутбук, реально ли? Как 100+ авторов пишут 100+ процессов в 3 версиях и не путаются. Или как мы переехали с Wiki на Git Прошла AnalystDays – хорошие выступления и нетворкинг VSCode как IDE для embedded разработки Моделирование широкополосной антенны с двойной круговой поляризацией и высокой изоляцией Ваше прошлое физически существует прямо сейчас. И вы заморожены там навсегда От списка инструментов к technical output: как security engineer’у описывать hands-on опыт в CV и на интервью I just want an agent. Часть 1. Как я научил ИИ собирать ИИ-агентов за пользователей и выиграл конкурс I just want an agent. Часть 1. Как я научил ИИ собирать ИИ-агентов за пользователей и выиграл конкурс Вайбкодинг спас меня от подрядчиков. А потом я поняла, что сама стала подрядчиком для своих агентов Святой Августин и GAN: почему борьба добра и зла — это генеративная состязательная сеть В каждом QR-коде зашита половина лишней информации. Намеренно Я открываю автомат ключом, меняю рулон бумаги и зарабатываю 180 тысяч в месяц с точки Мастер восстановления. Культура достиженства и выгорание Недельный геймдев: #279 — 24 мая, 2026 Защита от дублирования кода агентами: семантические концепции Frontend Status: свежий дайджест фронтенда и AI — 25.05.2026 Где искать IT-работу кроме HH: подборка платформ 2026 Почему простые числа собираются в спирали? OCR для Data Lakehouse: от Apache Tika к собственному решению на базе Docling Jira — Тьюринг-полная Kubernetes-аудит после Wiz и Prisma: как живут без CNAPP в 2026 «Тестируем MVP в 4 раза быстрее»: как нейросети изменили жизнь предпринимателей На каком стеке и железе работает умное наблюдение в вашем городе: обзор технологий от разработчиков видеоаналитики Как мы ускорили согласования на двух заводах в 24 раза Heartbeat-мониторинг cron-job'ов: dead-man-switch на FastAPI [Перевод] Сегодня нет джуниоров, а в 2031 году не станет и синьоров Профайлер для PostgreSQL: от идеи до работающего MVP за сутки [Перевод] Ограничения размера cookie в ASP.NET Core в продакшене: причины и способы решения Проблема «божественного» Obsidian: почему я отказался от централизованного подхода в работе Лицензии GNU GPL: как пройти проверку Минцифры и заказчика для госзакупок и КИИ Хакатон Samsung IT Academy Hack 2026: как студенты оптимизировали поиск в корпоративном мессенджере Хакатон Samsung IT Academy Hack 2026: как студенты оптимизировали поиск в корпоративном мессенджере MTProxy jumper — делаем автоматическое переключение прокси-серверов Telegram Ты уже используешь агента. Просто не заметил
Cache is hard — почему инвалидация кэша — это проблема согласованности, а не производительности
Andrey_Biryu · 2026-05-26 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение10 мин

Охват и читатели92

Обзор

Привет, Хабр! Меня зовут Андрей Бирюков. Я эксперт в области ИТ и ИБ, преподаю в учебных центрах и пишу книги. В сегодняшней статье мы поговорим об инвалидации кеша и способах решения связанных с этим проблем.

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

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

Великое заблуждение о кэше

Кеш можно назвать молотком, который есть у каждого архитектора, и в любой непонятной ситуации он может им воспользоваться. Например, база данных отвечает медленно, а пользователь не хочет ждать, значит нам нужно хранить копию данных где‑то поближе. В оперативной памяти сервера, в Redis, на диске. Данные меняются редко, а читаются часто. Действительно кажется, что это идеальный сценарий. Вы ставите кеш, поднимаете время ответа с двухсот миллисекунд до пяти. И все вроде бы довольны.

Только потом кто‑то обновляет данные, и начинается ад. Наш основной инструмент — старый добрый TTL (время жизни записи в кэше) — работает, только если вам можно отдавать устаревшие данные в течение нескольких секунд или минут. Для табло курса валют это нормально, а вот для списка товаров с ограниченным остатком уже нет. Вы представляете, что пользователь видит «в наличии 3 штуки», оформляет заказ, а через секунду получает отказ, потому что товар уже купил другой человек? Получается, что кэш солгал ему.

Инвалидация по событию означает, что при обновлении базы мы отправляем команду «удалить ключ из кэша». Вроде бы звучит правильно. Но на практике оказывается, что событие идёт по сети, и, пока оно дошло, другой запрос мог уже прочитать старое значение из базы и записать его обратно в кэш. Получаем состояние гонки. Или кэш на секунду упал, событие потерялось, и теперь в кэше навсегда поселилась старая версия. Либо инвалидация произошла, но перед этим кто‑то успел сделать запрос и положил в кэш устаревшие данные — и они будут жить до следующей инвалидации, которой уже не будет.

Парадокс кэширования формулируется просто: кэш делает систему быстрее ценой нарушения согласованности. Вопрос не в том, как сделать инвалидацию идеальной. Вопрос в том, какую степень несогласованности вы готовы принять и как сделать её предсказуемой.

Библиотечная проблема

Чтобы понять всю глубину проблемы, рассмотрим классический сценарий, который можно называть «библиотечной проблемой». Представьте онлайн‑библиотеку, в которой пользователь А смотрит карточку книги — кэш отдаёт «в наличии 1 экземпляр». Пользователь Б в тот же момент берёт эту книгу — выполняется запрос в базу, остаток уменьшается до нуля. Система отправляет событие инвалидации кэша. Но оно ещё не дошло до кэша, а пользователь А уже оформляет заказ, читая из кэша устаревшую единицу. Через секунду он получит отказ, потому что настоящий остаток ноль.

Пользователь А будет зол, так как он не поймёт, почему система показала доступность, а потом отменила заказ. С его точки зрения, система неработоспособна.

А в чём, собственно, проблема? Система не нарушила никаких законов физики: данные в базе согласованы, кэш обновится через мгновение. Но для пользователя А была продемонстрирована несогласованность, так как был момент времени, когда два пользователя видели разное состояние одного и того же объекта. Один видел единицу, другой — ноль. Оба были правы с точки зрения своих источников, но одновременно существовать эти две правды не могли.

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

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

Это и есть главная мудрость: некоторые данные не должны попадать в кэш с возможностью несогласованности.

Паттерн «lease»

Итак, мы пришли к ситуации, когда, с одной стороны, мы не можем обойтись без кеша, но при этом несогласованность для нас тоже недопустима. В таком случае нам на помощь приходят блокировки. Здесь не идет речь о полных блокировках всей базы, а о достаточно умном блокировании, позволяющем избежать гонок. Один из таких механизмов — паттерн «lease» (аренда).

Когда клиент хочет прочитать данные из кэша и готов полностью за них отвечать, он не просто читает значение. Он запрашивает у «владельца кэша» (отдельного сервиса или Redis с Lua‑скриптами) короткоживущий токен с правом обновления. За время жизни токена (например, 5 секунд) никакой другой клиент не может изменить этот ключ. Если за это время приходит событие о реальном обновлении данных в базе, оно ставится в очередь и ждёт, пока истечёт токен. Клиент, владеющий токеном, может обновить кэш, и только после этого токен освобождается.

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

Цена этого подхода — определённая сложность и задержки во время действия lease. Другие пользователи могут ждать освобождения кэша. Но в сценариях, где данные меняются нечасто и важна согласованность для каждого чтения (например, в биллинговых системах, при бронировании ресурсов), lease оправдан. Главный недостаток — lease требует централизованного координатора, который помнит о выданных токенах. Если этот координатор упал, система либо останавливается, либо теряет гарантии и возвращается к обычным гонкам.

Кеш как реплика

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

Отдельный процесс — Change Data Capture (CDC) — читает журнал транзакций базы (WAL, binlog) и транслирует каждое изменение в виде события в поток (Apache Kafka, RabbitMQ). Все кэши — их может быть несколько, в разных регионах — подписываются на поток и обновляют свои копии. Задержка между записью в базу и обновлением кэша — порядка десятков миллисекунд, предсказуема и конечна.

Преимущество этого подхода в том, что гонки «чтение старого значения + запись в кэш» просто не возникают. Потому что кэш никогда не читает данные из базы напрямую. Он только слушает события и обновляется. Если кэш упал и пропустил несколько событий, он при старте может подтянуть снапшот из базы и затем доесть события из Kafka с нужного смещения.

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

Рассмотрим небольшой пример: крупный сервис бронирования авиабилетов использует именно такую модель. База броней — PostgreSQL, CDC через Debezium в Kafka. Всего несколько десятков кэшей Redis по всему миру слушают изменения и показывают актуальные цены и остатки мест. Задержка от момента бронирования до обновления кэша во всех регионах — в среднем 120 миллисекунд, что для туриста незаметно. Но при этом система способна обрабатывать пики до 50 000 бронирований в секунду, а каждый билет виден консистентно во всех регионах с разницей в сотни миллисекунд.

Пламя, которое сожгло магазин

А теперь давайте разберём реальную историю, произошедшую в одном ритейлере. Компания имела классическую архитектуру: веб‑приложение, за ним — кэш Redis, за кэшем — PostgreSQL. Остатки товаров хранились в Redis с TTL 30 секунд и обновлялись из базы при промахе кэша. Инвалидация по событию вообще не использовалась — надеялись, что 30 секунд — допустимый люфт.

Так работало два года, но потом случилась распродажа на товар‑хит — игровую консоль. В течение двух минут пришло 5000 запросов. Представим, что на складе было 2000 единиц. Первые 2000 запросов прошли: Redis честно показывал остаток 2000, забирали. База тоже честно уменьшала остаток. Но Redis каждые 30 секунд перезагружал значение из базы.

Проблема возникла из‑за того, что Redis не блокировался во время обновления. Запрос номер 2001 пришёл в тот момент, когда Redis как раз перезагружал значение из базы — а база уже показывала 0. Но из‑за асинхронности Redis взял базы 0, записал в себя и с этого момента показывал всем «0». Всё правильно.

А вот запросы 2002–5000 получили 0 и ушли с пустыми руками. Но проблема была не в них. Проблема была в том, что пользователи, оформившие заказ в первые секунды, получили подтверждение, а через несколько минут — отмену. Почему? Потому что когда приложение получало запрос на покупку, оно сначала проверяло Redis. Если Redis говорил «есть остаток», приложение делало реальную резервацию в базе. В базе остаток был, блокировка проходила, заказ сохранялся. Но затем, через секунду, проверка антифрода отбраковывала заказ, потому что «несоответствие остатка» — какой‑то баг в интеграции.

В итоге честные покупатели получали подтверждение заказа и через 20 минут — письмо «к сожалению, товар закончился». В результате компания потеряла репутацию.

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

Кэшировать или не кэшировать: навигатор принятия решений

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

  • Первый вопрос. Что произойдёт, если пользователь увидит устаревшие данные? Если ничего страшного — например, аватарка пользователя загрузится на секунду старой — кэшируйте смело с любым TTL хоть в минуту. Если произойдёт финансовый ущерб, потеря заказа, юридические последствия — не кэшируйте эту сущность вообще, либо используйте только синхронную строгую согласованность с блокировками.

  • Второй вопрос. Как часто данные меняются относительно того, как часто они читаются? Классическая формула: кэш эффективен при соотношении чтений к записям больше 10 к 1. Если данные меняются чаще — кэш будет постоянно инвалидироваться и создавать нагрузку без пользы.

  • Третий вопрос. Готовы ли вы к распределённой отладке? Когда в системе есть кэш, база и, возможно, CDC, исчезает простая причинность. Вы не можете сказать «я обновил запись, теперь она везде такая». Вы можете сказать только «через некоторое время, если всё пойдёт нормально, она обновится везде». Если ваша команда не готова к такому уровню неопределённости и инструментам трассировки (распределённые трейсы, дашборды задержек), возможно, кэш добавит больше проблем, чем решений.

  • Четвёртый вопрос. Можете ли вы решить проблему без кэша? Часто оказывается, что медленная база данных — это симптом неправильных индексов, неоптимальных запросов или плохой схемы. Ускорение может принести вертикальное масштабирование (больше памяти, NVMe‑диски), использование read replica или изменение способа работы с данными. Кэш — это мощное, но опасное средство. Применяйте его, когда все более простые способы уже испробованы.

Делаем выводы

Кэширование — это всегда компромисс между скоростью и правдой. TTL даёт вам контролируемую ложь на заданный интервал. Инвалидация по событию пытается быть честной, но сталкивается с гонками и потерянными событиями. CDC и lease решают проблемы ценной сложности и координации.

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

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

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

Ближайшие уроки по теме:

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

И подписывайтесь на канал OTUS в MAX — там делимся анонсами открытых уроков, полезными материалами и новостями для IT-специалистов.