Cache-aside паттерн
Проблема
Приложения используют кэш для оптимизации повторного доступа к информации, хранящейся в хранилище данных. Однако обычно нецелесообразно ожидать, что кэшированные данные всегда будут полностью соответствовать данным в хранилище. Приложения должны реализовывать стратегию, которая помогает обеспечить максимально возможную актуальность данных в кэше, но также может обнаруживать и обрабатывать ситуации, возникающие, когда данные в кэше устарели.
Решение
Многие коммерческие системы кэширования обеспечивают операции read-through и write-through/write-behind. В этих системах приложение получает данные, ссылаясь на кеш. Если данные не находятся в кэше, они извлекаются из хранилища данных и добавляются в кэш. Любые изменения данных, хранящихся в кеше, автоматически записываются обратно в хранилище данных.
Приложение может эмулировать функциональность сквозного кэширования. Эта стратегия эффективно загружает данные в кэш по требованию. Рисунок ниже описывает шаги в этом процессе:
- Определяет, есть ли айтем в кэше
- Если элемент отсутствует в кэше, читаем запись из хранилища данных
- Помещаем копию записи в кэш
Кейсы:
Время жизни кэша
Многие кэши реализуют "expiration policy", которая приводит к тому что данные становятся не валидными и удаляются с кэша. Чтобы Cache-aside был эффективным, убедитесь, что политика истечения срока действия, соответствует тому как это реализовано в приложении и как используются эти данные. Не делайте срок действия слишком коротким, потому что это может привести к тому, что приложения будут постоянно извлекать данные из хранилища данных и добавлять их в кэш. Точно так же не делайте период истечения настолько длинным, чтобы кэшированные данные могли устареть. Помните, что кэширование наиболее эффективно для относительно статических данных или данных, которые часто читаются.
Evicting Data
Большинство кэшированных данных имеют ограниченный размер по сравнению с хранилищем данных, из которого они были получены, и при необходимости они удаляют данные. Большинство кешей применяют политику least-recently-used, которая используется для удаления данных из кэша, в большинстве случаев это можно настроить. Настройте глобальное свойство expiration и другие свойства кэша, а также свойство expiration каждого кэшируемого элемента, чтобы обеспечить экономическую эффективность кэша. Возможно, не всегда целесообразно применять глобальную политику удаления к каждому элементу в кэше. Например, если кэшированный элемент очень дорогой для извлечения из хранилища данных, может быть полезно сохранить этот элемент в кэше за счет более часто используемых, но менее дорогих элементов.
Priming the Cache
Многие решения заполняют кэш данными, которые, вероятно, понадобятся приложению при обработке запуска. Шаблон Cache-Aside все еще может быть полезен, если некоторые из этих данных экспайрятся или удаляются.
Consistency
Реализация Cashe-Aside паттерна не гарантирует согласованности между хранилищем данных и кэшем. Объект в хранилище данных может быть изменен в любое время сторонними процессами. Это изменение может не отразиться в кэше до следующей загрузки элемента в кэш. В системе которая реплицирует данные между хранилищами данных, эта проблема может стать особенно сильной, если синхронизация происходит очень часто.
In-Memory Cache
Кэш приложения может быть локальным для приложения и храниться в памяти инстанса приложения. Cache-aside может быть полезным в этой конфигурации, при условии если приложение постоянно обращается к одним и тем же данным. Однако локальный кэш является частным, и поэтому каждый экземпляр приложения может иметь копию одних и тех же кэшированных данных. Эти данные могут быстро стать несовместимыми между кешами, поэтому может потребоваться сброс данных, хранящихся в частном кеше, и более частое его обновление. В этих сценариях может оказаться целесообразным использовать shared или distributed кэш механизма.
Рассмотрим пример реализации Cache-aside в C#
private static ConcurrentDictionary<string, object> concurrentDictionary = new ConcurrentDictionary<string, object>();
public static T CacheAside<T>(this ICacheManager cacheManager, Func<T> execute, TimeSpan? expiresIn, string key)
{
var cached = cacheManager.Get(key);
if (EqualityComparer<T>.Default.Equals(cached, default(T)))
{
object lockOn = concurrentDictionary.GetOrAdd(key, new object());
lock (lockOn)
{
cached = cacheManager.Get(key);
if (EqualityComparer<T>.Default.Equals(cached, default(T)))
{
var executed = execute();
if (expiresIn.HasValue)
cacheManager.Set(key, executed, expiresIn.Value);
else
cacheManager.Set(key, executed);
return executed;
}
else
{
return cached;
}
}
}
else
{
return cached;
}
}
Когда использовать этот шаблон
Используйте этот шаблон, когда:
- Кэш не обеспечивает собственные операции чтения и записи.
- Потребность в ресурсах непредсказуема. Этот шаблон позволяет приложениям загружать данные по требованию. Он не делает никаких предположений о том, какие данные приложение будет требовать заранее.
Не используйте этот шаблон, когда:
- Когда кэшированный набор данных является статическим. Если данные помещаются в доступное пространство кеша, заполните кеш данными при запуске и заюзайте политику, которая предотвращает истечение срока действия данных в кэше (expire policy).
- Для кэширования информации о состоянии сеанса в веб приложении. В этом случае лучше избегать зависимостей, которые базируются на client-server affinity