
В статье рассматривается исполнительная часть утилиты ggrebalance: механизм физического перемещения primary- и mirror-сегментов между хостами кластера Greengage DB (open-source форк Greenplum), устройство конечного автомата ребаланса, отслеживание статусов операций, обработка сбоев и реентерабельность, откат перемещений, а также практические рекомендации по эксплуатации и итоговое сравнение с альтернативными инструментами.
В предыдущей части мы детально рассмотрели механизм планирования: как планировщик ggrebalance строит список перемещений сегментов для их равномерного распределения по хостам, и какие сценарии он охватывает — шринк, добавление хостов, декомиссия. Теперь речь пойдет о том, как именно этот план воплощается в жизнь: как сегменты физически перемещаются между машинами работающего кластера.
3.1 Проблемы перемещения сегментов
Перенос сегмента с данными — фундаментально сложная задача. Сегмент Greengage — полноценный экземпляр PostgreSQL, который обслуживает свою долю каждого распределенного запроса, участвует в двухфазном коммите, непрерывно стримит WAL, взаимодействует с механизмом FTS (Fault Tolerance Service) и т.д. Его адрес, порт и путь к данным зафиксированы в gp_segment_configuration — таблице, к которой диспетчер обращается при маршрутизации каждого запроса. Любое рассогласование между каталогом и реальным состоянием сегмента немедленно нарушает работу кластера. Также и перемещение сегмента может нарушить его работу, поэтому без должной предварительной подготовки осуществление переноса несет риск потери данных, нарушения консистентности и ошибок запросов.
Теоретически, можно пойти офлайн-путем: остановить весь кластер, скопировать PGDATA на новые хосты, обновить каталог gp_segment_configuration в режиме coordinator-only и все поднять. Это относительно просто, однако подход подвержен ошибкам; также в данной ситуации возникает неприемлемо долгий для продакшен-среды даунтайм. ggrebalance, в свою очередь, перемещает сегменты в режиме онлайн, используя возможности Greengage: стриминговую репликацию, работу FTS probe и смену ролей между primary и mirror.
Greengage сам по себе не предоставляет единого инструмента для произвольного перемещения сегментов между хостами. ggrebalance комбинирует два стандартных инструмента экосистемы, каждый из которых решает свою подзадачу:
gpmovemirrors— физическое перемещение данных. Утилита предназначена для перемещения mirror-сегментов. Ее работа включает три фазы: создание нового каталога данных на целевом хосте черезpg_basebackupс физическим копированием данных с primary-сегмента, ожидание завершения синхронизации нового экземпляра с первичным, и, наконец, обновление записи в системном каталогеgp_segment_configuration. Весь процесс происходит без каких-либо остановок primary-сегментов — операция полностью прозрачна для пользователей, выполняющих запросы. Ключевое ограничение утилитыgpmovemirrorsсостоит в том, что она работает исключительно с зеркалами. Это делает перемещение primary-сегментов более сложной задачей, требующей отдельного механизма.gprecoverseg -r— смена ролей. Утилита восстанавливает "предпочтительное" распределение ролей — переводит сегменты в роли, указанные в полеpreferred_roleкаталогаgp_segment_configuration. Ее механизм работы следующий: утилита определяет пары, у которых текущая роль расходится сpreferred_role, затем останавливает primary-сегменты в режимеfast. После этого FTS автоматически фиксирует отказ и промоутит зеркало в роль primary. В завершение запускается обычныйgprecoverseg, чтобы восстановить синхронизацию — теперь бывший primary-сегмент становится зеркалом и начинает реплицироваться с новоиспеченного сегмента. Смена ролей является одним из этапов перемещения primary-сегмента (перенос primary подробно рассматривается в следующем разделе).
Операции перемещения неизбежно затрагивают доступность и надежность кластера, и это необходимо учитывать при планировании работ. Во время перемещения зеркала (gpmovemirrors) primary-сегмент продолжает обслуживать запросы без прерывания. Однако зеркало временно недоступно в процессе копирования и последующей синхронизации. Это означает временную потерю отказоустойчивости: если первичный сегмент откажет в этот период, соответствующие данные станут недоступны для восстановления.
Если говорить о смене ролей, то primary-сегмент останавливается принудительно, а все транзакции, которые он обрабатывал в этот момент, прерываются. FTS "продвигает" зеркало в роль первичного. Это занимает какое-то время, что приводит к даунтайму, однако он заметно ниже, чем в офлайн-подходе. ggrebalance предоставляет опцию --replay-lag, передаваемую в gprecoverseg, которая задает максимально допустимое отставание зеркала перед инициированием switchover (переключение). Это позволяет управлять балансом между скоростью операции и гарантиями консистентности. При этом ввиду реентерабельности ggrebalance этап смены ролей можно отложить на запланированные часы обслуживания, оставив кластер в промежуточной, но в полностью работоспособной конфигурации.
3.2 Типы выполняемых перемещений
Рассмотрим процесс балансирования кластера после выполненного шринка (Рис. 1).

