> Какие стратегии масштабирования кэша существуют (Go)

Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: Ozon

Стек: Go

> Пример ответа

Короткий ответ

Основные стратегии масштабирования кэша: вертикальное масштабирование (увеличение ресурсов одного узла), горизонтальное масштабирование с шардированием (consistent hashing), репликация (leader-follower, multi-leader), распределённые кэши (Redis Cluster, Memcached), локальные кэши с инвалидацией через pub/sub, а также гибридные подходы (L1 + L2). Выбор зависит от требований к консистентности, latency и объёму данных. Для Go-сервисов часто комбинируют in-process кэш с Redis.

Подробное объяснение

Масштабирование кэша решает две задачи: увеличение ёмкости и повышение пропускной способности. Основные стратегии:

  1. Вертикальное масштабирование - увеличиваем RAM/CPU на одном узле. Просто, но есть физический предел и единая точка отказа.

  2. Горизонтальное шардирование - данные распределяются по нескольким узлам. Ключевой момент - выбор ключа шардирования. Consistent hashing минимизирует перемещение данных при добавлении/удалении узлов. Минус - операции с несколькими ключами (multi-get) становятся дорогими.

  3. Репликация - копии данных на нескольких узлах. Leader-follower: запись только в leader, чтение из followers - увеличивает read throughput, но есть репликационный lag. Multi-leader - сложнее с конфликтами, но лучше для geo-distributed сценариев.

  4. Клиентское шардирование - клиент сам определяет, на какой узел идти (например, по hash ключа). Просто, но требует управления топологией на стороне клиента.

  5. Двухуровневый кэш (L1 + L2) - L1 (in-process, например, bigcache или freecache) даёт минимальный latency, L2 (Redis) - общий для всех инстансов. Инвалидация L1 через pub/sub или короткий TTL.

  6. Кэш рядом с данными (cache-aside vs read-through vs write-through) - это скорее паттерны доступа, но они влияют на масштабирование: write-through увеличивает нагрузку на запись, но упрощает консистентность.

  7. Кэш с истечением и фоновой перестройкой - позволяет избежать stampede (когда много запросов одновременно бьют в БД после истечения TTL). Используется singleflight в Go.

Для Go-специфики:

  • sync.Map для локального кэша с высокой конкуренцией.
  • singleflight для дедупликации одновременных запросов к источнику.
  • bigcache/freecache - zero-GC кэши, эффективные для in-process хранения.

На практике

Для senior-позиции важно показать понимание trade-off. Например:

  • Если чтения преобладают и данные помещаются в память одного узла - хватит Redis с репликами.
  • Если данных больше, чем RAM одного узла - Redis Cluster с 16384 слотами или client-side sharding.
  • Если latency критичен (например, < 1ms) - добавляем L1 кэш, но тогда нужно решать проблему инвалидации.
  • Для горячих ключей - local cache + singleflight, чтобы не перегружать Redis.

Также стоит упомянуть мониторинг: hit ratio, eviction rate, latency percentiles. И операционные аспекты: backup, persistence (RDB/AOF), network partitioning.

Пример кода

Пример двухуровневого кэша с singleflight в Go:

GO
type Cache struct {
local *bigcache.BigCache
redis *redis.Client
sf singleflight.Group
}
func (c *Cache) Get(ctx context.Context, key string) ([]byte, error) {
// L1
if v, err := c.local.Get(key); err == nil {
return v, nil
}
// singleflight для защиты от stampede
v, err, _ := c.sf.Do(key, func() (interface{}, error) {
// L2
if v, err := c.redis.Get(ctx, key).Bytes(); err == nil {
c.local.Set(key, v)
return v, nil
}
// источник
data, err := loadFromDB(ctx, key)
if err != nil {
return nil, err
}
c.redis.Set(ctx, key, data, ttl)
c.local.Set(key, data)
return data, nil
})
if err != nil {
return nil, err
}
return v.([]byte), nil
}

Как отвечать на собеседовании

Начни с классификации: ёмкость vs пропускная способность. Затем перечисли стратегии и для каждой - когда применять, какие проблемы решает и какие создаёт. Обязательно упомяни consistent hashing и его преимущества. Покажи понимание инвалидации и консистентности. Если спросят про Go - расскажи про singleflight и zero-GC кэши. Закончи примером из практики: как выбирал стратегию под конкретные метрики (RPS, размер данных, latency budget).

Что проверяет интервьюер

  • Понимание trade-off между консистентностью и производительностью.
  • Знание распределённых систем: шардирование, репликация, consistent hashing.
  • Умение проектировать под нагрузку: расчёт ёмкости, оценка hit ratio.
  • Практический опыт: какие проблемы возникали (stampede, hot keys, rehash).
  • Go-специфику: singleflight, sync.Map, bigcache.

Типичные ошибки

  • Предлагать только вертикальное масштабирование без анализа предела.
  • Игнорировать проблему инвалидации L1 кэша.
  • Забывать про hot keys при шардировании (один ключ с высокой нагрузкой).
  • Не учитывать репликационный lag при чтении из followers.
  • Использовать sync.Map для кэша с большим объёмом данных - он не оптимизирован для этого.
  • Не упоминать мониторинг и алерты на hit ratio.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью