В статье рассматривается исполнительная часть утилиты 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).

Рис. 1. Шринк трех сегментов
Рис. 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  \rightarrow 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  \rightarrow primary

Еще один вызов gprecoverseg -r с предварительным ручным обновлением каталога возвращает предпочитаемые роли: сегмент на целевом хосте становится первичным, а бывший первичный (исходного хоста) — зеркалом. По завершении этого шага первичный сегмент "переехал"; зеркало относительно него расположено там, где и было изначально.

Рис. 2. Перемещение primary
Рис. 2. Перемещение primary

Из-за достаточно продолжительного времени работы 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.

Рис. 3. Машина состояний операции ребаланса
Рис. 3. Машина состояний операции ребаланса

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; она позволяет автоматически одобрять смены ролей.

Рис. 4. Реализация операции ребаланса
Рис. 4. Реализация операции ребаланса

Также предусмотрена возможность отката (rollback) перемещений. Выполнение отката происходит аналогично прямому ходу ребаланса, присутствуют лишь определенные нюансы реализации и трекинга состояний. Поток отката намеренно сходится с STATE_REBALANCE_EXECUTION_STARTED — это полностью исключает дублирование логики выполнения. Подробнее про сценарий отката будет рассказано в следующих разделах.

3.4 Отслеживание статусов операций

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

Рис. 5. Служебная схема ggrebalance
Рис. 5. Служебная схема ggrebalance

Говоря о статусах процесса балансировки, в таблице 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 читает последнее сохраненное состояние из схемы и определяет следующее состояние, переход в которое возобновит прерванный процесс. Логика маппинга такова:

  1. Если сохраненного состояния нет (STATE_NOT_DEFINED) — это свежий запуск, переходим в STATE_REBALANCE_STARTED.

  2. Если зафиксировано финальное состояние — кластер уже сбалансирован, ничего делать не нужно.

  3. STATE_REBALANCE_EXECUTION_STARTEDSTATE_REBALANCE_MOVES_SUCCEEDED и STATE_REBALANCE_EXECUTION_AWAITING_SWITCHOVER_APPROVE_DONE — все три отображаются обратно в STATE_REBALANCE_EXECUTION_STARTED, потому что это состояние умеет "вылечить" себя при входе: сначала оно анализирует и исправляет шаги в статусе ERROR, и только потом продолжает выполнение.

  4. Состояния отката переходят к следующему состоянию этого же потока.

  5. Все прочие состояния переходят к следующему состоянию основного потока (Рис. 3).

Для шагов типа RebalanceStepMoveMirror ggrebalance при повторном запуске выполняет детальный анализ каждого сбойного шага, основанный на фактическом состоянии кластера:

  1. Проверка каталога.

    Запрашивается gp_segment_configuration для данного dbid. Если hostname сегмента уже совпадает с целевым — каталог был обновлен до сбоя.

  2. Проверка конфигурационного файла.

    Если каталог обновлен, через GpConfigHelper читается postgresql.conf на целевом хосте для проверки параметра port. Это позволяет определить, насколько далеко продвинулось выполнение gpmovemirrors до сбоя.

  3. Ожидание старта сегмента.

    Если порт тоже обновлен, запускается цикл опроса состояния сегмента. Если сегмент запустился и вошел в синхронизированный режим, шаг помечается как DONE — никаких дополнительных действий не требуется.

  4. Интерактивное продление ожидания.

    Если тайм-аут ожидания истек — в интерактивном режиме оператору предлагается продлить ожидание.

  5. 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). Утилита читает сохраненный план, определяет, какие шаги уже выполнены, и формирует обратный план. Возможны три ситуации:

  1. Первоначальное выполнение плана прервано посередине.

    Часть шагов выполнена, часть — нет. Невыполненные шаги помечаются DONE без действий; выполненные превращаются в обратные шаги.

  2. Откат после успешного завершения.

    Если ребаланс полностью завершен (но ggrebalance --clean не выполнен), --rollback тоже сработает и вернет распределение в исходное. Это полезно, если после ребаланса обнаружились проблемы с производительностью или с одним из новых хостов.

  3. Сам откат был прерван.

    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.

  • Отслеживать репликацию между сегментами.

  • Следить за ресурсами хостов: iostatnloaddf -h на участвующих хостах. Если диск или сеть уперлись в потолок — увеличение --parallel не даст прироста скорости, а скорее наоборот замедлит из-за увеличения конкуренции.

После завершения:

  • gpstate -e. Убедиться, что все сегменты в состоянии Uprole == 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 — без долгого планирования, окна обслуживания и ручного контроля каждого шага.