Статьи

Масштабирование БД в Java: Replication и Eventual Consistency

Пока приложение небольшое, база данных обычно живёт на одном сервере: приложение отправляет запросы, база их обрабатывает. Всё просто. Но с ростом проекта увеличивается количество пользователей, запросов и данных. Сервер начинает упираться в CPU, память, диск или количество соединений.

Можно поставить более мощный сервер — это вертикальное масштабирование. Но у него есть предел. Другой вариант — добавить несколько экземпляров базы и распределить между ними нагрузку. Это уже горизонтальное масштабирование, и один из самых распространённых способов его реализовать — репликация.

Primary и Replica

При репликации один экземпляр базы становится Primary, а другие — Replica. Primary принимает изменения, Replica получает их копии и может использоваться для чтения.

Например, в интернет-магазине пользователи постоянно просматривают каталог, но относительно редко меняют данные:
Если чтений становится очень много, можно добавить несколько Replica и распределять запросы между ними.
Это позволяет масштабировать read-нагрузку: вместо того чтобы один сервер обрабатывал все SELECT, работу можно распределить между несколькими.

Но здесь появляется важный нюанс.

Replica может отставать

В типичной асинхронной репликации данные не появляются на Replica в ту же миллисекунду, когда они были записаны на Primary.

Представим:
Primary успешно сохранил изменение. Сразу после этого приложение делает:
Но запрос попадает на Replica, которая ещё не получила новую версию данных.
В результате пользователь может увидеть старое имя.
Эта задержка называется replication lag. А архитектура, в которой разные экземпляры некоторое время могут содержать разные версии данных, приводит нас к понятию Eventual Consistency.

Идея здесь простая: данные не обязаны стать одинаковыми мгновенно, но при нормальной работе системы реплики в итоге синхронизируются.
Для каталога товаров такая задержка может быть практически незаметной. А вот для некоторых сценариев она становится проблемой.

Read-after-write: когда старые данные недопустимы

Представим, что пользователь только что изменил пароль или оформил заказ. Запись успешно завершилась, и следующим запросом приложение получает данные пользователя.
Если этот запрос уйдёт на отстающую Replica, пользователь может увидеть старое состояние.

Получается ситуация:
Поэтому приложение должно понимать, какие данные можно читать с Replica, а какие требуют гарантированно свежего состояния.

Для этого можно направлять критичные чтения на Primary, а обычные — на Replica. В более сложных системах используются специальные механизмы маршрутизации и контроля задержки репликации.
Именно здесь репликация перестаёт быть исключительно задачей базы данных. Архитектура приложения тоже должна учитывать, что данные теперь существуют в нескольких экземплярах.

Как это связано с Java

В обычном Java-приложении разработчик может вообще не знать, на каком сервере выполняется запрос:
После появления репликации появляется дополнительный слой маршрутизации:
Условно можно договориться: операции записи идут на Primary, а большая часть чтений — на Replica.

В Spring-приложениях подобную маршрутизацию можно реализовать на уровне DataSource или инфраструктуры доступа к базе, не заставляя бизнес-логику самостоятельно выбирать сервер.
Главное — не превратить код в набор условий вроде if (read) useReplica(). Решение о маршрутизации лучше держать в отдельном инфраструктурном слое.

А что с отказоустойчивостью?

Replica может быть полезна не только для чтений. Если Primary выходит из строя, одна из Replica может стать новым Primary — это failover.

Но при асинхронной репликации возможна потеря последних изменений, которые Primary уже успел принять, а Replica ещё не получила. Поэтому репликация повышает отказоустойчивость, но не означает автоматически нулевой риск потери данных.
Более строгие гарантии можно получить с помощью синхронной репликации, однако за них приходится платить дополнительной задержкой и сложностью.

И снова получается компромисс: чем сильнее требования к согласованности, тем дороже их обеспечить.

Когда репликация действительно нужна?

Если база нормально работает на одном сервере, добавлять Replica просто «на будущее» обычно не стоит. Это усложняет архитектуру и добавляет новые сценарии, которые нужно тестировать.

Репликация становится особенно полезной, когда основная проблема — большое количество чтений или требования к отказоустойчивости. При этом подход к согласованности данных во многом зависит от выбранной модели базы. Для традиционных SQL-систем строгая согласованность и транзакционность обычно являются базовыми свойствами, поэтому переход к модели с задержкой между репликами приходится осознанно учитывать на уровне архитектуры. В NoSQL-системах вроде Cassandra или DynamoDB горизонтальное масштабирование и распределённость изначально заложены в архитектуру, поэтому Eventual Consistency может быть естественной частью модели работы с данными. В таких системах нет необходимости строить всё вокруг единственного Primary: разные узлы могут принимать операции, а согласованное состояние достигается постепенно. Такой подход часто описывают через модель BASE — противопоставляя её классическому ACID.

Но репликация не решает другую важную проблему: что делать, если самих данных становится слишком много для одного сервера.
Представим таблицу событий с миллиардами строк. Мы можем добавить десять Replica, но весь объём данных всё равно должен храниться на Primary. В такой ситуации нужно уже не просто копировать данные, а разделять их.

Но это часть отдельной темы, Partitioning и Sharding.

Replication позволяет иметь несколько экземпляров базы и распределять между ними нагрузку, прежде всего чтение. Но вместе с этим появляется replication lag и необходимость учитывать Eventual Consistency.

Для Java-разработчика это означает, что недостаточно просто знать, как выполнить SQL-запрос. Нужно понимать, куда он попадёт, насколько свежие данные нужны конкретной операции и что произойдёт, если основной сервер станет недоступен.


Хотите узнать больше? Изучите другие статьи из разделов:
2026-09-02 10:00 Базы данных Java