788просмотров
10 декабря 2025 г.
question📷 ФотоScore: 867
Что такое кэш?
Кэш - это промежуточное хранилище данных для ускорения повторных обращений к ресурсу с высокой латентностью или высокой стоимостью (CPU, I/O, деньги, рейт-лимиты) Кэш - не оптимизация. Это архитектурный компромисс: мы жертвуем строгой консистентностью ради скорости и отказоустойчивости
Он решает три ключевые задачи:
1. Снижение latency - вместо 300 ms к стороннему api - 0.2 ms из кэша 2. Снижение нагрузки - не ходим каждый раз в сторонний ресурс для получения данных, а получаем из кэша 3. Защита от внешних зависимостей - если сторонняя api упала, можно отдавать stale, но рабочие данные Stale данные
Это компромисс который мы применяем для управления свежестью данных, вместо всегда актуальны переходим к достаточно актуальны. Но при этом гарантируем что в конечном моменте данные будут согласованны
Для чего это нужно:
Отказаться от синхронных обновлений при каждом изменении
Избежать каскадных сбоев при недоступности источника
Сгладить пики нагрузки (например, при запуске закупа рекламы) TTL (time-to-live)
Наиболее часто используемый подход: устанавливаем время жизни записи в кэше - и после его окончания данные либо удаляются, либо считаются недействительными. Что даёт: Простая реализация, set с ttl n - и всё
Предсказуемое поведение: нагрузка на источник равномерно распределяется во времени (при рандомизированном ttl)
Отказоустойчивость: если пайплайн инвалидации сломался - кэш всё равно обновится сам Но:
Нет гарантии актуальности данных
Нет реакции на изменение в реальном времени
Сложно подобрать правильное значение ttl Когда обновлять кэш? 🔍 Cache-aside (lazy loading)
Приложение само ходит в источник, когда в кэше ничего нет. Алгоритм исполнения:
Ищем данные в кэше, если нашли, то возвращаем их
Идем в бд за данными
Обновляем данные и возвращаем их
Например так работает memcached Этот подходи позволяет кэшировать только часто запрашиваемые данные, что позволяет сохранять память, но имеет такие минусы:
Создание кэша на каждый запрос на новые данные, которых нет в кэше - повышение latency
Stale данные если кэш еще жив, а в бд данные обновлены
При падение или смене ноды, нужно заново прогревать кэш 🔍Write-through
Кэш - основное место для записи. При SET данные синхронно уходят в БД
Имеем строгую консистентность данных, stale данных почти нет, но жертвуем скоростью записи в бд и лишними данными в кэше, если запись редко читается. 🔍 Write-behind
Запись идёт в кэш, а в БД - асинхронно (batch, delay, по расписанию)
Имеем очень быструю запись и уменьшенную нагрузку на бд, но сложно реализовать правильно. 🔍 Refresh-ahead (background refresh)
Кэш сам, по таймеру или правилам, обновляет горячие записи до истечения ttl, пример:
Если оставшийся ttl < n от общего времени - запускаем фоновое обновление
Если запись запрашивали больше m раз за n времени - помечаем как горячую и обновляем Плюсы: Почти нет увеличения latency Плавное распределение нагрузки Минусы:
Сложно определять горячие ключи
Можем рефрешить лишние данные 🔍 Event-driven invalidation При изменении данных (например, через CDC или domain event) публикуем событие UserUpdated(id=123), которое: Удаляет (или обновляет) кэш по ключу user:123
Может инвалидировать зависимые кэши (feed:123, search_index:user:123) Опасности: Потеря события - stale навсегда
Гонка данных: set cache - invalidate - set cache с устаревшим значением Consistent hashing Consistent hashing - техника для распределенных систем, представленная кольцом распределения, помогает избегать перераспределения кучи данных при добавление/удаление нод.
Как работает
Хэш-кольцо - кольцо [0, 2³²) (или 2⁶⁴, зависит от хэш-функции). Ноды маппятся на кольцо: Для каждой физической ноды создаётся несколько виртуальных реплик (например, 100–200): Каждый хэш - точка на кольце
Ключ маппится на кольцо: pos = hash(key). Выбор ноды: идём по кольцу по часовой стрелке от pos, пока не найдём первую ноду (её виртуальную точку)