Почему In-Memory Cache не рекомендуют использовать в REST API?

Ответ

In-Memory Cache (например, IMemoryCache в ASP.NET Core) не рекомендуется для кэширования в масштабируемых REST API по нескольким ключевым причинам:

  1. Отсутствие распределенности: Кэш хранится в памяти отдельного экземпляра приложения. При горизонтальном масштабировании (несколько инстансов за балансировщиком) данные в кэше будут неконсистентны между серверами. Запрос, попавший на другой инстанс, не найдет закэшированных данных.
  2. Потеря данных при перезапуске: Весь кэш очищается при остановке или деплое приложения, что увеличивает нагрузку на источники данных после каждого развертывания.
  3. Неэффективное использование памяти: На каждом сервере хранится полная копия кэша, что приводит к дублированию данных и нерациональному использованию общей памяти кластера.

Когда его можно использовать:

  • Для кэширования данных, специфичных для одного экземпляра (например, кэширование результатов тяжелых вычислений).
  • В сценариях с одним сервером (синглтон).
  • Для хранения временных, не критичных к консистентности данных.

Рекомендуемая альтернатива для REST API — распределенный кэш:

  • Redis: Наиболее популярный выбор. Предоставляет общее кэш-хранилище для всех инстансов приложения, обеспечивает высокую производительность и persistence.
  • NCache: Распределенный кэш для .NET экосистемы.

Пример использования распределенного кэша (IDistributedCache с Redis):

// В Startup / Program.cs
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
});

// В контроллере или сервисе
public class CatalogController : ControllerBase
{
    private readonly IDistributedCache _cache;
    public CatalogController(IDistributedCache cache) => _cache = cache;

    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        var cacheKey = $"product_{id}";
        var cachedProduct = await _cache.GetStringAsync(cacheKey);
        if (cachedProduct != null)
        {
            return Ok(JsonSerializer.Deserialize<Product>(cachedProduct));
        }
        // ... получение из БД и запись в кэш
    }
}

Ответ 18+ 🔞

Давай разберемся, что за хрень с этим In-Memory Cache, а то народ его в REST API суёт, а потом удивляется, почему всё ебётся.

Смотри, IMemoryCache — это как твой личный холодильник в комнате. Удобно, под рукой, но если у тебя дом на три семьи и у каждого свой холодильник — начинается пиздец. Один купил колбасу, положил к себе, а другие приходят — а у них пусто. Вот и тут так же.

Почему он нихуя не годится для нормального масштабируемого API?

  1. Он локальный, блядь. Кэш живёт в памяти одного конкретного экземпляра твоего приложения. Запустил ты два инстанса за балансировщиком — и всё, пизда. Запрос прилетел на сервер А, данные закэшировались там. Следующий запрос балансировщик швырнул на сервер Б — а там кэш пустой, как мои карманы после зарплаты. И пошла опять тягать базу, нагружать всё. Консистентности — ноль ебать.

  2. Обнуляется при любом чихе. Перезапустил приложение — кэш накрылся медным тазом. Сделал деплой — всё, хуй. После каждого обновления база получает пиздюлей, потому что все запросы идут напрямую, кэш-то пустой. Красота, да?

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

Так когда его, блядь, тогда использовать-то?

Ну, есть же адекватные сценарии:

  • Кэшируешь что-то, что нужно только этому конкретному экземпляру. Ну, там, результаты каких-то ебанутых расчётов, которые другие сервера делать не будут.
  • У тебя приложение-одиночка, один сервер на весь мир. Тогда да, можно.
  • Хранишь временные данные, которым похуй на консистентность. Упало — не страшно.

А что делать для нормального REST API?

Брать распределённый кэш. Это как общий холодильник на кухне. Все к нему ходят, все видят, что там есть.

  • Redis — это царь и бог, самый популярный вариант. Быстрый, надёжный, данные переживают перезапуски.
  • NCache — тоже вариант, заточенный под .NET.

Смотри, как это выглядит в коде (IDistributedCache + Redis):

// Регаем в Program.cs
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
});

// Юзаем в контроллере
public class CatalogController : ControllerBase
{
    private readonly IDistributedCache _cache;
    public CatalogController(IDistributedCache cache) => _cache = cache;

    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        var cacheKey = $"product_{id}";
        var cachedProduct = await _cache.GetStringAsync(cacheKey);
        if (cachedProduct != null)
        {
            return Ok(JsonSerializer.Deserialize<Product>(cachedProduct));
        }
        // ... если не нашли, лезем в базу, потом кладём в кэш
    }
}

Вот и вся магия. Все инстансы твоего API тычутся в один Redis, видят одни и те же данные, и жизнь налаживается. А IMemoryCache оставь для мелких локальных поделок, не тащи его туда, где он создаст только проблемы.