---
title: "Репликация для разработчика — read-replica и routing"
nav_title: "Репликация для разработчика"
excerpt: "PostgreSQL streaming replication: master пишет WAL → реплика проигрывает → второе соединение готово к чтению. Replication lag 50-500ms в хорошей сети, до секунд под нагрузкой. Сценарии: разгрузка мастера (тяжёлые SELECT), high availability (promote реплики), геораспределение. НЕ делать: read-after-write в одном действии, операции с мгновенной консистентностью. Routing через Spring AbstractRoutingDataSource на основе @Transactional(readOnly=true). LazyConnectionDataSourceProxy обязателен — без него Spring выбирает соединение в момент открытия транзакции, до того как известно readOnly. Read-after-write антипаттерн: «создал заказ — сразу прочитал список» на реплике покажет старый. Решения: read-from-master (без readOnly), возвращать данные сразу из write-операции, wait for replica catch-up через pg_current_wal_lsn / pg_last_wal_replay_lsn (сложно). Synchronous replication — мастер ждёт подтверждения реплики; цена — latency commit + network round-trip; обычно не нужно. Failover: HikariCP переподключается; retry на критичных. Logical replication для DWH-копирования, online migration. Алёрт на replay_lag > 30 сек или > 1 GB WAL. Раскрытие правил PG-RP-001..PG-RP-085."
keywords: "PostgreSQL replication, AbstractRoutingDataSource, LazyConnectionDataSourceProxy, replication lag, read-after-write, PG-RP-001 PG-RP-085"
focus_keyword: "PostgreSQL replication read-replica routing"
---

# Репликация для разработчика — read-replica и routing