Компонент планировщика, описанный во второй статье серии, строит план перемещений, выполнение которого приводит к равномерному распределению сегментов по хостам. План выглядит следующим образом (идентификация сегментов начинается с content=0):
20260412:20:09:41:014374 ggrebalance:cdw:gpadmin-[INFO]:-Final plan: ---------------------------BALANCE MOVES--------------------------- Total moves planned: 4 [1] Move Segment(content=3, dbid=5, role=p) [16.25 GB] From: sdw1:7005:/home/gpadmin/.data/primary/gpseg3 To: sdw3:7005:/home/gpadmin/.data/primary/gpseg3 [2] Move Segment(content=3, dbid=17, role=m) [16.19 GB] From: sdw2:7055:/home/gpadmin/.data/mirror/gpseg3 To: sdw1:7055:/home/gpadmin/.data/mirror/gpseg3 [3] Move Segment(content=7, dbid=9, role=p) [16.25 GB] From: sdw2:7009:/home/gpadmin/.data/primary/gpseg7 To: sdw3:7009:/home/gpadmin/.data/primary/gpseg7 [4] Move Segment(content=7, dbid=21, role=m) [16.19 GB] From: sdw3:7059:/home/gpadmin/.data/mirror/gpseg7 To: sdw1:7059:/home/gpadmin/.data/mirror/gpseg7 =====================================================================
Выводимый план является логическим и показывает, какие сегменты и куда необходимо перенести, чтобы в итоге кластер стал сбалансированным. Для перемещения зеркала такого представления более чем достаточно, так как зеркало можно одноэтапно перенести на другой хост с помощью одного вызова утилиты gpmovemirrors. Но с primary-сегментами дело обстоит чуть сложнее — Greengage не поддерживает онлайн-миграцию первичного сегмента без прерывания обслуживания. Поэтому ggrebalance осуществляет перемещение первичного сегмента через трехшаговую схему: перевести зеркало в роль первичного, перенести бывший первичный (теперь зеркало) на целевой хост, а затем вернуть роли обратно. Эта последовательность шагов реализуется в ggrebalance следующим образом (Рис. 2):
Шаг 1. Switchover primary mirror
Чтобы вызов gprecoverseg -r привел к смене ролей, необходимо, чтобы в gp_segment_configuration для сегментов было несоответствие атрибутов role и preferred_role. Для этого при switchover ggrebalance атомарно обновляет две строки gp_segment_configuration: у бывшего primary меняет preferred_role с p на m и у mirror — с m на p. Затем вызывается gprecoverseg -r, который промоутит в primary все зеркала сегментов, для которых есть несоответствие атрибутов каталога.
Шаг 2. Перемещение зеркала
Старый первичный сегмент, теперь работающий как зеркало, переносится через gpmovemirrors на целевой хост. В это время кластер работает штатно — новый первичный сегмент обслуживает запросы, а перенесенное зеркало синхронизируется с ним уже на новом месте.
Шаг 3. Switchover mirror primary
Еще один вызов gprecoverseg -r с предварительным ручным обновлением каталога возвращает предпочитаемые роли: сегмент на целевом хосте становится первичным, а бывший первичный (исходного хоста) — зеркалом. По завершении этого шага первичный сегмент "переехал"; зеркало относительно него расположено там, где и было изначально.

Из-за достаточно продолжительного времени работы pg_basebackup во время вызова gpmovemirrors, выполнять последовательно шаги плана, очевидно, неуместно в продакшен-среде. ggrebalance старается распараллелить выполнение перемещений следующим образом. Логический план, сформированный планировщиком, преобразуется в физический план компонентом-экзекьютором утилиты. На уровне выполнения весь план разбивается на атомарные шаги трех видов, реализованных как подклассы RebalanceStep, и описанных в таблице 1.
Таблица 1. Представление шагов в экзекьюторе | |||
Тип шага | Python-класс | Инструмент | Суть операции |
|---|---|---|---|
Перемещение зеркала | RebalanceStepMoveMirror | gpmovemirrors | Физическое перемещение данных зеркала |
Переключение в зеркало | RebalanceStepSwitchoverToMirror | gprecoverseg -r | Первичный |
Переключение в первичный | RebalanceStepSwitchoverToPrimary | gprecoverseg -r | Зеркало |
При этом шаги одного типа, которые можно выполнять параллельно, группируются в батчи. Например, для нескольких перемещений первичных сегментов реализация не чередует шаги смены ролей/перемещение (switchover1 → move1 → switchover_back1 → switchover2 → move2 → …), а объединяет однотипные шаги. Это позволяет запускать gprecoverseg один раз для группы switchover’ов, а не по одному вызову на каждый сегмент. Операции батча выполняются одновременно, а размер батча можно контролировать опцией --parallel.
3.3 Конечный автомат ребаланса
Выполнение балансировки кластера реализовано, как и операция шринка, на базе машины состояний. Подход с явными состояниями и переходами дает несколько ключевых преимуществ: фиксацию текущей стадии в долговременном хранилище, воспроизводимость при сбоях и четкое разграничение логики каждого этапа. Именно то, что каждое состояние несет ответственность только за одно действие и немедленно сохраняет факт своего входа, делает RebalanceSM реентерабельным. Диаграммы состояний ребаланса и его компонентов изображены на рисунках 3 и 4.

STATE_CHECK_PREVIOUS_RUN
Точка входа при каждом запуске RebalanceSM. Читает последнее сохраненное состояние из служебной схемы ggrebalance (об отслеживании статусов в следующем разделе) и определяет, является ли данный запуск новым или продолжением прерванной операции.
STATE_REBALANCE_PREPARE_MOVES_STARTED
Единовременная инициализация: план трансформируется в конкретные шаги выполнения, каждому присваивается порядковый номер, и все шаги сохраняются в схему.
STATE_REBALANCE_EXECUTION_STARTED
Главный управляющий цикл, который повторно активируется после каждого выполненного батча. Именно здесь происходит: проверка завершенности всех шагов, обработка шагов в статусе ERROR, оставшихся от прерванных запусков, формирование очередного батча, его выполнение через вызов gpmovemirrors или gprecoverseg -r в зависимости от типа шагов в нем.
STATE_REBALANCE_EXECUTION_AWAITING_SWITCHOVER_APPROVE_STARTED
Интерактивный контрольный пункт. ggrebalance по умолчанию явно спрашивает пользователя перед каждым батчем последовательных switchover’ов о продолжении операции. Это вводится из-за наличия даунтайма при выполнении смены ролей, так как в этот момент останавливаются primary и mirror. Избежать ручного контроля каждого такого этапа возможно, передав опцию -y / --approve-swap-roles или --non-interactive-mode; она позволяет автоматически одобрять смены ролей.

