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

«Почему In-Memory Cache не рекомендуют использовать в REST API?» — вопрос из категории Архитектура, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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));
        }
        // ... получение из БД и запись в кэш
    }
}