Перейти к основному содержанию
`Ctrl`+`K`

# Настройка дополнительных пулов подключения (Read Pool) Global ERP

## 1. Общие положения

Документ описывает архитектуру и порядок настройки дополнительных пулов подключения (Read Pool) в Global ERP для работы с читающими репликами PostgreSQL.

Вся функциональность разделяется на два логических режима:

- Синхронные реплики — поддерживаемый и рекомендованный режим.

- Асинхронные реплики — режим находится в разработке и в текущих версиях системы не поддерживается.

## 2. Синхронные реплики

### 2.1 Назначение и область применения

PostgreSQL имеет ограниченные возможности вертикального масштабирования, упирающиеся в физические ресурсы сервера (CPU, RAM, дисковая подсистема). Практическая рекомендация при эксплуатации продуктивных систем — средняя загрузка CPU мастер-базы не должна превышать ~50%.

При достижении данного порога дальнейший рост нагрузки рекомендуется обеспечивать за счёт подключения читающих реплик, на которые переносится часть read-нагрузки (вплоть до 100%). Это позволяет:

- снизить нагрузку на мастер-базу;

- стабилизировать производительность при росте числа пользователей и отчётов;

- обеспечить масштабирование без увеличения ресурсов мастер-базы.

#### Ключевой принцип работы

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

- все write-запросы и любые запросы внутри транзакций всегда выполняются на мастер-базе;

- все read-запросы вне транзакций рассматриваются как равнозначные и распределяются глобально;

- отсутствует управление конкретными SQL-запросами — невозможно указать, какой именно запрос должен выполняться на мастер-базе или реплике;

- управление осуществляется только процентным распределением read-нагрузки между пулами.

Это является фундаментальным архитектурным ограничением данного режима.

#### Планируемые улучшения

В разработке находится инструмент, который позволит на прикладном уровне управлять отправкой запросов на реплики, отключать использование реплик для интерфейсов, отчётов, печатных форм.

Данный механизм предназначен для сглаживания архитектурного ограничения, связанного с невозможностью управлять распределением отдельных SQL-запросов, и позволит исключать чувствительные сценарии из использования читающих реплик без изменения конфигурации БД.

### 2.2 Поддерживаемые сценарии и ограничения

- Поддерживаются только синхронные реплики PostgreSQL.

- Асинхронная репликация в данном режиме не используется.

- Распределение читающих запросов осуществляется глобально на уровне системы, а не на уровне отдельных интерфейсов или SQL-запросов.

Внимание

Каждая подключаемая реплика должна быть синхронной.

### 2.3 Рекомендации по количеству реплик

Рекомендуется использовать не менее двух читающих реплик.

Основная причина данной рекомендации — защита мастер-ноды от резкого возврата нагрузки при отказе одной из реплик:

- при использовании одной реплики значительная часть read-нагрузки уходит с мастера;

- при отказе этой реплики вся нагрузка мгновенно возвращается на мастер;

- в условиях высокой загрузки мастера это может привести к деградации или отказу системы.

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

### 2.4 Типы синхронной репликации PostgreSQL

PostgreSQL поддерживает несколько режимов синхронной репликации, управляемых параметром `synchronous_commit`:

- `off` — коммит не ожидает подтверждений;

- `local` — ожидание записи WAL на диск мастера;

- `on` (по умолчанию) — ожидание записи WAL на синхронную реплику;

- `remote_write` — ожидание записи WAL в ОС реплики;

- `remote_flush` — ожидание fsync WAL на реплике;

- `remote_apply` — ожидание применения WAL на реплике.

Для работы читающих реплик в Global ERP критически необходим режим `synchronous_commit = remote_apply`.

Только данный режим гарантирует, что данные, считанные с реплики, уже применены и видимы после завершения COMMIT на мастере.

### 2.5 Архитектурные ограничения и риски

#### Основной риск — блокировка COMMIT на мастере

При использовании `synchronous_commit = remote_apply` подтверждение COMMIT на мастере происходит только после применения WAL на всех синхронных репликах.

Это означает:

- производительность системы определяется самой медленной репликой;

- деградация одной реплики напрямую влияет на скорость работы мастера;

- при блокировке WAL replay на реплике COMMIT-операции на мастере будут ожидать.

#### Конфликты WAL replay

Длительные read-запросы на реплике могут конфликтовать с применением WAL (VACUUM, DDL, обновление visibility map).

В этом случае PostgreSQL вынужден:

- либо задерживать применение WAL;

- либо отменять конфликтующий read-запрос.

До момента разрешения конфликта COMMIT на мастере может быть заблокирован.

### 2.6 Управление конфликтами WAL replay

Для ограничения времени блокировки WAL replay используются параметры:

- `max_standby_streaming_delay` — для потоковой репликации;

- `max_standby_archive_delay` — для восстановления из архива.

После превышения заданного лимита конфликтующие read-запросы принудительно отменяются.

