Представьте, что вам нужно создать реплику для базы данных объёмом 1 TiB. У вас есть быстрый сервер с NVMe-накопителями и сетью 75 Гбит/с, но pg_basebackup обычно выдаёт всего 300–500 МиБ/с из-за своей однопоточной архитектуры, вне зависимости от того, насколько мощное у вас железо (хотя в PG18 есть сюрприз, о нём поговорим позже).
Решение: заменить pg_basebackup на pgBackRest и использовать параллельную обработку, чтобы создавать реплику существенно быстрее, почти полностью забивая пропускную способность (≈97%) на канале 75 Гбит/с.
Примечание: это исследование в духе R&D, сфокусированное на бенчмарках производительности на простаивающих системах, а не готовое руководство по автоматизации для продакшена. Многие важные для продакшн-среды вещи (мониторинг, логика повторов, интеграция с инструментами оркестрации и т. п.) намеренно опущены, чтобы сосредоточиться на ключевых характеристиках производительности.
// 2025-11-12: Перепроверено и обновлено для точности
pg_basebackup — однопоточный
Стандартный подход к созданию реплики Postgres использует pg_basebackup:
pg_basebackup \ --write-recovery-conf \ --wal-method=stream \ --checkpoint=fast \ --progress \ --verbose \ --host=$PRIMARY \ --username=$REPLICATION_USER \ --pgdata=$PG_DATA_DIR
Несмотря на быстрые NVMe-накопители и сеть 75 Гбит/с, pg_basebackup принципиально ограничен своей однопоточной архитектурой. В версиях Postgres до 18 pg_basebackup обычно выдаёт лишь 300–500 МиБ/с, независимо от возможностей железа.
Новый асинхронный ввод-вывод (I/O) в PostgreSQL 18 (мы оставили настройки по умолчанию: io_method=worker и io_workers=3) может заметно ускорить работу. В нашем эксперименте на машинах i4i.32xlarge с локальными NVMe-дисками нам удалось получить 1.08 GiB/s, и это впечатляет. Но ограничение всё равно остаётся. Для больших баз данных это превращается в серьёзное операционное узкое место, особенно когда дисковая подсистема и сеть позволяют прокачивать многие гигабайты в секунду.
Тема многопоточного pg_basebackup годами всплывает в обсуждениях на pgsql-hackers (пример 1, пример 2). Однако до реализации и включения в основную ветку дело так и не дошло. По сей день pg_basebackup остаётся однопоточным.
Примечание: производительность PostgreSQL 18 «из коробки» с его асинхронным вводом-выводом стала приятным сюрпризом и составила 1.08 GiB/s по сравнению с ожидаемыми 300–500 МиБ/с в версиях до 18. Это даёт ускорение в 2–3 раза (примерно соответствует 3 рабочим процессам I/O по умолчанию), но pg_basebackup по-прежнему однопоточный и оставляет большую часть доступной пропускной способности сети и хранилища неиспользованной.
Альтернатива: pgBackRest
pgBackRest в первую очередь предназначен для резервного копирования и восстановления Postgres, но ничто не мешает использовать его, чтобы скопировать каталог данных с одного сервера на другой. Это ровно то, что нам нужно для создания реплики. Этот подход становится очень эффективным при --process-max=8 и выше. Настройка не требует никаких специальных конфигураций на исходной базе: ни Postgres, ни pgBackRest не нужно заранее настраивать на основном сервере.
Требования
Беспарольный доступ по SSH к
postgres@$PRIMARYс использованием приватного ключаБеспарольное подключение к PostgreSQL (libpq/psql) для
$REPLICATION_USERк$PRIMARY(обычное SQL-подключение нужно pgBackRest для проверки; настройка репликации через слот и primary_conninfo выполняется в конце)pgBackRest установлен и на основном сервере, и на реплике в одной и той же версии. Используйте свежую версию (2.55+), чтобы гарантировать поддержку PostgreSQL 18 и наличие оптимизаций производительности
Вся конфигурация выполняется на целевом сервере, аналогично pg_basebackup.
Конфигурация
Сначала проверьте окружение:
postgres --version pgbackrest --version uname -a lsblk -o NAME,SIZE,MODEL
Задайте переменные:
export PRIMARY=primary-db export REPLICATION_USER=replica export STANZA_TMP=replica_load export HOSTNAME=$(hostname) export PG_DIR=/var/lib/postgresql/18 export PG_DATA_DIR="$PG_DIR/main" export TMP_DIR="$PG_DIR/pgbackrest_tmp" export CONFIG_FILE="$TMP_DIR/pgbackrest.conf" # Путь к PGDATA на основном сервере (должен совпадать с фактическим путём на основном сервере) export PRIMARY_PGDATA=/var/lib/postgresql/18/main # Количество параллельных процессов (подбирайте под вашу сеть и CPU) # Начните с 8–16, увеличивайте до 32 для сетей 10+ Гбит/с export N=16 mkdir -p "$TMP_DIR" "$PG_DATA_DIR" && chmod 700 "$TMP_DIR" "$PG_DATA_DIR"
Важно: убедитесь, что
$TMP_DIR(путь к репозиторию) и каталог spool уpgBackRestнаходятся на быстром хранилище (в той же файловой системе, что и PGDATA). Использование/tmpна медленном томе EBS вместо NVMe RAID снизило пропускную способность с 9+ GiB/s до 110 MiB/s, то есть дало 80-кратную деградацию производительности. Всегда размещайте repo и spool каталогиpgBackRestна самом быстром доступном хранилище (NVMe, а не EBS).
Создайте временный файл конфигурации, чтобы ��е перезаписывать /etc/pgbackrest.conf:
# Если делаете бэкап со standby, а не с primary, добавьте: # pg2-host=replica-db # pg2-path=$PG_DATA_DIR cat <<EOS > "$CONFIG_FILE" [$STANZA_TMP] pg1-host=$PRIMARY pg1-path=$PRIMARY_PGDATA repo99-path=$TMP_DIR repo99-type=posix repo99-retention-full=1 spool-path=$TMP_DIR/spool expire-auto=n start-fast=y log-level-console=info compress-type=none archive-check=n log-path=/var/log/postgresql/ EOS
Выполнение
# Настраиваем обработку ошибок и очистку trap cleanup EXIT cleanup() { if [ $? -ne 0 ]; then echo "Резервное копирование не удалось, выполняю очистку..." rm -rf "$TMP_DIR" # Логируем сбой для мониторинга logger -t pgbackrest "Создание реплики для $PRIMARY не удалось" fi } # Создаём stanza с обработкой ошибок pgbackrest --config="$CONFIG_FILE" --stanza="$STANZA_TMP" stanza-create || { echo "ОШИБКА: не удалось создать stanza. Частые причины:" echo " - Нет доступа по SSH к $PRIMARY (проверка: ssh postgres@$PRIMARY 'echo ok')" echo " - Неверный pg1-path в конфиге (проверьте путь PGDATA на основном сервере)" echo " - pgBackRest не установлен на основном сервере (проверка: ssh postgres@$PRIMARY 'which pgbackrest')" echo " - Postgres не запущен на основном сервере" exit 1 } # Если делаете бэкап со standby, добавьте --backup-standby=y # Когда увидите "INFO: wait for replay on the standby to reach", # выполните CHECKPOINT на этом standby time pgbackrest \ --config="$CONFIG_FILE" \ --stanza="$STANZA_TMP" \ --type=full \ --process-max="$N" \ backup # Начните с --process-max=8, чтобы сбалансировать скорость и потребление ресурсов # Увеличивайте до 16–32 для максимальной скорости на быстрых сетях (10+ Гбит/с) # Сетевая компрессия (--compress-level-network=1) даёт минимальный эффект # на быстрых сетях (<6% прироста), но полезна на более медленных (<10 Гбит/с)
Если на исходном сервере ограничены ресурсы CPU, понизьте приоритет процессов pgBackRest на о��новном сервере:
# Добавьте в cron на основном сервере на время окна бэкапа * * * * * for pid in $(pgrep pgbackrest); do renice 19 -p "$pid"; done
Завершение
# Проверка безопасности: убедитесь, что целевой каталог пуст if [ "$(ls -A "$PG_DATA_DIR" 2>/dev/null)" ]; then echo "ОШИБКА: $PG_DATA_DIR не пуст. Сначала очистите его или используйте другой каталог" exit 1 fi # Исследовательское упрощение: копируем файлы напрямую из репозитория pgBackRest # (без проверки при восстановлении). Для продакшена не рекомендуется. mv "$TMP_DIR/backup/$STANZA_TMP/latest/pg_data"/* "$PG_DATA_DIR/" rm -rf "$TMP_DIR" # Также удаляет временный файл конфигурации touch "$PG_DATA_DIR/standby.signal" cat <<EOS >> "$PG_DATA_DIR/postgresql.auto.conf" primary_conninfo = 'user=''$REPLICATION_USER'' host=''$PRIMARY'' application_name=''$HOSTNAME'' ' EOS # Примечание: для потоковой репликации restore_command не нужен # Реплика будет получать всё по стримингу через primary_conninfo # Важно: перед запуском реплики создайте слот репликации на основном сервере # Это не даст WAL-файлам удалиться до того, как реплика их потребит: # psql -h $PRIMARY -U $REPLICATION_USER -c "SELECT pg_create_physical_replication_slot('replica_slot_name');" # Запустите экземпляр Postgres на реплике как обычно
Важно: после создания реплики pgBackRest больше не нужен. Реплика будет использовать стандартную потоковую репликацию через primary_conninfo. Этот приём нужен только для ускорения первоначального копирования данных. После запуска реплика работает как обычная потоковая реплика.
Для продакшен-развёртываний, где на основном сервере настроено архивирование WAL, имеет смысл добавить restore_command, чтобы включить восстановление из архива как запасной вариант, если реплика начнёт отставать от потоковой репликации. Однако это не зависит от процесса создания реплики через pgBackRest, описанного здесь.
Бенчмарки производительности
Среда тестирования:
База данных: 1.023 TiB (70 баз данных pgbench, ~7 млрд строк)
Хранилище: 8× 3 750 ГБ NVMe SSD в RAID0 (AWS
i4i.32xlarge)Сеть: 75 Гбит/с = 9.375 ГБ/с (десятичные) = 8.73 GiB/s (двоичные). Ниже мы приводим значения в GiB/s
CPU: 128 vCPU
PostgreSQL: 18.0 со стандартным асинхронным вводом-выводом на воркерах (
io_method=worker,io_workers=3)Условия тестов: без сетевой компрессии (compress-type=none), без проверки контрольных сумм для максимальной пропускной способности
Холодный кэш: между тестами мы перезапускали PostgreSQL на реплике и сбрасывали страничный кэш ОС на обоих серверах (
sync; echo 3 > /proc/sys/vm/drop_caches). Это неидеально, но даёт достаточно «холодные» условия для каждого прогона
Результаты (холодный кэш для каждого теста):
# Базовый уровень pg_basebackup (PostgreSQL 18 с асинхронным I/O на воркерах) time pg_basebackup \ --pgdata="$PG_DATA_DIR" \ --write-recovery-conf \ --wal-method=stream \ --checkpoint=fast \ --progress \ --verbose \ --host=$PRIMARY \ --username=$REPLICATION_USER # real 950s (15.8 minutes) - 1.08 GiB/s # pgbackrest с 4 процессами time pgbackrest --stanza="$STANZA_TMP" --type=full --process-max=4 backup # real 575s (9.6 minutes) - 1.78 GiB/s # pgbackrest с 8 процессами time pgbackrest --stanza="$STANZA_TMP" --type=full --process-max=8 backup # real 298s (5.0 minutes) - 3.43 GiB/s # pgbackrest с 16 процессами time pgbackrest --stanza="$STANZA_TMP" --type=full --process-max=16 backup # real 164s (2.7 minutes) - 6.24 GiB/s # pgbackrest с 32 процессами time pgbackrest --stanza="$STANZA_TMP" --type=full --process-max=32 backup # real 101s (1.7 minutes) - 10.13 GiB/s - сеть насыщена # pgbackrest с 64 процессами time pgbackrest --stanza="$STANZA_TMP" --type=full --process-max=64 backup # real 107s (1.8 minutes) - 9.56 GiB/s - ограничение по сети # pgbackrest со 128 процессами time pgbackrest --stanza="$STANZA_TMP" --type=full --process-max=128 backup # real 133s (2.2 minutes) - 7.69 GiB/s - накладные расходы SSH снижают пропускную способность # pgbackrest с 64 процессами + сетевая компрессия time pgbackrest --stanza="$STANZA_TMP" --type=full --process-max=64 --compress-level-network=1 backup # real 101s (1.7 minutes) - 10.13 GiB/s - улучшение на 6% # pgbackrest со 128 процессами + сетевая компрессия time pgbackrest --stanza="$STANZA_TMP" --type=full --process-max=128 --compress-level-network=1 backup # real 127s (2.1 minutes) - 8.25 GiB/s - улучшение на 5%
Анализ
метод | максимум параллельных процессов | длительность (сек) | пропускная способность (логическая), GiB/с | TiB в час | ускорение (во сколько раз) |
pg_basebackup | 1 | 950 | 1.08 | 3.80 | 1.0x |
pgBackRest | 4 | 575 | 1.78 | 6.26 | 1.7x |
pgBackRest | 8 | 298 | 3.43 | 12.06 | 3.2x |
pgBackRest | 16 | 164 | 6.24 | 21.94 | 5.8x |
pgBackRest | 32 | 101 | 10.13 | 35.61 | 9.4x |
pgBackRest | 64 | 107 | 9.56 | 33.61 | 8.9x |
pgBackRest | 128 | 133 | 7.69 | 27.04 | 7.1x |
pgBackRest + compress | 64 | 101 | 10.13 | 35.61 | 9.4x |
pgBackRest + compress | 128 | 127 | 8.25 | 29.00 | 8.1x |
Примечание: эти измерения включают накладные расходы на старт (установление соединений, первичные рукопожатия). Для более крупных баз (10+ TiB) эти стартовые накладные расходы становятся пренебрежимо малыми, и устойчивая скорость передачи приближается к показанным пиковым значениям. Например, перенос 10 TiB при скорости, соответствующей 32 процессам, занял бы примерно 1000 секунд, а не 1010 секунд (101 с × 10; это очень грубая оценка, приведена лишь для примера).
Ниже приведена визуализация:

