The Distributed Cache
Problem statement
Типичная система состоит из следующих компонентов:
- Клиент, который запрашивает услугу.
- Один или несколько серверов, которые обрабатывают запросы клиента.
- База данных, используемая сервисом для хранения данных.
При нормальных условиях эта абстракция работает хорошо. Однако, с увеличением числа пользователей, количество запросов к базе данных также увеличивается. В результате, серверы перегружаются, что приводит к замедлению производительности.
В таких случаях для предотвращения ухудшения производительности в систему добавляется кэш (cache). Кэш - это временное хранилище данных, которое может обслуживать запросы быстрее, сохраняя наиболее часто запрашиваемые данные в памяти. Когда запрос поступает на сервер, он извлекает данные из кэша (cache hit) и обслуживает пользователя. Однако, если данные недоступны в кэше (cache miss), они запрашиваются из базы данных. Также, кэш пополняется новым значением, чтобы избежать промахов кэша в следующий раз.

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

Мы понимаем необходимость использования кэша и подходящего аппаратного обеспечения для хранения, но что такое распределенный кэш (distributed cache)? Давайте обсудим это далее.
Что такое распределенный кэш?
Распределенный кэш - это система кэширования, в которой несколько серверов кэша координируют свою работу для хранения часто запрашиваемых данных. Распред еленные кэши необходимы в тех случаях, когда один сервер кэша недостаточен для хранения всех данных. Такая система масштабируема и гарантирует более высокий уровень доступности.
Кэши, как правило, представляют собой небольшое, часто используемое, краткосрочное хранилище с быстрым временем чтения. Кэши используют принцип локальности ссылок (locality of reference).
В чем польза распределенных кэшей?
Распределенные кэши полезны по следующим причинам:
- Они минимизируют задержку, воспринимаемую пользователем, путем предвычисления результатов и хранения часто запрашиваемых данных.
- Они предварительно генерируют сложные запросы из базы данных.
- Они временно хранят данные сессий пользователя.
- Они предоставляют данные из временного хранилища, даже если основное хранилище временно недоступно.
- Наконец, они сокращают сетевые затраты, обслуживая данные из локал ьных ресурсов.
Почему распределенный кэш?
Когда объем данных, необходимых для кэширования, увеличивается, хранение всех данных в одной системе становится непрактичным по следующим трем причинам:
- Это может стать потенциальной единой точкой отказа (SPOF).
- Система спроектирована слоями, и каждый слой должен иметь свой механизм кэширования, чтобы обеспечить декуплирование чувствительных данных между различными слоями.
- Кэширование в различных точках помогает сократить задержку обслуживания на соответствующем слое.
Кэширование на разных слоях системы
В таблице ниже описано, как кэширование на различных слоях системы осуществляется с использованием различных технологий. Важно отметить, что компоненты key-value store (хранилище типа "ключ-значение") используются на различных уровнях.
| Слой системы | Используемая технология | Назначение |
|---|---|---|
| Веб | HTTP заголовки кэша, веб-ускорители, key-value store, CDN и т.д. | Ускорение получения статического веб-контента, управление сессиями |
| Приложение | Локальный кэш и key-value store | Ускорение вычислений на уровне приложения и извлечение данных |
| База данных | Кэш базы данных, буферы, и key-value store | Сокращение задержки при извлечении данных и уменьшение нагрузки на I/O базы данных |
Помимо трех вышеупомянутых слоев системы, кэширование также осуществляется на уровне DNS и клиентских технологий, таких как браузеры или конечные устройства.
Как мы будем проектировать распределенный кэш?
Мы разделим задачу проектирования и углубленного изучения основных концепций распределенного кэша на пять уроков:
- Основы распределенного кэша: Необходимо создать базовые знания, необходимые для принятия ключевых решений при проектировании распределенного кэша. Этот урок повторит некоторые базовые, но важные концепции.
- Высокоуровневое проектирование распределенного кэша: В этом уроке мы построим высокоуровневую архитектуру распределенного кэша.
- Детализированное проектирование распределенного кэша: Мы выявим некоторые ограничения нашего высокоуровневого дизайна и будем работать над созданием масштабируемого, доступного и производительного решения.
- Оценка проектирования распределенного кэша: Этот урок оценит наш проект по различным нефункциональным требованиям, таким как масштабируемость, консистентность, доступность и т.д.
- Memcached против Redis: Мы обсудим известные промышленные решения, такие как Memcached и Redis. Также рассмотрим их детали и сравним их функции, чтобы понять потенциальные сценарии использования и их связь с нашим проектом.
Как работают распределенный кеш и CDN вместе
-
Рекомендации на основе ML:
- Распределенный кеш используется для хранения результатов работы модели машинного обучения (ML), которая, например, вычислила, какие видео будут интересны конкретному пользователю или когорте пользователей. В этом случае в кеше хранится список ID рекомендованных видео.
-
Обращение к кешу:
- Когда пользователь заходит в приложение или на веб-страницу, где отображаются рекомендации, приложение сначала обращается к распределенному кешу. Кеш возвращает список ID видео, что позволяет избежать повторного вычисления рекомендаций и обеспечивает быстрое реагирование системы.
-
Поиск видео в CDN:
- После получения списка ID видео из кеша приложение начинает запрашивать сами видеофайлы. Для этого оно обращается к CDN (Content Delivery Network), где уже хранятся видео. CDN доставляет видеофайлы пользователю с ближайшего к нему сервера, что минимизирует задержку и обеспечивает высокую скорость загрузки.
Почему оба подхода важны
-
Распределенный кеш:
- Используется для хранения динамически изменяющихся данных (например, рекомендации, сессии пользователей), что ускоряет работу приложения и снижает нагрузку на базу данных.
-
CDN:
- Эффективен для доставки статического контента (например, видеофайлов, изображений) пользователям по всему миру с минимальной задержкой.
Пример процесса взаимодействия
- Пользователь открывает приложение.
- Приложение обращается к распределенному кешу, чтобы получить список рекомендованных видео.
- Приложение запрашивает первое видео из списка в CDN.
- Видео быстро доставляется с ближайшего сервера CDN и отображается пользователю.
Важность обоих инструментов
Таким образом, распределенный кеш обеспечивает быстрое получение рекомендаций и другой динамической информации, а CDN гарантирует, что сами видео или другие статические ресурсы будут доставлены пользователю с минимальной задержкой. Эти два инструмента вместе делают пользовательский опыт быстрым и персонализированным.
Background of Distributed Cache
Основная цель этой главы — спроектировать распределенный кэш. Для достижения этой цели необходимо обладать значительными знаниями о различных техниках чтения и записи данных. Этот урок поможет нам построить такую базу знаний. Давайте рассмотрим структуру этого урока в таблице ниже.
Структура урока
| Раздел | Описание |
|---|---|
| Политики записи | Данные записываются в кэш и базы данных. Порядок записи данных влияет на производительность. Мы обсудим различные политики записи, чтобы определить, какая из них будет наиболее подходящей для проектируемого распределенного кэша. |
| Политики вытеснения | Так как кэш построен на ограниченном хранилище (RAM), мы стремимся хранить в нем наиболее часто запрашиваемые данные. Поэтому мы обсудим разные политики вытеснения, чтобы заменять менее часто используемые данные на наиболее востребованные. |
| Инвалидация кэша | Некоторые кэшированные данные могут устаревать. Мы рассмотрим различные методы инвалидации, чтобы удалять устаревшие ил и неактуальные записи из кэша. |
| Механизм хранения | В распределенном хранилище много серверов. Мы обсудим важные проектные аспекты, такие как на каком сервере хранить ту или иную запись кэша и какую структуру данных использовать для хранения. |
| Клиент кэша | Сервер кэша хранит записи кэша, но клиент кэша обращается к серверу кэша с запросом данных. В этом разделе мы рассмотрим детали клиентской библиотеки кэша. |
Политики записи
Часто кэш хранит копию (или часть) данных, которые сохраняются в постоянном хранилище (например, в базе данных). Когда мы записываем данные в базу данных, возникают важные вопросы:
- Куда мы сначала записываем данные: в базу данных или в кэш?
- Какие последствия каждая из стратегий записи имеет для моделей консистентности?
Краткий ответ заключается в том, что это зависит от требований приложения. Рассмотрим подробнее различные политики записи, чтобы лучше понять концепцию:
-
Write-through cache: В механизме write-through запись происходит как в кэш, так и в базу данных. Запись в оба хранилища может происходить одновременно или последовательно. Это увеличивает задержку записи, но обеспечивает строгую консистентность между базой данных и кэшем.
-
Write-back cache: В механизме write-back cache данные сначала записываются в кэш, а затем асинхронно записываются в базу данных. Хотя кэш содержит обновленные данные, неизбежна ситуация, когда клиент может прочитать устаревшие данные из базы данных. Однако системы, использующие эту стратегию, будут иметь минимальную задержку при записи.
-
Write-around cache: Эта стратегия предполагает запись данных только в базу данных. Позже, при запросе этих данных, они записываются в кэш после возникновения cache miss (промаха кэша). В этом случае база данных будет содержать актуальные данные, но такая стратегия не подходит для чтения недавно обновленных данных.

