Cache-aside паттерн
![Cache-aside паттерн](https://files.bool.dev/site/blog/8024fbde-92a4-4c39-b978-86309c9f3132/meta.jpg)
Проблема
Приложения используют кэш для оптимизации повторного доступа к информации, хранящейся в хранилище данных. Однако обычно нецелесообразно ожидать, что кэшированные данные всегда будут полностью соответствовать данным в хранилище. Приложения должны реализовывать стратегию, которая помогает обеспечить максимально возможную актуальность данных в кэше, но также может обнаруживать и обрабатывать ситуации, возникающие, когда данные в кэше устарели.
Решение
Многие коммерческие системы кэширования обеспечивают операции read-through и write-through/write-behind. В этих системах приложение получает данные, ссылаясь на кеш. Если данные не находятся в кэше, они извлекаются из хранилища данных и добавляются в кэш. Любые изменения данных, хранящихся в кеше, автоматически записываются обратно в хранилище данных.
Приложение может эмулировать функциональность сквозного кэширования. Эта стратегия эффективно загружает данные в кэш по требованию. Рисунок ниже описывает шаги в этом процессе:
![cashe aside](https://files.bool.dev/site/blog/17c38884-55d0-4522-907e-28d6b86d40fc/cache-aside.jpg)
- Определяет, есть ли айтем в кэше
- Если элемент отсутствует в кэше, читаем запись из хранилища данных
- Помещаем копию записи в кэш
Кейсы:
Время жизни кэша
![время жизни кэша](https://files.bool.dev/site/blog/8024fbde-92a4-4c39-b978-86309c9f3132/live.jpg)
Многие кэши реализуют "expiration policy", которая приводит к тому что данные становятся не валидными и удаляются с кэша. Чтобы Cache-aside был эффективным, убедитесь, что политика истечения срока действия, соответствует тому как это реализовано в приложении и как используются эти данные. Не делайте срок действия слишком коротким, потому что это может привести к тому, что приложения будут постоянно извлекать данные из хранилища данных и добавлять их в кэш. Точно так же не делайте период истечения настолько длинным, чтобы кэшированные данные могли устареть. Помните, что кэширование наиболее эффективно для относительно статических данных или данных, которые часто читаются.
Evicting Data
![evicting data](https://files.bool.dev/site/blog/8024fbde-92a4-4c39-b978-86309c9f3132/v.jpg)
Большинство кэшированных данных имеют ограниченный размер по сравнению с хранилищем данных, из которого они были получены, и при необходимости они удаляют данные. Большинство кешей применяют политику least-recently-used, которая используется для удаления данных из кэша, в большинстве случаев это можно настроить. Настройте глобальное свойство expiration и другие свойства кэша, а также свойство expiration каждого кэшируемого элемента, чтобы обеспечить экономическую эффективность кэша. Возможно, не всегда целесообразно применять глобальную политику удаления к каждому элементу в кэше. Например, если кэшированный элемент очень дорогой для извлечения из хранилища данных, может быть полезно сохранить этот элемент в кэше за счет более часто используемых, но менее дорогих элементов.
Priming the Cache
![Priming the Cache](https://files.bool.dev/site/blog/8024fbde-92a4-4c39-b978-86309c9f3132/vot.gif)
Многие решения заполняют кэш данными, которые, вероятно, понадобятся приложению при обработке запуска. Шаблон Cache-Aside все еще может быть полезен, если некоторые из этих данных экспайрятся или удаляются.
Consistency
![cons](https://files.bool.dev/site/blog/8024fbde-92a4-4c39-b978-86309c9f3132/cons.jpg)
Реализация Cashe-Aside паттерна не гарантирует согласованности между хранилищем данных и кэшем. Объект в хранилище данных может быть изменен в любое время сторонними процессами. Это изменение может не отразиться в кэше до следующей загрузки элемента в кэш. В системе которая реплицирует данные между хранилищами данных, эта проблема может стать особенно сильной, если синхронизация происходит очень часто.
In-Memory Cache
![in memory cache](https://files.bool.dev/site/blog/8024fbde-92a4-4c39-b978-86309c9f3132/inmem.jpg)
Кэш приложения может быть локальным для приложения и храниться в памяти инстанса приложения. 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