> Какие плюсы и минусы шардирования по сообщениям и по пользователям (Go)

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

Компании: Ozon

Стек: Go

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

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

Шардирование по сообщениям даёт равномерную нагрузку и горизонтальное масштабирование, но усложняет запросы "все сообщения пользователя" и требует двойной индексации. Шардирование по пользователям локализует данные и упрощает запросы, но создаёт риск перекоса нагрузки из-за "горячих" пользователей и ограничивает масштабирование одного пользователя. Выбор зависит от паттерна доступа: если чтение идёт в основном в рамках диалога - по пользователям, если нужен глобальный поиск или аналитика - по сообщениям.

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

Шардирование по сообщениям - это распределение записей по шардам на основе хэша от идентификатора сообщения (или времени). Ключевые характеристики:

  • равномерное распределение данных и нагрузки при хорошей хэш-функции;
  • простое добавление новых шардов - данные перераспределяются без привязки к конкретному пользователю;
  • любой запрос "все сообщения пользователя" требует fan-out по всем шардам и последующей merge-сортировки;
  • для поддержки запросов по пользователю нужен вторичный индекс (например, отдельная таблица user_id → message_id), что удваивает стоимость записи;
  • удобно для глобальных операций: поиск по тексту, аналитика, агрегации по времени.

Шардирование по пользователям - это распределение всех сообщений одного пользователя на один шард, обычно по хэшу от user_id. Ключевые характеристики:

  • все данные пользователя находятся локально, запросы "все сообщения" выполняются одним обращением к шарду;
  • транзакционность в рамках пользователя достигается без распределённых транзакций;
  • перекос нагрузки: один активный пользователь создаёт "горячий шард", остальные простаивают;
  • масштабирование ограничено: если у пользователя миллионы сообщений, один шард становится узким местом;
  • миграция данных при изменении числа шардов затрагивает всех пользователей, но перераспределение проще, чем при шардировании по сообщениям.

Сравнение по ключевым критериям:

КритерийПо сообщениямПо пользователям
Равномерность нагрузкивысокаянизкая, зависит от активности
Локальность запросовнизкаявысокая
Сложность запросовfan-out + mergeодин шард
Поддержка транзакцийсложнопросто в рамках пользователя
Масштабирование одного пользователяне ограниченоограничено
Стоимость записивыше (вторичный индекс)ниже
Глобальные запросыестественнытребуют fan-out

На практике

В реальных системах часто используют гибридный подход. Например, в мессенджерах шардируют по user_id, но для "горячих" пользователей выделяют отдельный шард или разбивают их данные по времени. В системах аналитики, где запросы идут по времени или по тексту, предпочитают шардирование по сообщениям.

Дополнительно стоит учитывать:

  • при шардировании по пользователям нужно продумать стратегию для "суперпользователей" - например, вертикальное разделение их данных или отдельный кластер;
  • при шардировании по сообщениям вторичный индекс лучше хранить в отдельном хранилище (например, Elasticsearch), чтобы не дублировать данные в основном хранилище;
  • выбор шард-ключа должен учитывать не только чтение, но и запись: если пользователь пишет часто, его шард перегружается;
  • для Go-систем важно учитывать, что fan-out запросы требуют конкурентного выполнения (goroutines + errgroup) и таймаутов, иначе один медленный шард задерживает весь ответ.

Пример кода

Пример конкурентного fan-out запроса при шардировании по сообщениям на Go:

GO
func GetUserMessages(ctx context.Context, userID string, shards []*Shard) ([]Message, error) {
var mu sync.Mutex
var messages []Message
g, ctx := errgroup.WithContext(ctx)
for _, shard := range shards {
shard := shard
g.Go(func() error {
msgs, err := shard.QueryByUser(ctx, userID)
if err != nil {
return err
}
mu.Lock()
messages = append(messages, msgs...)
mu.Unlock()
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
sort.Slice(messages, func(i, j int) bool {
return messages[i].CreatedAt.Before(messages[j].CreatedAt)
})
return messages, nil
}

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

Начни с краткого определения обоих подходов, затем переходи к сравнению по ключевым осям: равномерность нагрузки, локальность данных, сложность запросов, транзакционность, масштабируемость. Обязательно упомяни trade-off между равномерностью и локальностью - это центральная точка ответа.

Приведи конкретный пример из практики: для чата с историей переписки логичнее шардировать по пользователям, для лог-системы с поиском по времени - по сообщениям. Покажи, что понимаешь проблему "горячих" пользователей и можешь предложить решения: отдельный шард, вертикальное разделение, кэширование.

Если спросят про Go, добавь детали про конкурентный fan-out, использование errgroup, важность таймаутов и обработку частичных ошибок. Не углубляйся в код, если не просят - лучше сфокусируйся на архитектурных решениях.

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

Интервьюер оценивает:

  • понимание фундаментального trade-off между равномерностью распределения и локальностью данных;
  • способность анализировать паттерны доступа и выбирать шард-ключ под них;
  • знание проблем "горячих" ключей и способов их решения;
  • понимание влияния шардирования на сложность запросов и транзакций;
  • умение рассуждать о масштабируемости и миграции данных;
  • для senior-уровня - способность предложить гибридные стратегии и учесть особенности конкретной системы.

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

  • утверждение, что шардирование по сообщениям всегда лучше из-за равномерности - без учёта паттерна запросов;
  • игнорирование проблемы "горячих" пользователей при шардировании по пользователям;
  • предложение шардировать по user_id без обсуждения того, что делать с пользователями с миллионами сообщений;
  • забывают про вторичный индекс при шардировании по сообщениям и его стоимость;
  • не учитывают, что при изменении числа шардов миграция данных - это нетривиальная операция в обоих случаях;
  • путают шардирование с партиционированием внутри одной БД - это разные уровни.

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

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