Политики вытеснения
Одна из главных причин высокой производительности кэшей заключается в их небольшом размере. Однако ограниченная емкость кэша требует механизма вытеснения данных, чтобы удалять менее часто запрашиваемые данные из кэша.
Существует несколько хорошо известных стратегий вытеснения данных из кэша. Среди них наиболее популярны следующие:
- Least Recently Used (LRU) — Вытесняются данные, которые не использовались дольше всех.
- Most Recently Used (MRU) — Вытесняются данные, которые использовались последними.
- Least Frequently Used (LFU) — Вытесняются данные, которые использовались наименее часто.
- Most Frequently Used (MFU) — Вытесняются данные, которые использовались наиболее часто.
Существуют также другие стратегии, такие как First In, First Out (FIFO). Выбор алгоритма зависит от системы, для которой разрабатывается кэш.
Классификация данных по частоте доступа
Данные могут быть классифицированы в зависимости от частоты доступа на три температурные области:
- Горячие данные (Hot): Данные, к которым часто обращаются.
- Теплые данные (Warm): Данные, к которым обращаются реже.
- Холодные данные (Cold): Данные, к которым обращаются очень редко.
Холодные данные часто вытесняются из кэша и заменяются горячими или теплыми данными.

Инвалидация кэша
Помимо вытеснения менее часто запрашиваемых данных, некоторые данные, находящиеся в кэше, могут со временем устаре ть. Такие записи кэша считаются неактуальными и должны быть помечены для удаления.
Возникает вопрос: как определить устаревшие записи?
Для решения этой проблемы необходимо хранить метаданные для каждой записи в кэше, в частности, поддерживать значение time-to-live (TTL), чтобы справляться с устаревшими элементами кэша.
Существуют два подхода к работе с устаревшими элементами, используя TTL:
- Активное истечение (Active expiration): Этот метод активно проверяет TTL записей в кэше через процесс или поток-демон.
- Пассивное истечение (Passive expiration): Этот метод проверяет TTL записи кэша в момент обращения к ней.
Каждый просроченный элемент удаляется из кэша при обнаружении.
Механизм хранения
Хранение данных в кэше не так просто, как кажется, особенно когда распределенный кэш имеет несколько серверов. При использовании нескольких серверов кэша необходимо ответить на следующие вопросы:
- Какие данные следует хранит ь на каких серверах кэша?
- Какую структуру данных использовать для хранения?
Эти вопросы важны, поскольку они будут определять производительность нашего распределенного кэша, что является нашим главным требованием. Для ответа на эти вопросы мы будем использовать следующие техники.
Хеш-функция
Возможны два сценария использования хеширования:
- Определение сервера кэша в распределенном кэше для хранения и извлечения данных.
- Поиск записей кэша внутри каждого сервера кэша.
Для первого сценария можно использовать различные алгоритмы хеширования. Однако consistent hashing или его варианты обычно хорошо работают в распределенных системах, поскольку простое хеширование не будет оптимальным в случае сбоев или масштабирования.
Во втором сценарии можно использовать типичные хеш-функции для поиска записи кэша при чтении или записи внутри сервера кэша. Однако сама по себе хеш-функция только определяет местоположение записи в кэше. Она ничего н е говорит о том, как управлять данными внутри сервера кэша, например, о стратегии вытеснения менее часто используемых данных или о том, какие структуры данных используются для хранения данных на серверах кэша.
Связанный список
Мы будем использовать двусвязный список. Основная причина — его широкое применение и простота. Кроме того, добавление и удаление данных из двусвязного списка в нашем случае будет операцией с постоянным временем. Это потому, что мы либо вытесняем конкретную запись с хвоста списка, либо перемещаем запись в начало списка. Таким образом, итерации не требуются.
Примечание: Bloom filters — интересный выбор для быстрого определения, существует ли запись кэша на сервере. Они могут использоваться для определения того, что запись кэша определенно отсутствует на сервере, но вероятность её присутствия является вероятностной. Bloom filters весьма полезны в больших системах кэширования или базах данных.
Шардинг в кэш-кластерах
Чтобы избежать единой точки отказа (SPOF) и высокой нагрузки на один экземпляр кэша, мы вводим шардинг. Шардинг включает в себя разделение данных кэша между несколькими серверами кэша. Он может выполняться двумя способами:
- Выделенные серверы кэша:
- В этом методе мы отделяем серверы приложений и веб-сервера от серверов кэша.
- Преимущества выделенных серверов кэша:
- Гибкость в выборе аппаратного обеспечения для каждой функции.
- Возможность масштабировать веб/приложение и серверы кэша отдельно.
- Кроме того, работа в качестве автономной службы кэширования позволяет другим микросервисам использовать её, например, кэш как услуга (Cache as a Service). В этом случае система кэширования должна быть осведомлена о разных приложениях, чтобы их данные не пересекались.

- Совместное размещение кэша (Co-located cache):
- Кэш и сервисная функциональность находятся на одном хосте.
- Преимущество этой стратегии — сокращение капитальных (CAPEX) и операционных затрат (OPEX) на дополнительное оборудование. Кроме того, при масштабировании одной службы автоматически масштабируется и другая служба. Однако сбой одной машины приведет к одновременному отключению обеих служб.

Клиент кэша
Мы обсуждали, что хеш-функции следует использовать для выбора серверов кэша. Но какой элемент выполняет эти вычисления?
Клиент кэша — это фрагмент кода, находящийся на серверах, который выполняет вычисления (хеширование) для хранения и извлечения данных на серверах кэша. Также клиенты кэша могут координировать работу с другими компонентами системы, такими как службы мониторинга и конфигурации. Все клиенты кэша запрограммированы одинаково, чтобы одни и те же операции PUT и GET от разных клиентов возвращали одинаковые результаты. Некоторые характеристики клиентов кэша:
- Каждый клиент кэша знает обо всех серверах кэша.
- Все клиенты могут использовать известные транспортные протоколы, такие как TCP или UDP, для общения с серверами кэша.
What will be the behavior of cache clients to an access request if one of the cache servers is dead?
Details
Since the data within the cache servers will no longer be available, cache clients will mark that access request as a cache miss.
High-level Design of a Distributed Cache
Требования
Начнем с понимания требований к нашему решению.