Также предусмотрена возможность отката (rollback) перемещений. Выполнение отката происходит аналогично прямому ходу ребаланса, присутствуют лишь определенные нюансы реализации и трекинга состояний. Поток отката намеренно сходится с STATE_REBALANCE_EXECUTION_STARTED — это полностью исключает дублирование логики выполнения. Подробнее про сценарий отката будет рассказано в следующих разделах.
3.4 Отслеживание статусов операций
Из предыдущих статей уже известно, что каждому этапу работы ggrebalance, будь то шагам шринка или балансировки кластера, соответствует определенный статус, который хранится в Greengage в служебной схеме ggrebalance базы данных postgres (Рис. 5). Схема содержит: сохраненный план (для восстановления после прерывания), текущее состояние главного автомата, автоматов шринка и ребаланса, а также таблицы с детальной информацией о каждом этапе работы. Решение хранить статусы непосредственно в СУБД было принято для того, чтобы обеспечить отслеживаемость работы утилиты и избежать потери информации о выполненных операциях.

Говоря о статусах процесса балансировки, в таблице ggrebalance.segment_move_steps для каждого запланированного перемещения хранится один из перечисленных статусов.
Статус | Значение |
|---|---|
PLANNED | Запланирован, готов к выполнению |
APPROVE_REQUIRED | Ожидает подтверждения оператора |
IN_PROGRESS | Выполняется в настоящий момент |
DONE | Успешно завершен |
ERROR | Завершился с ошибкой |
CANCELLED | Отменен из-за ошибки другого шага |
Как уже упоминалось, шаги switchover создаются со статусом APPROVE_REQUIRED намеренно: переключение ролей кратковременно влияет на поведение кластера, и оператор должен сознательно подтвердить готовность к этому.
Также утилита активно пишет в лог, как это делают другие кластерные команды. Как в стандартном потоке, так и в файле в директории gpAdminLogs будет содержаться более подробная информация о ходе выполнения ggrebalance. Наполнение логов можно контролировать опциями: --verbose — детальный вывод отладочной информации о работе Python-утилит по каждому шагу, --quiet — подавление обычных логов.
3.5 Реентерабельность. Возможные сбои и их обработка
Любая длительная операция в распределенной системе должна предполагать, что она будет прервана: сетевым сбоем, отключением координатора, истечением окна обслуживания. ggrebalance проектировался исходя из этого допущения — каждый запуск утилиты восстанавливает контекст из служебной схемы и продолжает работу с того места, где предыдущий запуск завершился.
При каждом запуске STATE_CHECK_PREVIOUS_RUN читает последнее сохраненное состояние из схемы и определяет следующее состояние, переход в которое возобновит прерванный процесс. Логика маппинга такова:
Если сохраненного состояния нет (
STATE_NOT_DEFINED) — это свежий запуск, переходим вSTATE_REBALANCE_STARTED.Если зафиксировано финальное состояние — кластер уже сбалансирован, ничего делать не нужно.
STATE_REBALANCE_EXECUTION_STARTED,STATE_REBALANCE_MOVES_SUCCEEDEDиSTATE_REBALANCE_EXECUTION_AWAITING_SWITCHOVER_APPROVE_DONE— все три отображаются обратно вSTATE_REBALANCE_EXECUTION_STARTED, потому что это состояние умеет "вылечить" себя при входе: сначала оно анализирует и исправляет шаги в статусеERROR, и только потом продолжает выполнение.Состояния отката переходят к следующему состоянию этого же потока.
Все прочие состояния переходят к следующему состоянию основного потока (Рис. 3).
Для шагов типа RebalanceStepMoveMirror ggrebalance при повторном запуске выполняет детальный анализ каждого сбойного шага, основанный на фактическом состоянии кластера:
Проверка каталога.
Запрашивается
gp_segment_configurationдля данногоdbid. Еслиhostnameсегмента уже совпадает с целевым — каталог был обновлен до сбоя.Проверка конфигурационного файла.
Если каталог обновлен, через
GpConfigHelperчитается postgresql.conf на целевом хосте для проверки параметраport. Это позволяет определить, насколько далеко продвинулось выполнениеgpmovemirrorsдо сбоя.Ожидание старта сегмента.
Если порт тоже обновлен, запускается цикл опроса состояния сегмента. Если сегмент запустился и вошел в синхронизированный режим, шаг помечается как
DONE— никаких дополнительных действий не требуется.Интерактивное продление ожидания.
Если тайм-аут ожидания истек — в интерактивном режиме оператору предлагается продлить ожидание.
Retry/Rollback.
Если шаг так и не восстановился самостоятельно, оператору предлагается повторить попытку или откатить шаг.
Для шагов типа switchover алгоритм значительно проще: поскольку gprecoverseg идемпотентен, оператору предлагается просто повторить операцию или откатить ее.
Приведем в качестве примеров три сценария. Используется следующий кластер: восемь хостов sdw-1 - sdw-8, по восемь primary на каждом, итого 64 сегмента. Зеркала распределены по соседним хостам по grouped-схеме. Запускается ребаланс после шринка до 56 сегментов и декомиссии хоста sdw-8:
$ ggrebalance -x 56 --remove-hosts="sdw-8" -v
План ребаланса
SHRINK PLAN ================================================================================ Target Segment Count: 56 -------------------------------SEGMENTS TO REMOVE------------------------------- Total segments to shrink: 8 [1] Segment Pair: Primary: Content: 56 DbId: 58 Host: sdw-8 Datadir: /data1/primary/gpseg56 Port: 10000 Mirror: Content: 56 DbId: 122 Host: sdw-1 Datadir: /data1/mirror/gpseg56 Port: 10500 [2] Segment Pair: Primary: Content: 57 DbId: 59 Host: sdw-8 Datadir: /data1/primary/gpseg57 Port: 10001 Mirror: Content: 57 DbId: 123 Host: sdw-1 Datadir: /data1/mirror/gpseg57 Port: 10501 [3] Segment Pair: Primary: Content: 58 DbId: 60 Host: sdw-8 Datadir: /data1/primary/gpseg58 Port: 10002 Mirror: Content: 58 DbId: 124 Host: sdw-1 Datadir: /data1/mirror/gpseg58 Port: 10502 [4] Segment Pair: Primary: Content: 59 DbId: 61 Host: sdw-8 Datadir: /data1/primary/gpseg59 Port: 10003 Mirror: Content: 59 DbId: 125 Host: sdw-1 Datadir: /data1/mirror/gpseg59 Port: 10503 [5] Segment Pair: Primary: Content: 60 DbId: 62 Host: sdw-8 Datadir: /data1/primary/gpseg60 Port: 10004 Mirror: Content: 60 DbId: 126 Host: sdw-1 Datadir: /data1/mirror/gpseg60 Port: 10504 [6] Segment Pair: Primary: Content: 61 DbId: 63 Host: sdw-8 Datadir: /data1/primary/gpseg61 Port: 10005 Mirror: Content: 61 DbId: 127 Host: sdw-1 Datadir: /data1/mirror/gpseg61 Port: 10505 [7] Segment Pair: Primary: Content: 62 DbId: 64 Host: sdw-8 Datadir: /data1/primary/gpseg62 Port: 10006 Mirror: Content: 62 DbId: 128 Host: sdw-1 Datadir: /data1/mirror/gpseg62 Port: 10506 [8] Segment Pair: Primary: Content: 63 DbId: 65 Host: sdw-8 Datadir: /data1/primary/gpseg63 Port: 10007 Mirror: Content: 63 DbId: 129 Host: sdw-1 Datadir: /data1/mirror/gpseg63 Port: 10507 ---------------------------------BALANCE MOVES---------------------------------- Total moves planned: 8 [1] Move Segment(content=48, dbid=114, role=m) [7.85 GB] From: sdw-8:10500:/data1/mirror/gpseg48 To: sdw-1:10508:/data1/mirror/gpseg48 [2] Move Segment(content=49, dbid=115, role=m) [7.85 GB] From: sdw-8:10501:/data1/mirror/gpseg49 To: sdw-1:10509:/data1/mirror/gpseg49 [3] Move Segment(content=50, dbid=116, role=m) [7.85 GB] From: sdw-8:10502:/data1/mirror/gpseg50 To: sdw-1:10510:/data1/mirror/gpseg50 [4] Move Segment(content=51, dbid=117, role=m) [7.85 GB] From: sdw-8:10503:/data1/mirror/gpseg51 To: sdw-1:10511:/data1/mirror/gpseg51 [5] Move Segment(content=52, dbid=118, role=m) [7.85 GB] From: sdw-8:10504:/data1/mirror/gpseg52 To: sdw-1:10512:/data1/mirror/gpseg52 [6] Move Segment(content=53, dbid=119, role=m) [7.85 GB] From: sdw-8:10505:/data1/mirror/gpseg53 To: sdw-1:10513:/data1/mirror/gpseg53 [7] Move Segment(content=54, dbid=120, role=m) [7.85 GB] From: sdw-8:10506:/data1/mirror/gpseg54 To: sdw-1:10514:/data1/mirror/gpseg54 [8] Move Segment(content=55, dbid=121, role=m) [7.85 GB] From: sdw-8:10507:/data1/mirror/gpseg55 To: sdw-1:10515:/data1/mirror/gpseg55 ================================================================================
Сценарий 1. SIGTERM во время gpmovemirrors
Оператор отправил kill на ggrebalance, пока шел pg_basebackup для одного из батчей. Сигнал перехватывается, в коде вызывается shutdown(). По умолчанию SIGTERM посылается дочернему gpmovemirrors, тот в свою очередь прерывает pg_basebackup. В каталоге для прерванных шагов в таблице segment_move_steps остается статус IN_PROGRESS, в gp_segment_configuration — обновленный адрес сегмента (gpmovemirrors обновляет каталог раньше, чем начинается бэкап), на целевом хосте — недозаписанный PGDATA.
20260521:17:24:52:181448 ggrebalance:cdw:gpadmin-[DEBUG]:-Running Command: $GPHOME/bin/gpmovemirrors --skip-resource-estimation -a -i /tmp/ggrebalance_move_config_pid181448 -B 16 -b 16 20260521:17:27:38:182714 gpmovemirrors:cdw:gpadmin-[INFO]:-Shutting down gpmovemirrors... 20260521:17:27:38:181448 ggrebalance:cdw:gpadmin-[ERROR]:-ggrebalance failed: Failed to execute 'gpmovemirrors --skip-resource-estimation -a -i /tmp/ggrebalance_move_config_pid181448 -B 16 -b 16'
select * from gp_segment_configuration where content in (48, 49, 50, 51, 52, 53, 54, 55) and role='m'; dbid | content | role | preferred_role | mode | status | port | hostname | address | datadir ------+---------+------+----------------+------+--------+-------+----------+---------+------------------------- 114 | 48 | m | m | n | d | 10500 | sdw-1 | sdw-1 | /data1/mirror/gpseg48 115 | 49 | m | m | n | d | 10501 | sdw-1 | sdw-1 | /data1/mirror/gpseg49 116 | 50 | m | m | n | d | 10502 | sdw-1 | sdw-1 | /data1/mirror/gpseg50 117 | 51 | m | m | n | d | 10503 | sdw-1 | sdw-1 | /data1/mirror/gpseg51 118 | 52 | m | m | n | d | 10504 | sdw-1 | sdw-1 | /data1/mirror/gpseg52 119 | 53 | m | m | n | d | 10505 | sdw-1 | sdw-1 | /data1/mirror/gpseg53 120 | 54 | m | m | n | d | 10506 | sdw-1 | sdw-1 | /data1/mirror/gpseg54 121 | 55 | m | m | n | d | 10507 | sdw-1 | sdw-1 | /data1/mirror/gpseg55 (8 rows)
При следующем запуске ggrebalance:
STATE_CHECK_PREVIOUS_RUNобнаруживает состояниеSTATE_REBALANCE_EXECUTION_STARTED, продолжает с него;reset_in_progress_execution_steps()переводитIN_PROGRESSвERROR:
-- До возобновления ggrebalance прерванные операции остались в IN_PROGRESS select move_order, status, is_rollback from ggrebalance.segment_move_steps; move_order | status | is_rollback ------------+-------------+------------- 0 | IN_PROGRESS | f 1 | IN_PROGRESS | f 2 | IN_PROGRESS | f 3 | IN_PROGRESS | f 4 | IN_PROGRESS | f 5 | IN_PROGRESS | f 6 | IN_PROGRESS | f 7 | IN_PROGRESS | f (8 rows)
process_error_execution_steps_mirror_moves()для каждого шага читает реальное состояние, опрашивает удаленный хост;продолжает ребаланс:
select * from gp_segment_configuration where content in (48, 49, 50, 51, 52, 53, 54, 55) and role='m'; dbid | content | role | preferred_role | mode | status | port | hostname | address | datadir ------+---------+------+----------------+------+--------+-------+----------+---------+------------------------- 116 | 50 | m | m | s | u | 10502 | sdw-1 | sdw-1 | /data1/mirror/gpseg50 120 | 54 | m | m | s | u | 10506 | sdw-1 | sdw-1 | /data1/mirror/gpseg54 121 | 55 | m | m | s | u | 10507 | sdw-1 | sdw-1 | /data1/mirror/gpseg55 114 | 48 | m | m | s | u | 10500 | sdw-1 | sdw-1 | /data1/mirror/gpseg48 118 | 52 | m | m | s | u | 10504 | sdw-1 | sdw-1 | /data1/mirror/gpseg52 119 | 53 | m | m | s | u | 10505 | sdw-1 | sdw-1 | /data1/mirror/gpseg53 115 | 49 | m | m | s | u | 10501 | sdw-1 | sdw-1 | /data1/mirror/gpseg49 117 | 51 | m | m | s | u | 10503 | sdw-1 | sdw-1 | /data1/mirror/gpseg51 (8 rows)
Сценарий 2. Тайм-аут окна обслуживания
Задана опция -T 02:00. Через два часа таймер вызывает мягкий shutdown и затем посылает себе SIGINT. В мягком режиме текущий gpmovemirrors не прерывается, ggrebalance дожидается завершения обработки текущего батча и выходит. Можно запустить ggrebalance на следующий вечер в этом же окне, и просто продолжить с сохраненного состояния.
Сценарий 3. Аварийный отказ хоста sdw-3 во время балансировки
Это самая опасная ситуация. Если в этот момент шла фаза переноса данных одного из mirror’ов на sdw-3 — content окажется с одним primary без зеркала. FTS не пометит primary как недоступный, кластер продолжит обслуживать запросы, но без избыточности до восстановления хоста или явного gprecoverseg. Если же sdw-3 — это исходный хост primary, который в момент сбоя был временно зеркалом (фаза 2 трехшаговой схемы), — соответствующий content также теряет избыточность. Поэтому ggrebalance в начале запуска вызывает check_down_segments: проверяет, нет ли primary в статусе down, и если есть — отказывается стартовать, требуя ручного восстановления. Это предотвращает запуск ребаланса на уже частично сломанном кластере.
3.6 Откат ребаланса
Откат запускается командой ggrebalance --rollback (или -r). Утилита читает сохраненный план, определяет, какие шаги уже выполнены, и формирует обратный план. Возможны три ситуации:
Первоначальное выполнение плана прервано посередине.
Часть шагов выполнена, часть — нет. Невыполненные шаги помечаются
DONEбез действий; выполненные превращаются в обратные шаги.Откат после успешного завершения.
Если ребаланс полностью завершен (но
ggrebalance --cleanне выполнен),--rollbackтоже сработает и вернет распределение в исходное. Это полезно, если после ребаланса обнаружились проблемы с производительностью или с одним из новых хостов.Сам откат был прерван.
is_rollback_flowопределяется по факту присутствияSTATE_REBALANCE_ROLLBACK_STARTEDв истории статусов, и rollback продолжается с прерванного места.
При запуске отката исходный список перемещений трансформируется по следующим правилам в on_enter_STATE_REBALANCE_ROLLBACK_PREPARE_MOVES_STARTED:
move_orderинвертируется (последний выполненный — первый в откате).RebalanceStepMoveMirrorостается того же типа, но с пометкойis_rollback=True. Это меняет местами хост-источник и целевой хост в конфигеgpmovemirrors.RebalanceStepSwitchoverToMirrorпревращается вRebalanceStepSwitchoverToPrimaryи наоборот.
После подготовки шаги сохраняются в схему, и управление передается стандартному циклу конечного автомата ребаланса. Никакого дополнительного кода выполнения не требуется — откат использует ровно ту же инфраструктуру, что и первое выполнение ребаланса. Откат сам по себе полностью реентерабелен: если он был прерван, повторный запуск ggrebalance --rollback обнаружит, что поток отката уже инициирован, и продолжит с места остановки, не начиная подготовку шагов заново.
Например, откат прерванного ребаланса из Cценария 1 предыдущего раздела выглядит так:
Роллбэк
$ ggrebalance -r 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-No time limit is set 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-local Greengage Version: 'postgres (Greengage Database) 7.4.1+dev.118.g033b9133b1 build 1+git033b913' 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-coordinator Greengage Version: 'PostgreSQL 12.22 (Greengage Database 7.4.1+dev.118.g033b9133b1 build 1+git033b913) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0, 64-bit compiled on May 14 2026 20:19:21 Bhuvnesh C.' 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-Init gparray from catalog 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-Starting rebalance rollback 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-Start preparing steps for rollback... 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-Saving following rollback rebalance execution steps: 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 0, status: PLANNED, type: mirror move: Move Segment(content=55, dbid=121, role=m) [7.96 GB] From: sdw-8:10507:/data1/mirror/gpseg55 To: sdw-1:10507:/data1/mirror/gpseg55 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 1, status: PLANNED, type: mirror move: Move Segment(content=54, dbid=120, role=m) [7.96 GB] From: sdw-8:10506:/data1/mirror/gpseg54 To: sdw-1:10506:/data1/mirror/gpseg54 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 2, status: PLANNED, type: mirror move: Move Segment(content=53, dbid=119, role=m) [7.96 GB] From: sdw-8:10505:/data1/mirror/gpseg53 To: sdw-1:10505:/data1/mirror/gpseg53 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 3, status: PLANNED, type: mirror move: Move Segment(content=52, dbid=118, role=m) [7.96 GB] From: sdw-8:10504:/data1/mirror/gpseg52 To: sdw-1:10504:/data1/mirror/gpseg52 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 4, status: PLANNED, type: mirror move: Move Segment(content=51, dbid=117, role=m) [7.96 GB] From: sdw-8:10503:/data1/mirror/gpseg51 To: sdw-1:10503:/data1/mirror/gpseg51 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 5, status: PLANNED, type: mirror move: Move Segment(content=50, dbid=116, role=m) [7.96 GB] From: sdw-8:10502:/data1/mirror/gpseg50 To: sdw-1:10502:/data1/mirror/gpseg50 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 6, status: PLANNED, type: mirror move: Move Segment(content=49, dbid=115, role=m) [7.96 GB] From: sdw-8:10501:/data1/mirror/gpseg49 To: sdw-1:10501:/data1/mirror/gpseg49 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 7, status: PLANNED, type: mirror move: Move Segment(content=48, dbid=114, role=m) [7.96 GB] From: sdw-8:10500:/data1/mirror/gpseg48 To: sdw-1:10500:/data1/mirror/gpseg48 20260521:18:24:46:192582 ggrebalance:cdw:gpadmin-[INFO]:-Saved rollback rebalance execution steps 20260521:18:24:47:192582 ggrebalance:cdw:gpadmin-[INFO]:-Rebalance - start moving segments: 20260521:18:24:47:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 0, status: IN_PROGRESS, type: mirror move: Move Segment(content=55, dbid=121, role=m) [7.96 GB] From: sdw-8:10507:/data1/mirror/gpseg55 To: sdw-1:10507:/data1/mirror/gpseg55 20260521:18:24:47:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 1, status: IN_PROGRESS, type: mirror move: Move Segment(content=54, dbid=120, role=m) [7.96 GB] From: sdw-8:10506:/data1/mirror/gpseg54 To: sdw-1:10506:/data1/mirror/gpseg54 20260521:18:24:47:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 2, status: IN_PROGRESS, type: mirror move: Move Segment(content=53, dbid=119, role=m) [7.96 GB] From: sdw-8:10505:/data1/mirror/gpseg53 To: sdw-1:10505:/data1/mirror/gpseg53 20260521:18:24:47:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 3, status: IN_PROGRESS, type: mirror move: Move Segment(content=52, dbid=118, role=m) [7.96 GB] From: sdw-8:10504:/data1/mirror/gpseg52 To: sdw-1:10504:/data1/mirror/gpseg52 20260521:18:24:47:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 4, status: IN_PROGRESS, type: mirror move: Move Segment(content=51, dbid=117, role=m) [7.96 GB] From: sdw-8:10503:/data1/mirror/gpseg51 To: sdw-1:10503:/data1/mirror/gpseg51 20260521:18:24:47:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 5, status: IN_PROGRESS, type: mirror move: Move Segment(content=50, dbid=116, role=m) [7.96 GB] From: sdw-8:10502:/data1/mirror/gpseg50 To: sdw-1:10502:/data1/mirror/gpseg50 20260521:18:24:47:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 6, status: IN_PROGRESS, type: mirror move: Move Segment(content=49, dbid=115, role=m) [7.96 GB] From: sdw-8:10501:/data1/mirror/gpseg49 To: sdw-1:10501:/data1/mirror/gpseg49 20260521:18:24:47:192582 ggrebalance:cdw:gpadmin-[INFO]:-[ROLLBACK] Rebalance step with move_order: 7, status: IN_PROGRESS, type: mirror move: Move Segment(content=48, dbid=114, role=m) [7.96 GB] From: sdw-8:10500:/data1/mirror/gpseg48 To: sdw-1:10500:/data1/mirror/gpseg48 20260521:18:28:12:192582 ggrebalance:cdw:gpadmin-[INFO]:-Rebalance - end moving segments 20260521:18:28:12:192582 ggrebalance:cdw:gpadmin-[INFO]:-Rebalance rollback is complete 20260521:18:28:12:192582 ggrebalance:cdw:gpadmin-[INFO]:-------------------------------------SUMMARY------------------------------------- 20260521:18:28:12:192582 ggrebalance:cdw:gpadmin-[INFO]:-==================================================================================== 20260521:18:28:12:192582 ggrebalance:cdw:gpadmin-[INFO]:-REBALANCE 20260521:18:28:12:192582 ggrebalance:cdw:gpadmin-[INFO]:-==================================================================================== 20260521:18:28:12:192582 ggrebalance:cdw:gpadmin-[INFO]:-Segments moved: 0 20260521:18:28:12:192582 ggrebalance:cdw:gpadmin-[INFO]:-Rolled back moves: 8 20260521:18:28:12:192582 ggrebalance:cdw:gpadmin-[INFO]:-Cancelled moves: 0
После завершения отката схема ggrebalance удаляется, кластер возвращается ровно в то распределение, которое было до первоначального запуска. Стоит отметить, что если произведен шринк, то откатить его не получится — сегменты уже выведены из кластера.
3.7 Сценарий использования и полезные возможности
Рассмотрим сценарий на нашем кластере 8x8. Изначально: 8 хостов sdw-1 - sdw-8, по 8 primary на каждом, стратегия зеркалирования grouped. Содержание сценария:
Часть данных удалена, нужно уменьшить число первичных сегментов с 64 до 48 (шринк).
Хост
sdw-5помечен на декомиссию (плановая замена железа).Вместо него вводится новый хост
sdw-9.
Все вышеперечисленное можно осуществить одной командой:
$ ggrebalance -x 48 --remove-hosts="sdw-5" --add-hosts="sdw-9" \ --target-datadirs="/data1/primary/gpseg{content}, /data1/mirror/gpseg{content}"
Но для наглядности и демонстрации дополнительных опций будем выполнять сценарий пошагово:
Шаг 1. Шринк до 48 сегментов
$ ggrebalance -x 48 -S --skip-rebalance
Опция -S (--simple-progress) включает простое отображение прогресса в представлении ggrebalance.rebalance_progress. На этом шаге ggrebalance определяет 16 лишних content ID с конца; перераспределяет данные таблиц на оставшиеся 48 сегментов через ALTER TABLE … REBALANCE; обновляет каталог gp_segment_configuration; останавливает шринкнутые сегменты. Передав опцию -S можно следить за прогрессом шринка:
select * from ggrebalance.rebalance_progress; stat_name | stat_value ---------------------------------+------------ 1.1. Tables shrunk | 17 1.2. Tables shrink in progress | 7 1.3. Tables left to shrink | 0 (3 rows)
-D (--detailed-progress) добавит к прогрессу метрики скорости шринка и оценки оставшегося времени.
Шаг 2. Декомиссия sdw-5 и ввод sdw-9
$ ggrebalance -x 48 -R sdw-5 -A sdw-9 -d /data1/primary/gpseg{content},/data1/mirror/gpseg{content}
Планировщик увидит, что один хост выбывает, другой добавляется, и построит план, минимизирующий перемещения, который уберет сегменты с sdw-5 и равномерно распределит все сегменты по хостам, соблюдая стратегию зеркалирования.
Перед выполнением можно посмотреть план без запуска:
$ ggrebalance -x 48 -R sdw-5 -A sdw-9 -d /data1/primary/gpseg{content},/data1/mirror/gpseg{content} -p
Может возникнуть ситуация, когда процесс масштабирования Greengage кластера или изменение его топологии возможно производить лишь в определенное время, в окне обслуживания. Допустим, окно обслуживания — с 22:00 до 06:00. Тогда можно запустить ребаланс с таймером и автоматическим одобрением switchover’ов:
$ ggrebalance -x 48 -R sdw-5 -A sdw-9 \ -d /data1/primary/gpseg{content},/data1/mirror/gpseg{content} \ -E '2026-04-13 06:00:00' \ -y \ -n 8 -B 16
-E— мягкое завершение в 6 утра, не прерывая текущуюgpmovemirrors.-y— не задавать вопросов перед switchover.-n 8— до 8 параллельных операций (перераспределение таблиц, перемещение сегментов).-B 16— до 16 параллельных воркеров на хост.Если не успели — следующей ночью просто запускаем
ggrebalanceбез аргументов, и операция продолжится.
Дополнительные опции:
--show-plan (-p)
Построить план и вывести его, ничего не выполняя. Полезно для аудита перед изменением.
--seed N
Фиксированный seed для солвера. План будет воспроизводимым, что важно для тестирования и согласования с DBA-командой.
--inplace-swap-roles
Разрешает временно размещать primary и mirror одного content на одном хосте. Делает план более гибким в стесненных конфигурациях, но снижает отказоустойчивость во время выполнения.
--skip-rebalance
Выполнить только шринк или расширение каталога без перемещения сегментов между хостами. Полезно, когда хочется разнести этапы во времени.
--mirror-mode grouped|spread
Выбор стратегии размещения зеркал после ребаланса.
--replay-lag N
Допустимое отставание зеркала в ГБ при запуске gprecoverseg для переноса сегментов. Меньшее значение — выше согласованность, выше шанс тайм-аута.
--clean (-c)
Удалить служебную схему после успешного завершения. Без нее повторный запуск провалится, считая, что есть незавершенная операция.
3.8 Доступность и рекомендации по эксплуатации
Прежде чем говорить о рекомендациях, важно четко понимать, какие именно простои возможны при использовании ggrebalance и из чего они складываются. Утилита проектировалась как "онлайн" — но это не означает нулевой downtime. Ранее при описании метода переноса данных уже говорилось, что перенос зеркал никак не влияет на доступность, так как зеркала не обслуживают пользовательские запросы. Пока gpmovemirrors копирует данные и нагоняет WAL, primary продолжает работать в штатном режиме. Единственное последствие — content в это время не имеет резервной копии, что повышает риск, но не вызывает downtime.
Switchover ролей, в свою очередь, вызывает определенный downtime. Каждая смена ролей primary/mirror через gprecoverseg -r вносит downtime, складывающийся из:
остановки старого primary в режиме
fast;ожидания FTS-пробы, которая зафиксирует недоступность — до
gp_fts_probe_interval;промоута зеркала в primary — зависит от объема данных;
обновления каталога
gp_segment_configuration.
Тестовый прогон ребаланса показал, что перенос primary полностью, состоящий из двух switchover’ов и двух перемещений зеркала, суммарно включал около 20 минут недоступности на каждый content, разнесенных по времени всей операции переноса. Тут можно отметить, что опция --parallel увеличивает количество одновременных switchover’ов.
Перед запуском желательно:
Провести healthcheck кластера: все ли сегменты активны, синхронизированы с зеркалами,
roleсоответствуютpreferred_role.Учесть, что перенос одного зеркала требует столько свободного места на целевом хосте, сколько занимают данные исходного primary. Для нашего кластера 8x8 с размером сегмента 200 ГБ это означает 1.6 ТБ свободного места на каждом принимающем хосте.
Помнить, что
pg_basebackupупирается в сеть между source- и target-хостами: время передачи данных, грубо говоря, прямо пропорционально размеру сегмента и обратно пропорционально пропускной способности интерфейса. Соответственно, для ускорения операций необходимо обеспечить достаточную сетевую пропускную способность.
Во время запуска:
Мониторить прогресс по логам в gpAdminLogs/ggrebalance[_timestamp].log.
Отслеживать репликацию между сегментами.
Следить за ресурсами хостов:
iostat,nload,df -hна участвующих хостах. Если диск или сеть уперлись в потолок — увеличение--parallelне даст прироста скорости, а скорее наоборот замедлит из-за увеличения конкуренции.
После завершения:
gpstate -e. Убедиться, что все сегменты в состоянииUp,role == preferred_role.gpcheckcat. Проверить консистентность каталога.ANALYZE. Либоggrebalance --analyzeпри запуске.ggrebalance --clean. Удалить служебную схемуggrebalance.
Что делать при сбое:
Не запускайте
gprecoversegили другие утилиты вручную. Это может сломать состояние, на которое опираетсяggrebalance.Прочитайте лог в gpAdminLogs/ggrebalance[_timestamp].log. Последние сообщения обычно указывают на причину.
Запустите
ggrebalanceповторно. Утилита проанализирует состояние и предложит действия.Если предложенный путь невозможен, используйте
--rollbackдля отката или--cleanдля сброса (только если кластер фактически в здоровом состоянии).При критическом сбое —
gpstop -ar, проверкаgp_segment_configurationзапросом, ручное приведение в исходное состояние и обращение в поддержку.
Заключение
Серия статей о ggrebalance охватила ключевые аспекты утилиты: архитектуру и сценарии использования, реализацию планировщика и исполнительную часть. Вместе они составляют полный технический обзор инструмента, призванного решить одну из практических задач эксплуатации больших Greengage-кластеров — поддержание равномерного распределения нагрузки в динамически меняющейся топологии.
ggrebalance закрывает важный пробел в эксплуатации Greengage: дает инструмент изменения топологии кластера без остановки кластера. Наш подход основан на реализации собственного планировщика, шринка с оптимизированным механизмом перераспределения таблиц, исполнителя переноса сегментов с поддержкой восстановления после прерывания и отката операции в сочетании со штатными утилитами gpmovemirrors/gprecoverseg. Это позволяет получить три важных свойства: онлайн-выполнение, реентерабельность и контролируемый откат.
Насколько это весомо на практике, наглядно показывает сравнение с альтернативами: gpshrink (инструмент только для шринка) и ручным подходом через gpmovemirrors + gprecoverseg отдельно.
Таблица 2. Сравнение возможностей ggrebalance, gpshrink и ручного подхода | |||
Возможность | ggrebalance | gpshrink от CloudberryDB | Вручную |
|---|---|---|---|
Масштабирование и изменение топологии | |||
Шринк | + | + | - |
Балансировка сегментов по хостам | + | - | - |
Добавление хостов | + | - | + |
Декомиссия хостов | + | - | + |
Перемещение и сокращение числа сегментов | |||
Способ перераспределения таблиц | INSERT (performance optimized) | CTAS | - |
Перемещение зеркал | + | - | + |
Перемещение primary без остановки кластера | + | - | +/- |
Параллельное выполнение перемещений | + | + | + |
Планирование | |||
Автоматическое построение плана перемещений | + | - | - |
Предпросмотр плана без выполнения | + | - | - |
Учет стратегии зеркалирования (grouped / spread) | + | - | - |
Оценка свободного места перед запуском | + | - | + |
Надежность и восстановление | |||
Реентерабельность шринка | + | - | - |
Откат шринка (частичный, до перераспределения) | +/- | +/- | - |
Реентерабельность ребаланса | + | - | - |
Откат ребаланса | + | - | - |
Поддержка окна обслуживания (таймер) | + | + | + |
Хранение состояния операции в СУБД | + | +/- | - |
Healthcheck кластера перед запуском | + | + | - |
Наблюдаемость | |||
Runtime-мониторинг через SQL-представления | + | + | - |
Поддержка статусов операций | + | + | - |
Логирование | + | + | + |
Управление детализацией логов | + | + | +/- |
Ручной подход закрывает лишь базовые операции с сегментами — и только при условии, что оператор сам берет на себя планирование, отслеживание состояния и восстановление после сбоев. gpshrink автоматизирует перераспределение данных при шринке, но не затрагивает балансировку физического расположения сегментов по хостам. ggrebalance объединяет обе задачи — шринк и ребаланс — в единый управляемый процесс с полным набором свойств, необходимых для длительных операций в продакшен-среде.
Описанная в трех статьях серии реализация покрывает основные сценарии — шринк, балансировку, добавление и декомиссию хостов — но это только первый этап эволюции инструмента. На очереди несколько направлений для дальнейшего исследования и развития.
Поддержка операции expand
Сейчас ggrebalance умеет добавлять хосты при условии, что число сегментов не увеличивается. Полноценный expand — увеличение числа сегментов с перераспределением данных — это следующий крупный блок работ. Он не был включен в первый релиз ввиду наличия устоявшегося инструмента gpexpand.
Сокращение downtime при switchover
В текущей реализации каждая смена ролей primary/mirror сопровождается коротким простоем сегмента.
Ускорение переноса данных сегментов
pg_basebackup — узкое место для больших объемов. Также во время вызова gpmovemirrors кластер на время теряет свойство избыточности, и мы на данный момент исследуем возможности уменьшения этого окна.
Адаптивное планирование
Текущий планировщик строит план статически, без учета оценок размеров сегментов и возможного downtime. Адаптивный планировщик мог бы минимизировать downtime за счет уменьшения числа switchover и увеличения числа перемещений в целом. Также является востребованным сценарий смены стратегии зеркалирования: с grouped на spread и наоборот.
Интеграция с системами мониторинга
Сейчас прогресс доступен через SQL-представление в схеме ggrebalance. Экспорт метрик в дашборды и т.п. упростили бы наблюдение за длительными операциями.
Вместе эти направления развития должны сделать изменение топологии Greengage-кластера такой же рутинной операцией, как VACUUM — без долгого планирования, окна обслуживания и ручного контроля каждого шага.

