Какие виды кэширования вы знаете в разработке программного обеспечения?

«Какие виды кэширования вы знаете в разработке программного обеспечения?» — вопрос из категории Архитектура, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Кэширование — ключевая техника для повышения производительности. Виды кэширования классифицируют по разным критериям:

1. По расположению (уровень в архитектуре)

Уровень Технологии/Примеры Когда использовать
Клиентский HTTP-заголовки (Cache-Control, ETag), LocalStorage, Service Worker Статичные ресурсы (CSS, JS, изображения), данные, не меняющиеся часто.
Сетевой (CDN) Cloudflare, AWS CloudFront, Akamai Глобальное распространение статического контента и даже динамического (с edge computing).
Веб-сервер/Прокси NGINX, Varnish, IIS Output Caching Кэширование целых HTML-страниц или API-ответов.
Уровень приложения (In-Memory) IMemoryCache (ASP.NET Core), ConcurrentDictionary, статические поля Данные, общие для всех пользователей в рамках одного экземпляра приложения (справочники, конфигурация).
Распределенный (Distributed) Redis, Memcached, NCache, SQL Server как кэш Данные, которые должны быть доступны всем экземплярам масштабируемого приложения (сессии, результаты тяжелых запросов).
Уровень базы данных Query cache (MySQL), Buffer pool (SQL Server), Materialized Views Внутренний кэш СУБД для ускорения повторяющихся запросов.

2. По стратегии записи (Write Policy)

  • Write-Through: Данные записываются одновременно в кэш и в основное хранилище (БД). Гарантирует согласованность, но медленнее на запись.
  • Write-Back (Write-Behind): Данные сначала пишутся в кэш, а в БД записываются асинхронно пачкой. Высокая производительность на запись, но риск потери данных при сбое.
  • Write-Around: Запись идет напрямую в БД, минуя кэш. Кэш обновляется только при последующем чтении. Подходит для данных, которые редко перечитываются после записи.

3. По стратегии вытеснения (Eviction Policy) При заполнении кэша нужно решать, какие данные удалить:

  • LRU (Least Recently Used): Удаляет давно неиспользуемые. Самый популярный и эффективный в большинстве случаев.
  • LFU (Least Frequently Used): Удаляет реже всего используемые.
  • FIFO (First In, First Out): Удаляет самые старые по времени добавления.
  • Sliding Expiration: Данные удаляются, если к ним не обращались в течение заданного интервала.
  • Absolute Expiration: Данные удаляются в определенный момент времени.

4. По типу кэшируемого контента

  • Кэширование объектов: Сохранение десериализованных объектов (например, User, Product).
  • Кэширование данных: Сохранение результатов запросов к БД (сырых данных или DTO).
  • Кэширование вычислений: Сохранение результатов тяжелых вычислений (например, агрегаций, отчетов).
  • Кэширование страниц (Output Caching): Сохранение готового HTML.

Практический пример в ASP.NET Core:

// 1. In-Memory Cache (для данных в рамках одного сервера)
public class CatalogService
{
    private readonly IMemoryCache _cache;
    public CatalogService(IMemoryCache cache) => _cache = cache;

    public async Task<List<Product>> GetProductsAsync()
    {
        // Пытаемся получить из кэша
        if (!_cache.TryGetValue("TopProducts", out List<Product> products))
        {
            // Кэш пуст — идем в БД
            products = await _dbContext.Products.Take(10).ToListAsync();
            // Сохраняем в кэш на 5 минут
            _cache.Set("TopProducts", products, TimeSpan.FromMinutes(5));
        }
        return products;
    }
}

// 2. Распределенный кэш (Redis)
services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
});
// Использование IDistributedCache аналогично IMemoryCache

Критические аспекты при работе с кэшем:

  1. Инвалидация: Самый сложный момент. Как и когда очищать устаревшие данные? Используйте TTL (время жизни), зависимые тэги или паттерн «Cache Aside».
  2. Согласованность: Кэш и БД могут расходиться. Выберите подходящую стратегию записи для ваших требований (возможна eventual consistency).
  3. Сериализация: Для распределенного кэша объекты должны сериализоваться (JSON, MessagePack, Protobuf).
  4. Горячие ключи (Hot Keys): Один часто запрашиваемый ключ может создать нагрузку на один узел Redis. Решение — шардирование или дублирование ключей.