Отмена read-запросов является штатным и ожидаемым механизмом, направленным на защиту мастер-базы и поддержание реплик в актуальном состоянии.

#### Пользовательский опыт (UX)

При автоматической отмене read-запроса пользователь получит понятное и явное сообщение об ошибке, содержащее следующую информацию:

- операция была прервана автоматически;

- причина прерывания — поддержание реплики в актуальном состоянии;

- действие, рекомендуемое пользователю — повторить операцию позже.

Такое поведение является ожидаемым для архитектуры с синхронными репликами и не свидетельствует о сбое системы.

В перспективе рассматривается доработка сервера приложений, позволяющая:

- автоматически повторять отменённые read-запросы;

- выполнять повтор либо на той же реплике, либо на мастер-базе;

- скрывать подобные технические отмены от конечного пользователя в допустимых сценариях.

### 2.7 Параметр hot_standby_feedback

При использовании читающих реплик параметр `hot_standby_feedback` должен быть включён.

Назначение параметра:

- передача информации о snapshot’ах standby на мастер;

- снижение вероятности конфликтов между VACUUM на мастере и WAL replay на реплике.

Важно понимать, что `hot_standby_feedback`:

- не гарантирует, что VACUUM или autovacuum на мастере не будет выполнен;

- не блокирует уже запущенные процессы VACUUM;

Параметр предназначен для уменьшения частоты возникновения конфликтов между длительными read-запросами на репликах и очисткой старых версий строк (cleanup) на мастере.

Основной эффект использования:

- VACUUM на мастере может быть отложен до завершения read-запросов на реплике;

- снижается вероятность отмены read-запросов из-за конфликтов WAL replay.

Как следствие возможен временный рост таблиц и bloat. Требуется мониторинг VACUUM и состояния таблиц.

### 2.8 Позиция вендора по использованию синхронных реплик

Вендор осознанно принимает следующие риски:

- возможный рост таблиц из-за отложенного VACUUM;

- возможность блокировки COMMIT при длительных read-запросах на репликах.

При этом:

- отмена read-запросов с помощью `max_standby_streaming_delay` считается штатным механизмом;

- длительные аналитические запросы на активно изменяемых таблицах требуют отдельной настройки и должны выполняться на мастер-базе, без использования читающих реплик;

- режим признаётся пригодным для продуктивных систем при корректной эксплуатации.

### 2.9 Архитектура приложения и распределение нагрузки

Сервер приложений не выполняет функции балансировки и отказоустойчивости.

Распределение нагрузки:

- write-запросы → WritePool;

- read-запросы вне транзакций → ReadPool.

Для отказоустойчивой схемы используется внешний балансировщик нагрузки.

Вендором рекомендуется использование HAProxy в качестве балансировщика нагрузки.

Текстовая схема:

    Application Server
      |
      |-- WritePool --> Master DB
      |
      |-- ReadPool --> Load Balancer (HAProxy)
                        |
                        |-- Replica 1
                        |-- Replica 2
                        |-- Master (резерв)

Балансировщик нагрузки выполняет:

- распределение read-нагрузки между репликами;

- автоматическое исключение деградировавших или недоступных реплик;

- переключение на мастер-ноду в случае отказа всех реплик.

При отказе реплики балансировщик автоматически исключает её из распределения нагрузки без изменения конфигурации приложения.

## 3. Асинхронные реплики (в разработке)

Поддержка асинхронных реплик находится в разработке и в текущих версиях Global ERP не поддерживается.

Планируемая архитектура:

- асинхронных реплик может быть произвольное количество;

- сервер приложений никогда не использует их автоматически;

- выбор реплики будет осуществляться на прикладном уровне (интерфейсы, отчёты, печатные формы);

- управление запросами будет выполняться на уровне бизнес-логики, а не SQL.

Как и в случае синхронных реплик, для асинхронного режима требуется внешний балансировщик нагрузки:

- балансировщик скрывает конкретные узлы БД от сервера приложений;

- обеспечивает отказоустойчивость;

- содержит резервную мастер-ноду для fallback-сценариев.

Использование балансировщика нагрузки является обязательным элементом архитектуры асинхронных реплик.

## 4. Настройка подключения

### 4.1 Настройка Standalone

Для подключения читающих реплик в режиме standalone используется `extraConnectionPools`.

В файле `global3config.xml` необходимо заполнить секцию `<databases>`.

