Шардирование базы данных: когда оно нужно, как выбрать ключ и чем его заменить

Чем шардирование отличается от репликации и партиционирования, когда оно действительно нужно, альтернативы, выбор ключа и стратегии распределения, межшардовые запросы и транзакции, Citus, Vitess и распределённые базы данных.

Когда продукт растёт, база данных одной из первых упирается в пределы: запросы замедляются, сервер работает на пределе, резервное копирование занимает часы, а следующий, более мощный сервер стоит непропорционально дороже. Шардирование — разделение данных между несколькими независимыми базами — позволяет масштабироваться горизонтально, добавляя серверы вместо покупки всё более мощного одного. Но шардирование — одно из самых дорогих архитектурных решений. Оно усложняет запросы, транзакции, миграции, резервное копирование и отладку, и повернуть назад почти невозможно. Ниже — когда шардирование действительно нужно, какие альтернативы стоит исчерпать сначала, как выбирать ключ и стратегию, какие проблемы возникают и какие инструменты позволяют не писать всё самостоятельно. Что такое шардирование Шард — отдельная база данных, хранящая часть общего набора данных. Например, заказы клиентов распределяются по четырём серверам: заказы каждого клиента хранятся на одном из них, а приложение или промежуточный слой знает, куда обращаться за данными конкретного клиента. Важно не путать шардирование с двумя близкими понятиями. Репликация — копирование одних и тех же данных на несколько серверов. Повышает отказоустойчивость и позволяет распределять чтение, но не уменьшает объём данных и нагрузку записи на каждый сервер. Партиционирование — разделение большой таблицы на части внутри одной базы данных, например по месяцам. Упрощает управление данными и ускоряет запросы по диапазонам, но все части остаются на одном сервере. Шардирование распределяет данные по разным серверам, и каждый шард обычно ещё и реплицируется для отказоустойчивости. Когда шардирование действительно нужно Признаки, что вертикальных возможностей недостаточно: объём данных или индексов не помещается в память самого мощного разумного по цене сервера, и производительность падает; нагрузка на запись превышает возможности одного сервера — чтение можно распределить репликами, запись нельзя; резервное копирование и восстановление занимают недопустимо долго для требований к восстановлению; операции обслуживания — создание индексов, миграции схемы — блокируют работу на часы; рост данных прогнозируемо превысит пределы одного сервера в обозримом будущем. Конкретных пороговых чисел — «после терабайта» или «после N запросов в секунду» — не существует: всё зависит от модели данных, характера запросов, оборудования и требований к задержкам. Решение принимается по измерениям конкретной системы. Что сделать до шардирования Большинству продуктов шардирование не нужно никогда, потому что проблемы решаются дешевле. Оптимизация запросов и индексов. Анализ медленных запросов и планов выполнения часто даёт многократный выигрыш без изменения архитектуры. Кэширование. Горячие данные в Redis или аналогичном хранилище снимают значительную часть нагрузки чтения. Подробно — в статье о Redis . Реплики для чтения. Распределение запросов на чтение по репликам. Вертикальное масштабирование. Современные серверы с большим объёмом памяти и быстрыми накопителями выдерживают очень большие нагрузки. Это часто дешевле, чем инженерное время на шардирование. Партиционирование таблиц. Для больших таблиц с временными данными — логи, события, заказы — разделение по времени упрощает обслуживание и удаление старых данных. Разделение по функциям. Вынос отдельных частей системы — аналитики, журналов, поиска — в специализированные хранилища: колоночную базу для аналитики, поисковый движок для поиска. Архивирование. Перенос старых, редко используемых данных в отдельное хранилище. Ключ шардирования Ключ шардирования — поле, по которому определяется, в каком шарде хранится запись. Это самое важное решение: сменить ключ позже значит перераспределить все данные. Требования к хорошему ключу Равномерность. Данные и нагрузка распределяются между шардами примерно поровну, без «горячих» шардов. Локальность запросов. Большинство запросов затрагивают один шард. Если приложение почти всегда работает в контексте одного клиента или одной организации, ключом естественно становится идентификатор клиента или организации. Неизменность. Значение ключа у записи не меняется, иначе запись придётся переносить между шардами. Высокая кардинальность. Много различных значений, чтобы данные можно было распределить на большое число шардов. Типичные ошибки ключ по дате создания: все новые записи попадают в один шард, и он перегружен; ключ по стране или региону без учёта неравномерности: один крупный регион перевешивает остальные; ключ, при котором ключевые запросы продукта затрагивают все шарды. Для B2B SaaS-продуктов частый удачный выбор — идентификатор организации-клиента: данные одной организации живут вместе, запросы в её контексте идут в один шард, а крупных клиентов можно выделять в отдельные шарды. Стратегии распределения По хешу Шард определяется хешем ключа. Даёт равномерное распределение, но простая формула «хеш по модулю числа шардов» при добавлении шарда перераспределяет почти все данные. shard = hash(tenant_id) % shard_count По диапазонам Каждый шард хранит диапазон значений ключа. Удобно для запросов по диапазонам и простого добавления новых шардов, но легко получить неравномерную нагрузку, если значения ключа распределены неравномерно или монотонно растут. tenant_id 100000 → shard_1 100000 ≤ tenant_id 200000 → shard_2 По справочнику Отдельная таблица соответствия хранит, в каком шарде находятся данные каждого ключа. Самый гибкий вариант: можно перемещать отдельных клиентов между шардами и выделять крупных. Цена — дополнительное обращение к справочнику, который должен быть быстрым, кэшируемым и отказоустойчивым. Географическая Данные распределяются по регионам. Снижает задержки для пользователей и помогает соблюдать требования к размещению данных в конкретных странах. Для российских проектов требования о локализации персональных данных граждан России влияют на то, где физически могут находиться шарды с такими данными. Согласованное хеширование и виртуальные шарды Чтобы добавление сервера не перераспределяло все данные, используют согласованное хеширование или фиксированное большое число логических шардов, которые распределяются между меньшим числом физических серверов. При добавлении сервера на него переносится часть логических шардов целиком, а не пересчитывается расположение каждой записи. Этот подход описывала, например, команда Instagram в публикации об устройстве идентификаторов: тысячи логических шардов отображались на значительно меньшее число физических серверов баз данных, а идентификатор записи содержал номер логического шарда. Логический шард можно перенести на новый сервер, не меняя идентификаторов. Проблемы, которые создаёт шардирование Запросы через несколько шардов Запрос без ключа шардирования — например, поиск пользователя по email, когда ключ — идентификатор организации, — приходится отправлять во все шарды и объединять результаты. Это медленно и нагружает всю систему. Решения: отдельный глобальный индекс «email → ключ шардирования», денормализация, вынос поиска в поисковый движок. Соединения таблиц JOIN между данными в разных шардах база выполнить не может. Приходится проектировать модель так, чтобы связанные данные хранились в одном шарде, дублировать справочные данные в каждом шарде или выполнять соединение на уровне приложения. Транзакции Транзакция, изменяющая данные в нескольких шардах, теряет простые гарантии одной базы. Распределённые транзакции сложны и медленны; на практике операции проектируют так, чтобы они укладывались в один шард, а межшардовые процессы реализуют через последовательность шагов с компенсирующими действиями и идемпотентными операциями. Уникальность и идентификаторы Автоинкремент в каждом шарде даёт повторяющиеся идентификаторы. Нужна схема генерации глобально уникальных идентификаторов: UUID, идентификаторы с временной меткой и номером шарда или центральный генератор. Перебалансировка Рост данных неравномерен, и шарды со временем разбалансируются. Перенос данных между шардами в работающей системе без простоя — одна из самых сложных операций, требующая двойной записи, сверки и переключения. Схема и миграции Изменение схемы нужно применить ко всем шардам согласованно. Инструменты миграций должны уметь работать с множеством баз и переживать частичные сбои. Эксплуатация Мониторинг, резервное копирование, восстановление и отладка умножаются на число шардов. Нужен мониторинг по каждому шарду, чтобы вовремя замечать перекосы нагрузки. Инструменты: не писать всё самому Citus — расширение PostgreSQL с открытым исходным кодом, распределяющее таблицы по узлам. Позволяет сохранить PostgreSQL и SQL, хорошо подходит для многоарендных SaaS-продуктов с ключом по организации и для аналитики в реальном времени. Vitess — система горизонтального масштабирования MySQL, изначально созданная в YouTube, берёт на себя маршрутизацию запросов, перебалансировку и управление шардами. Распределённые SQL-базы — CockroachDB, YugabyteDB и другие — распределяют данные и транзакции автоматически, сохраняя SQL-интерфейс. Упрощают разработку, но требуют понимания их модели согласованности и производительности. MongoDB поддерживает встроенное шардирование коллекций по выбранному ключу. Cassandra и ScyllaDB распределены по устройству и рассчитаны на огромные объёмы записи с моделированием данных вокруг запросов. Например, Discord описывал переход хранения сообщений с Cassandra на ScyllaDB. ClickHouse распределяет аналитические данные по шардам через распределённые таблицы. Выбор готового инструмента почти всегда предпочтительнее самостоятельной реализации маршрутизации в приложении: логика шардирования, перебалансировка и распределённые запросы — области, где ошибки проявляются редко и дорого. Если всё-таки на уровне приложения Иногда шардирование делают в приложении — например, отдельная база данных на каждого крупного клиента. Минимальный набор, который нужен в этом случае: // Маршрутизация по справочнику: tenant → shard async function dbFor(tenantId) { const shardId = await shardDirectory.get(tenantId); // кэшируется if (!shardId) throw new Error(`Нет шарда для ${tenantId}`); return pools[shardId]; } async function getOrders(tenantId, filters) { const db = await dbFor(tenantId); return db.query( 'SELECT * FROM orders WHERE tenant_id = $1 AND status = $2', [tenantId, filters.status] ); } Обратите внимание: ключ шардирования присутствует и в маршрутизации, и в самом запросе. Это страховка от ошибок, при которых запрос случайно уходит в чужой шард. Дополнительно нужны: генерация глобально уникальных идентификаторов, инструмент миграций по всем шардам, процедура переноса клиента между шардами, мониторинг по шардам и понятный процесс восстановления отдельного шарда. Как подойти к шардированию Измерить. Какие именно ресурсы исчерпываются — память, процессор, диск, запись, — и какие запросы создают нагрузку. Исчерпать альтернативы из списка выше и оценить, сколько времени роста они дают. Спроектировать модель данных вокруг будущего ключа шардирования: большинство запросов и транзакций в пределах одного шарда. Выбрать инструмент — готовое решение для текущей базы данных или распределённую базу. Подготовить приложение: глобальные идентификаторы, ключ шардирования во всех запросах, отказ от межшардовых соединений. Мигрировать поэтапно с двойной записью, сверкой данных и возможностью отката. Настроить эксплуатацию: мониторинг, резервное копирование и восстановление по шардам. Вопросы масштабирования данных тесно связаны с архитектурой всей системы. О выборе между монолитом и сервисами — в статье о микросервисах , об отказоустойчивости облачных систем — в материале о cloud-native архитектуре . Высоконагруженные системы и архитектуру данных мы проектируем в рамках разработки веб-проектов . Частые вопросы Шардирование и партиционирование — одно и то же? Нет. Партиционирование делит таблицу на части внутри одной базы данных на одном сервере. Шардирование распределяет данные по разным базам на разных серверах. Партиционирование часто решает проблему без сложности шардирования. Можно ли отм