Как шардировать таблицы пользователей, чатов и сообщений
Что проверяют
Умеете ли вы выбрать ключ шардирования под реальные запросы, а не просто разложить всё по hash(id) % N. Главное здесь: сообщения одного чата должны лежать вместе, а список чатов пользователя собираться без обхода всех шардов. Следом спрашивают про решардинг и огромные чаты.
Как отвечать
Начните с запросов: история чата с пагинацией, список чатов пользователя с последним сообщением, отправка сообщения. Отсюда ключи:
- пользователи: по
user_id; - чаты и сообщения: по
chat_id, тогда история читается с одного шарда запросом по индексу(chat_id, message_id); - участники: в двух видах,
chat_membersпоchat_idдля доставки иuser_chatsпоuser_idдля списка чатов. Это денормализация, зато без межшардовых запросов. Последнее сообщение и счётчик непрочитанных тоже держите вuser_chats; - идентификаторы: сортируемые по времени, например UUIDv7 или Snowflake-подобные
int64, без центральной последовательности.
Маршрутизацию не привязывайте к числу серверов. Заводят много логических шардов, скажем 1024, и таблицу соответствия «логический шард → физическая база». При росте логические шарды переносятся целиком, без перехеширования всех данных. В Go это слой-роутер, который по ключу выбирает *pgxpool.Pool или *sql.DB из карты пулов; готовые варианты: Vitess для MySQL и Citus для PostgreSQL.
Запись сообщения и обновление user_chats участников попадают на разные шарды, поэтому распределённую транзакцию заменяют outbox и асинхронной доставкой с идемпотентными обработчиками.
Где спотыкаются
Берут hash % N по числу серверов. Добавление сервера перемещает почти все ключи, и решардинг превращается в полный перенос данных.
Шардируют сообщения по user_id отправителя. История чата размазывается по всем шардам, и каждая страница требует обхода всех баз со слиянием результатов.
Забывают про горячие чаты. Канал с миллионами подписчиков перегружает свой шард, поэтому такие чаты дробят ещё и по времени, ключом (chat_id, bucket), и выносят на отдельные машины.