> **Опирается на правила:** `PG-RP-001` … `PG-RP-085` из PostgreSQL Style Guide → [раздел Репликация](/standards/backend/pg/#репликация-для-разработчика).

> **Важно знать**
> - **Streaming replication**: master пишет WAL → реплика проигрывает.
> - **Replication lag** 50-500ms норма, до секунд под нагрузкой.
> - **Сценарии**: разгрузка мастера, HA, геораспределение.
> - **Не для read-after-write** — реплика отстаёт.
> - **`AbstractRoutingDataSource`** + **`LazyConnectionDataSourceProxy`** — routing по `readOnly`.
> - **Без LazyConnection** Spring выбирает DataSource до знания `readOnly`.
> - **Read-after-write** решается: read-from-master, возврат из write, wait for catch-up.
> - **Synchronous replication** — обычно не нужно (latency commit).
> - **Failover** — HikariCP переподключается; retry на критичных.
> - **Алёрт** на `replay_lag > 30 сек` или `> 1 GB WAL`.

`PostgreSQL streaming replication` — master пишет WAL, реплика проигрывает. Для разработчика главный вопрос — как и когда читать с реплики.

## Архитектура

`PG-RP-001..002`:

- **Master** (primary) — single source of truth, все INSERT/UPDATE/DELETE.
- **Replica** (standby, hot standby) — проигрывает WAL, read-only.

PG поддерживает async (default) и sync. Для backend чаще async — replica может отстать на ms-секунды.

**Replication lag**: 50-500ms норма; под нагрузкой / большими транзакциями — секунды.

## Сценарии

`PG-RP-010..011`:

**Использовать**:
- **Разгрузка мастера** — тяжёлые SELECT (отчёты, аналитика) не конкурируют с OLTP.
- **HA** — failover при падении мастера.
- **Геораспределение** — реплика рядом с пользователем.

**Не использовать**:
- Read-after-write в одном пользовательском действии.
- Операции с мгновенной консистентностью.

## Routing

`PG-RP-020..022`: Spring AbstractRoutingDataSource.

```java
public enum DataSourceType { MASTER, REPLICA }

@Component
public class TransactionRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return TransactionSynchronizationManager.isCurrentTransactionReadOnly()
            ? DataSourceType.REPLICA
            : DataSourceType.MASTER;
    }
}

@Configuration
public class DataSourceConfig {

    @Bean @Primary
    public DataSource routingDataSource(DataSource master, DataSource replica) {
        var routing = new TransactionRoutingDataSource();
        routing.setTargetDataSources(Map.of(
            DataSourceType.MASTER, master,
            DataSourceType.REPLICA, replica
        ));
        routing.setDefaultTargetDataSource(master);
        return new LazyConnectionDataSourceProxy(routing);   // обязательно!
    }
}
```

`PG-RP-021`: **`LazyConnectionDataSourceProxy` обязателен**. Без него Spring выбирает DataSource в момент открытия TX, **до** того как становится известно `readOnly`. С Lazy — откладывается до первого реального запроса.

```java
@Transactional(readOnly = true)
public List<OrderView> findOrders(...) {
    // на replica
}

@Transactional
public OrderId createOrder(...) {
    // на master
}
```

## Read-after-write — антипаттерн

`PG-RP-030..031`:

```java
// ✗ — на реплике без свежего заказа
public OrderResponse handle(CreateOrderRequest req) {
    var id = service.createOrder(req);       // на master
    var orders = service.myOrders(req.customerId());   // на replica — без только что созданного
    return ...;
}
```

Решения:

### A. Read-from-master

```java
@Transactional   // без readOnly — пойдёт на master
public List<Order> myOrdersFromMaster(long customerId) { ... }
```

### B. Возвращать данные сразу из write

```java
public OrderResponse createOrder(...) {
    var saved = orderRepo.save(...);
    return OrderResponse.from(saved);   // данные уже в руке
}
```

### C. Wait for replica catch-up

```java
String lsn = jdbc.queryForObject("SELECT pg_current_wal_lsn()", String.class);

do {
    String replayLsn = replicaJdbc.queryForObject("SELECT pg_last_wal_replay_lsn()", String.class);
    if (lsnGte(replayLsn, lsn)) break;
    Thread.sleep(50);
} while (true);
```

Сложно, специфические случаи. Обычно A или B.

## Synchronous replication

`PG-RP-040..042`:

```ini
# postgresql.conf на мастере
synchronous_commit = on
synchronous_standby_names = 'replica1, replica2'
```

Мастер ждёт подтверждения реплики перед возвратом OK на COMMIT.

**Цена** — latency COMMIT увеличивается на network round-trip + replica fsync (1-5ms локально, десятки ms geo).

Обычно **не нужно**. Async + правильный routing решает 99%. Sync — только для критических данных «после COMMIT обязательно на двух дисках».

## Failover

`PG-RP-050..052`:

```
1. Failover-инструмент (Patroni, repmgr) обнаруживает падение мастера.
2. Promote одной из реплик в master.
3. DNS / LB / VIP переключаются.
4. Приложение переподключается.
```

Spring + HikariCP справится при правильных `validationTimeout` и `connectionTestQuery`. После failover соединения инвалидируются, открываются к новому мастеру.

На время failover (10-60 сек) — ошибки writes. Retry на критичных:

```java
@Retryable(retryFor = SQLException.class, maxAttempts = 5, backoff = @Backoff(delay = 1000, multiplier = 2))
```

## Logical replication

`PG-RP-060..062`: копирует **изменения по таблицам**, не WAL.

Можно реплицировать подмножество таблиц, в другую схему, с трансформацией.

Применения:
- Гибридный стек (PG → DWH/Kafka).
- Online migration с PG на PG.
- Multi-master (с conflict resolution).

Для read-replica с разгрузкой — обычная streaming replication. Logical имеет больший overhead.

## Мониторинг lag

`PG-RP-070..072`:

На мастере:
```sql
SELECT application_name, state, replay_lag
FROM pg_stat_replication;
```

На реплике:
```sql
SELECT now() - pg_last_xact_replay_timestamp() AS replication_lag;
```

**Алёрт** на `replay_lag > 30 сек` или > 1 GB WAL pending.

## Что запрещено

| Антипаттерн | Правило | Что взамен |
|---|---|---|
| Read-after-write через реплику | `PG-RP-080` | read-from-master |
| Все SELECT на реплику без `readOnly` | `PG-RP-081` | явный `@Transactional(readOnly = true)` |
| `AbstractRoutingDataSource` без `LazyConnection` | `PG-RP-082` | обязателен Lazy |
| Sync replication «на всякий случай» | `PG-RP-083` | async обычно |
| Игнорировать `replay_lag` в мониторинге | `PG-RP-084` | алёрт > 30 сек |
| Sticky-session по userId | `PG-RP-085` | не покрывает cross-user |
| `logical` для read-replica разгрузки | `PG-RP-062` | streaming |

## Куда дальше

- [PG → Репликация](/standards/backend/pg/#репликация-для-разработчика) — нормативные формулировки.
- [Spring @Transactional](/standards/backend/pg/transactional-spring/) — `readOnly` детали.
- [HikariCP](/standards/backend/pg/connection-pool/) — отдельные пулы.
- [Уровни изоляции](/standards/backend/pg/isolation-levels/) — read-only сессии.
- [WAL](/standards/backend/pg/wal/) — что лагает.
- [Distributed → eventual consistency](/standards/backend/distributed-patterns/java/eventual-consistency/) — read-your-writes.