Примечание о расчётах пропускной способности: эти значения получены как логический размер базы (1.023 TiB), делённый на «реальное» время выполнения (wall‑clock). Показатель 10.13 GiB/s превышает теоретический предел сети 8.73 GiB/s для 75 Гбит/с, потому что pgBackRest передаёт меньше, чем полный логический размер, за счёт следующего:
Разрежённые участки файлов и нулевые блоки не передаются
Сжатие метаданных на уровне протокола
Эффективная обработка частично заполненных блоков
Фактическая пропускная способность «по проводу» (on-wire) достигает пика примерно на уровне 8.5 GiB/s (по данным счётчиков сетевого интерфейса), что даёт около 97% теоретического предела с учётом протокольных накладных расходов. Более высокие «эффективные» значения показывают, что pgBackRest способен создать реплику базы на 1 TiB быстрее, чем сеть физически могла бы передать 1 TiB сырых данных.
Ключевые наблюдения:
Ускорение асинхронного I/O в PostgreSQL 18:
pg_basebackupдостигает 1.08 GiB/s с асинхронным вводом-выводом на воркерах, что в 2–3 раза быстрее старых версий PostgreSQL (обычно 300–500 MiB/s) и примерно соответствует 3 рабочим процессам I/O по умолчанию.Почти линейное масштабирование: производительность почти удваивается при каждом удвоении параллелизма вплоть до 16 процессов (96% эффективности).
Насыщение сети на 32 процессах: насыщение «по проводу» на уровне ~8.5 GiB/s (~97% от теоретических 8.73 GiB/s); логическая пропускная способность показывает 10.13 GiB/s, потому что pgBackRest пропускает разрежённые области и нулевые блоки.
Оптимальная конфигурация:
--process-max=32даёт лучшую производительность (в 9.4 раза быстрееpg_basebackup).Сетевая компрессия даёт минимальный эффект:
--compress-level-network=1обеспечивает лишь 5–6% улучшения на сети 75 Гбит/с, и для высокоскоростных сред это не стоит затрат CPU.Убывающая отдача после 32: больший параллелизм добавляет накладные расходы SSH без роста пропускной способности.
Для базы 1 TiB: время создания реплики сократилось с 15.8 минут до 1.7 минуты (снижение времени на 89%)
Практические соображения
Для продакшен-развёртываний:
Начните с
--process-max=$(($(nproc)/2)), чтобы сбалансировать производительность и нагрузку на систему.Мониторьте загрузку CPU на исходной и целевой стороне во время бэкапа.
Сетевая компрессия (
--compress-level-network=1), похоже, не даёт заметного улучшения на быстрых сетях (10+ Гбит/с). Используйте её на более медленных сетях (<10 Гбит/с), где эффект может быть выше.Рассмотрите использование
reniceна исходном сервере, если конкуренция за CPU влияет на продакшен-нагрузку.Убедитесь, что на целевой стороне достаточно производительности I/O, чтобы переварить параллельную запись
При высоком параллелизме (64+) увеличьте лимиты SSH:
MaxStartups=200:30:300иMaxSessions=200в/etc/ssh/sshd_config. Альтернатива: SSH ControlMaster создаёт одно постоянное соединение, через которое мультиплексируются все последующие SSH-с��ссии. Мы этот подход ещё не тестировали, но его стоит изучить, чтобы снизить накладные расходы на соединения при высоком параллелизме.В продакшене включите проверку контрольных сумм (
--checksum-page=y) для валидации целостности данных, хотя это снизит пропускную способность. В наших бенчмарках контрольные суммы были отключены, чтобы измерить максимально возможную «сырую» скорость передачи.
Выигрыш в производительности для больших баз данных существенный: сокращение времени создания реплики с 15.8 минут до 1.7 минуты позволяет агрессивнее тестировать аварийное восстановление, быстрее поднимать окружения и снижать операционные риски в сценариях переключения (failover).
Когда лучше использовать стандартный pg_basebackup
Хотя pgBackRest даёт заметный прирост производительности для больших баз на быстром железе, в следующих случаях лучше придерживаться стандартного pg_basebackup:
Небольшие базы (< 100 GiB): накладные расходы на настройку и дополнительная сложность перевешивают выигрыш по скорости. Экономия времени минимальна и не стоит лишней конфигурации.
Медленное хранилище (не NVMe): узким местом становится дисковый ввод-вывод ещё до того, как упрётся сеть. Параллельная обработка не поможет, если хранилище не успевает.
Ограниченная сеть (< 10 Гбит/с):
pg_basebackupи так способен насытить канал 1–8 Гбит/с. Однопоточность не является узким местом в таких средах.Ограниченные ресурсы на основном сервере: если мало CPU или памяти, параллельная обработка становится контрпродуктивной. Накладные расходы на управление множеством процессов могут ухудшить производительность основного сервера.
Простые окружения: когда разница «15 минут против 2» не оправдывает дополнительную сложность. Иногда операционная простота ценнее голой скорости.
Идеи для следующей итерации
Хотя эти бенчмарки показывают заметный прирост производительности, место для оптимизаций остаётся. В следующей итерации стоит изучить:
SSH ControlMaster для мультиплексирования соединений: вместо увеличения лимитов на SSH-подключения использовать SSH ControlMaster, чтобы создать одно постоянное соединение, через которое будут мультиплексироваться все процессы pgBackRest. Это может снизить накладные расходы на соединения при высоком параллелизме (64+ процессов).
Настройка сети для высокоскоростных соединений: для сетей 75+ Гбит/с тюнинг параметров ядра может помочь полнее использовать доступную полосу. Стоит рассмотреть такие настройки:
net.core.rmem_max,net.core.wmem_max,net.ipv4.tcp_rmem,net.ipv4.tcp_wmem,net.core.netdev_max_backlogиnet.ipv4.tcp_congestion_control(BBR)Формула для числа процессов: вместо подбора методом проб и ошибок можно вывести формулу, которая оценивает оптимальное число процессов по полосе сети и числу доступных ядер CPU, например так (черновой набросок):
# Расчёт оптимального числа процессов на основе эмпирических данных: # Наши тесты: 32 процесса насытили 75 Гбит/с (≈2.5 Гбит/с на процесс) NETWORK_GBPS=75 BASELINE=$(( NETWORK_GBPS / 3 )) # Консервативная отправная точка PROCESS_COUNT=$(( BASELINE < $(nproc) ? BASELINE : $(nproc) )) # Для 75 Гбит/с и 128 ядер: baseline = 25 процессов # На практике добавляем 20–30% запаса: 25 × 1.3 ≈ 32 процесса (совпадает с нашим оптимумом) # Начните с baseline и увеличивайте, если по CPU и сети остаётся запас
Измерять реальную скорость передачи данных по сети: наши текущие цифры показывают «эффективную» пропускную способность (логический размер ÷ время), которая может превышать физические пределы сети благодаря тому, что pgBackRest оптимизирует передачу разрежённых файлов и метаданных. В следующей итерации стоит измерять фактическую скорость «по проводу» (on-wire), используя счётчики сетевого интерфейса (NIC) или инструменты вроде
ifstat/sar -n DEVво время прогона, чтобы различать логическую и физическую скорость передачи.Тестирование на более новых инстансах AWS: инстансы AWS
i7iиi7ieимеют скорость сети 100 Гбит/с (против 75 Гбит/с уi4i), что потенциально может дать примерно на 30% больше пропускной способности, вплоть до 13+ GiB/s при использованииpgBackRest с --process-max=32или выше.Сравнение методов ввода-вывода PostgreSQL: протестировать производительность
pg_basebackupс разными конфигурациями I/O, чтобы понять вклад каждой:Более высокие значения
io_workers(по умолчанию 3, максимум 32), чтобы проверить, улучшают ли дополнительные воркеры пропускную способность.io_method=io_uringдля систем, собранных с поддержкой liburing.io_method=syncна PostgreSQL 18 и PostgreSQL 17 в качестве базовой линии, чтобы количественно оценить выигрыш от асинхронного I/O.
Эти оптимизации могут помочь поднять пропускную способность ещё выше и уменьшить н��кладные расходы, которые мы наблюдали при очень высоком параллелизме.
Ключевые выводы
Однопоточная архитектура
pg_basebackupограничивает пропускную способность независимо от возможностей железа (хотя асинхронный ввод-вывод на воркерах в PostgreSQL 18 даёт заметное улучшение).pgBackRest с параллельной обработкой может утилизировать доступную пропускную способность сети и производительность дисковой подсистемы.
Имеет смысл ожидать заметно лучшую скорость передачи данных уже при
--process-max=8и выше.Оптимальное число процессов зависит от доступных ядер CPU, пропускной способности сети и производительности дисковой подсистемы.
Для очень больших баз на быстром железе
pgBackRestможет сократить время создания реплики в 6–10 раз и даже сильнее.Настройка не требует никакой специальной конфигурации на исходной базе.
Все операции выполняются с целевого сервера, по схеме, похожей на рабочий процесс
pg_basebackup.Расположение хранилища критически важно: всегда используйте быстрое хранилище (NVMe, а не EBS) для repo и spool каталогов
pgBackRest, чтобы избежать 80-кратной деградации производительности.

Если такие эксперименты хочется превратить в устойчивую практику, на курсе «PostgreSQL для администраторов и разработчиков» разбираем настройку кластера, резервное копирование и диагностику блокировок, взаимные блокировки и многое другое. В итоге проще держать производительность и надёжность под контролем на больших объёмах данных. Готовы к обучению? Пройдите вступительный тест и выявите пробелы в знаниях.
А чтобы узнать больше о формате обучения и познакомиться с преподавателями, приходите на бесплатные уроки:
16 марта, 20:00. «PostgreSQL и секционирование: „разделяй и властвуй!“». Записаться
25 марта, 20:00. «Механизмы блокировок в PostgreSQL». Записаться
