> Какие стратегии масштабирования кэша существуют (Go)
Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: Ozon
Стек: Go
> Пример ответа
Короткий ответ
Основные стратегии масштабирования кэша: вертикальное масштабирование (увеличение ресурсов одного узла), горизонтальное масштабирование с шардированием (consistent hashing), репликация (leader-follower, multi-leader), распределённые кэши (Redis Cluster, Memcached), локальные кэши с инвалидацией через pub/sub, а также гибридные подходы (L1 + L2). Выбор зависит от требований к консистентности, latency и объёму данных. Для Go-сервисов часто комбинируют in-process кэш с Redis.
Подробное объяснение
Масштабирование кэша решает две задачи: увеличение ёмкости и повышение пропускной способности. Основные стратегии:
-
Вертикальное масштабирование - увеличиваем RAM/CPU на одном узле. Просто, но есть физический предел и единая точка отказа.
-
Горизонтальное шардирование - данные распределяются по нескольким узлам. Ключевой момент - выбор ключа шардирования. Consistent hashing минимизирует перемещение данных при добавлении/удалении узлов. Минус - операции с несколькими ключами (multi-get) становятся дорогими.
-
Репликация - копии данных на нескольких узлах. Leader-follower: запись только в leader, чтение из followers - увеличивает read throughput, но есть репликационный lag. Multi-leader - сложнее с конфликтами, но лучше для geo-distributed сценариев.
-
Клиентское шардирование - клиент сам определяет, на какой узел идти (например, по hash ключа). Просто, но требует управления топологией на стороне клиента.
-
Двухуровневый кэш (L1 + L2) - L1 (in-process, например, bigcache или freecache) даёт минимальный latency, L2 (Redis) - общий для всех инстансов. Инвалидация L1 через pub/sub или короткий TTL.
-
Кэш рядом с данными (cache-aside vs read-through vs write-through) - это скорее паттерны доступа, но они влияют на масштабирование: write-through увеличивает нагрузку на запись, но упрощает консистентность.
-
Кэш с истечением и фоновой перестройкой - позволяет избежать 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:
GOtype Cache struct {local *bigcache.BigCacheredis *redis.Clientsf singleflight.Group}func (c *Cache) Get(ctx context.Context, key string) ([]byte, error) {// L1if v, err := c.local.Get(key); err == nil {return v, nil}// singleflight для защиты от stampedev, err, _ := c.sf.Do(key, func() (interface{}, error) {// L2if 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.
> Похожие задачи по Go
Какие инструменты кроме трассировки используются для диагностики производительности
Как избежать утечек памяти при работе с файлами в цикле в Go
Какие ключевые особенности типа slice в Go
Как реализовать шардирование кэша
> Похожие задачи по backend
Какие инструменты кроме трассировки используются для диагностики производительности
Как избежать утечек памяти при работе с файлами в цикле в Go
Какие ключевые особенности типа slice в Go
Как реализовать шардирование кэша
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью