Какие плюсы и минусы шардирования по сообщениям и по пользователям
Что проверяют
Это вопрос из системного дизайна мессенджера. Интервьюер смотрит, думаете ли вы от запросов: какие чтения и записи самые частые и сколько шардов затронет каждое. Следом обычно спрашивают про горячие шарды и решардинг.
Как отвечать
Начните с главных операций: отправить сообщение, открыть историю чата, получить список своих диалогов. Потом оцените каждую схему.
Шардирование по сообщениям (ключ message_id):
- плюсы: ровное распределение записи, горячих шардов почти нет;
- минусы: история одного чата размазана по всем шардам, и каждое открытие чата превращается в scatter-gather: запрос ко всем шардам, слияние, сортировка. Пагинация становится дорогой.
Шардирование по пользователям (ключ user_id):
- плюсы: всё про пользователя на одном шарде, список диалогов читается одним запросом;
- минусы: у сообщения несколько участников на разных шардах. Приходится писать копию каждому (fan-out на запись, в группе на тысячу человек это тысяча вставок) или читать с нескольких шардов. Активные пользователи и боты делают шард горячим.
Сильный ответ предлагает третий вариант: ключ chat_id. История чата и порядок сообщений живут на одном шарде, а список диалогов пользователя лежит отдельной таблицей, шардированной по user_id. Огромные каналы остаются риском, их дополнительно делят по времени.
На Go: слой маршрутизации вычисляет шард по ключу, у каждого шарда свой pgxpool, запросы к нескольким шардам идут параллельно через errgroup с общим context и таймаутом. Для решардинга берут консистентное хеширование или фиксированный набор виртуальных бакетов, которые переносятся между узлами.
Где спотыкаются
Выбирают ключ по равномерности, забыв про чтение. Ровная запись ценой scatter-gather на каждое открытие чата это плохой обмен.
Берут hash(key) % N. Добавили шард, и переезжает почти всё.
Не думают об идентификаторах. Автоинкремент одного шарда не годится, нужен генератор с временной меткой в старших битах, чтобы сортировать без координации.