Пример конфигурации:

    <databases>
            <database alias="<db_alias>" driver="org.postgresql.Driver" schema="PUBLIC"
                    connectionType="proxyShared" authenticationType="btk"
                    maxActiveThreadCount="1000"
                    activeThreadTimeout="20">

                    <users>
                            <user name="username" password="password"/>
                    </users>

                    <extraConnectionPools>
                            <!-- WritePool - мастер-база -->
                            <pool name="WritePool" schema="PUBLIC"
                                    url="jdbc:postgresql://<host>:<port>/<dbname>"
                                    acceptPrimary="true"
                                    acceptSecondary="true"
                                    acceptTxSession="true"
                                    acceptNoTxSession="false"
                                    priority="1"
                                    minPoolSize="100"
                                    maxPoolSize="110"
                                    initialPoolSize="2"
                                    inactiveConnectionTimeout="30"
                                    poolTimeout="10"
                                    timeBetweenEvictionRunsMillis="5000"
                                    usageRatio="1" />

                            <!-- ReadPool - внешний балансировщик между репликами -->
                            <pool name="ReadPool" schema="PUBLIC"
                                    url="jdbc:postgresql://<host>:<port>/<dbname>"
                                    acceptPrimary="true"
                                    acceptSecondary="true"
                                    acceptTxSession="false"
                                    acceptNoTxSession="true"
                                    priority="100"
                                    minPoolSize="100"
                                    maxPoolSize="110"
                                    initialPoolSize="2"
                                    inactiveConnectionTimeout="30"
                                    poolTimeout="2"
                                    timeBetweenEvictionRunsMillis="5000"
                                    usageRatio="1" />
                    </extraConnectionPools>
            </database>
    </databases>

### 4.2 Описание параметров пулов подключения

Используемые параметры пула (Tomcat JDBC Pool) интерпретируются следующим образом:

- `minPoolSize` — минимальное количество свободных соединений;

- `maxPoolSize` — максимальное количество соединений в пуле;

- `initialPoolSize` — количество соединений при старте сервера;

- `usageRatio` — относительный вес пула при распределении нагрузки (является константой и не подлежит изменению);

- `acceptTxSession` — принимает соединения внутри транзакций;

- `acceptNoTxSession` — принимает соединения вне транзакций;

- `poolTimeout` — время ожидания свободного соединения;

- `inactiveConnectionTimeout` — таймаут неактивного соединения.

Подробная документация по параметрам пула доступна по ссылке: https://help.globalerp.ru/books/gs-docs-sphinx/master/reference/configuration/global3_config_xsd/configuration/databases/database/extraconnectionpools/Pool.html

### 4.3 Режим высокой доступности

Для добавления дополнительных пулов подключений в режиме высокой доступности необходимо:

- внести изменения в `global3config.xml` в архиве `profile.zip` по пути: `globalserver/template/config/global3.config.xml`;

- использовать конфигурацию, аналогичную режиму standalone;

- выполнить стандартное обновление системы, ознакомиться с инструкцией по обновлению можно по ссылке.

## 5. Конфигурация балансировщика нагрузки (HAProxy)

В качестве балансировщика нагрузки для работы с читающими репликами вендором рекомендуется использование HAProxy.

Балансировщик нагрузки решает следующие задачи:

- распределение read-запросов между репликами;

- исключение недоступных или деградировавших реплик;

- fallback на мастер-ноду при отказе всех реплик;

- прозрачность для сервера приложений (сервер не знает о топологии БД).

Ключевые принципы конфигурации HAProxy:

- read-пул указывает на виртуальный адрес балансировщика;

- мастер-база должна присутствовать в конфигурации как резервная;

- health-check должен учитывать доступность БД, а не только сетевую связность;

- при деградации реплики она должна быть исключена из пула до восстановления.

Пример конфигурации HAProxy:

    global
        #log /dev/log local0
        #log /dev/log local1 notice
        #log /var/log/haproxy/haproxy.log local0 info
        log 127.0.0.1:514 local0
        chroot /var/lib/haproxy
        user _haproxy
        group _haproxy
        daemon

    defaults
        log global
        mode tcp
        option tcplog
        option dontlognull
        timeout connect 10s
        timeout client  24h
        timeout server  600s
        option tcpka
        retries 3

    frontend stats
        mode http
        bind :8404
        stats enable
        stats refresh 10s
        stats uri /stats
        stats show-modules
        log-format "%ci:%cp [%t] %ft %b/%s %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r"

    frontend db_rw_frontend
        bind *:6434
        mode tcp
        log-format "%ci:%cp [%t] %ft %b/%s %Tw/%Tc/%Tt %ts %ac/%fc/%bc/%sc/%rc %sq/%bq"
        default_backend db_rw_backend

    frontend db_ro_frontend
        bind *:6433
        mode tcp
        log-format "%ci:%cp [%t] %ft %b/%s %Tw/%Tc/%Tt %ts %ac/%fc/%bc/%sc/%rc %sq/%bq"
        default_backend db_ro_backend

    backend db_rw_backend
        mode tcp
        balance roundrobin
        option tcp-check
        # Серверы СУБД
        server v00011 192.168.129.91:6432 check
        server v00012 192.168.129.92:6432 check

    backend db_ro_backend
        mode tcp
        balance roundrobin
        option tcp-check
        # Серверы СУБД
        server v0013 192.168.129.113:5432 check
        server v0014 192.168.129.120:5432 check
        server v0015 192.168.129.121:5432 check
        server v0012 192.168.129.92:5432 check backup