Как мы уменьшали количество сегментов в ArenData DB / Greenplum

Эта статья —рассказ о том, как мы в DB Serv проводили нетривиальную операцию уменьшения количества сегментов в кластере ArenData DB 6.4 (форк Greenplum). Задача казалась простой на бумаге, (кажется есть стандартный набор шагов gpinitclusterсс меньшенным количеством сегментов, потом заливка gprestore –resize-cluster ) но на практике потребовала нескольких дней исследований, strace-трассировок и нескольких неожиданных открытий.

Статья разбита на две части: первая — как мы разобрались с механизмом --resize-cluster и пришли к решению через hardlinks; вторая — борьба с колонками типа unknown при миграции на новую версию.

Часть 1. Уменьшение сегментов: от хаоса к hardlinks

Исходная ситуация

Кластер ArenData DB 6.4 на 6 хостах, каждый с 64 vCPU. Исторически при инициализации было выбрано 32 сегмента на хост — итого 192 primary-сегмента. Со временем стало ясно что это избыточно: слишком высокие накладные расходы на межсегментное взаимодействие, лишнее потребление памяти. Рекомендация по аудиту — снизить до 16 сегментов на хост (4 CPU на сегмент), итого 96.

Greenplum не имеет встроенного инструмента для уменьшения сегментов — в отличие от расширения (gpexpand), обратной операции не существует. Единственный путь: gpbackup → новый кластер → gprestore --resize-cluster.

Первые попытки и грабли

Грабля 1: .bash_profile

Первая неожиданность случилась ещё до начала миграции. При переключении между старым и новым кластером мы обнаружили что gpstart упорно поднимает старый кластер — несмотря на явный export MASTER_DATA_DIRECTORY в текущей сессии.

Причина оказалась проста: в ~/.bash_profile жёстко прописан путь:
export COORDINATOR_DATA_DIRECTORY=/data1/master/gpseg-1
export MASTER_DATA_DIRECTORY=$COORDINATOR_DATA_DIRECTORY
gpstart запускает дочерние процессы которые читают .bash_profile заново — поэтому экспорт в текущей сессии не помогает. Решение: менять .bash_profile и делать exit + повторный логин. Это правило мы выучили болезненно — потеряли несколько часов на диагностику.
Грабля 2: gpinitsystem_config для ArenData

При инициализации нового кластера выяснилось что ArenData DB требует обязательного параметра SEG_PREFIX=gpseg — без него gpinitsystem падает с FATAL. В upstream Greenplum этот параметр опциональный.
⚠️ DATA_DIRECTORY в конфиге — это родительская папка. gpinitsystem сам создаёт подпапки gpseg0..N внутри неё. Нумерация сегментов локальная на каждом хосте — все хосты имеют gpseg0..15.
Грабля 3: --resize-cluster и файлы бэкапа

Когда мы наконец подняли новый кластер с 6 сегментами и запустили gprestore --resize-cluster, получили загадочную ошибку:
Expected to find 2 file(s) on segment 0 on host Segment1, but found 1 instead.
Почему 2? Откуда это число? Мы долго пытались понять, что же это значит,
Пытались делать симлинки на директории. Создали симлинки на директории gpseg0..5 на каждом хосте. Директории нашлись — но количество файлов всё равно было неправильным. Ошибка не ушла. Поняли что проблема не в директориях а в самих файлах. Создали симлинки на файлы вместо копирования. ls показывал нужное количество — но gprestore всё равно находил меньше чем ожидал. Долго не могли понять почему.

Потом казалось бы : gprestore использует find -type f — а симлинки это не -type f, это -type l. Симлинки на файлы не считаются, Только реальные файлы. скопировали файлы всех старых сегментов в папку каждого нового сегмента?. Стало 24 файла на сегмент. Запустили — gprestore снова сказал "found 1 instead of 2". Мы в недоумении: файлы же есть!

Решили, что проблема в количестве файлов на таблицу. Сделали новый бэкап с флагом --single-data-file — один файл на сегмент вместо одного файла на таблицу. Казалось логичным. Запустили gprestore — та же ошибка, просто теперь ожидалось 2 файла вместо N×2.

Писало это загадочное сообщение. Пошли в исходники — точнее, в strace.

Ключевое открытие: strace раскрывает механизм

Самым эффективным инструментом диагностики оказался strace с правильными параметрами. Критически важный флаг: -s 500. Без него строки обрезаются до 32 символов и пути не видны.
strace -f -e trace=execve -s 500 gprestore \
  --timestamp 20260520132515 \
  --create-db \
  --resize-cluster \
  --verbose \
  2>&1 | grep "Segment1.*find" | head -10
Вывод показал точную find-команду которую gprestore запускает на каждом хосте:
find /data1/primary/new6/gpseg0/backups/20260520/20260520132515
  -type f -regextype posix-extended
  -regex ".*gpbackup_(0|6)_20260520132515.*" | wc -l
Вот оно! gprestore ищет файлы с content_id 0 ИЛИ 6 — то есть он знает какие именно старые сегменты должны слиться в новый. И здесь же ключевое ограничение: считает только -type f (реальные файлы), симлинки на файлы НЕ считаются.

Из regex вывели формулу round-robin:
new_seg = old_seg % NEW_COUNT
При 12 → 6: new_seg0 ← old 0,6; new_seg1 ← old 1,7; new_seg2 ← old 2,8 и т.д.
При 192 → 96: new_seg0 ← old 0,96; new_seg1 ← old 1,97 и т.д.

Решение: hardlinks на shared storage

Как только механизм стал понятен, решение оказалось элегантным. У нас /backup — shared storage (NFS), виден со всех хостов кластера. Это означает что мы можем создать hardlinks мгновенно, без копирования данных( и hardlinks выглядят как файлы, то есть копировать ничего никуда не надо) :
#!/bin/bash
TIMESTAMP=20260611194101
DATE=20260611
BACKUP_DIR=/data1/backup_prod/dwhprod
OLD_COUNT=192
NEW_COUNT=96
SUBDIR="backups/${DATE}/${TIMESTAMP}"

for old_seg in $(seq ${NEW_COUNT} $((OLD_COUNT-1))); do
  new_seg=$((old_seg - NEW_COUNT))
  src="${BACKUP_DIR}/gpseg${old_seg}/${SUBDIR}"
  dst="${BACKUP_DIR}/gpseg${new_seg}/${SUBDIR}"
  mkdir -p "${dst}"
  find "${src}" -type f | while read f; do
    ln "$f" "${dst}/$(basename $f)" 2>/dev/null
  done
done
Hardlinks работают только в пределах одной файловой системы. Для shared storage (NFS) — мгновенно даже для терабайтных файлов. Данные не копируются, создаются только дополнительные ссылки на те же блоки.
Если /backup не shared (локальные диски на каждом хосте) — нужно физически копировать файлы через scp/gpssh. Это медленнее, но работает по той же логике.

Часть 2. Миграция на Greengage 7.4: правильный алгоритм заливки данных

Контекст: смена версии

Параллельно с уменьшением сегментов мы мигрировали с ArenData DB 6.4 на Greengage Database 7.4.1. Казалось бы — есть флаг --create-db, он должен сделать всё сам: создать базу, восстановить схему, залить данные. Но именно здесь нас ждал неожиданный сюрприз.

Проблема: --create-db и бардак с метаданными

Первый запуск gprestore с флагом --create-db завершился ошибкой:
Первый запуск gprestore с флагом --create-db завершился ошибкой:
ERROR: role "liquser" does not exist
gprestore пытался создать базу с владельцем которого ещё не существует в новом кластере. Создали роль вручную, запустили снова — новая ошибка:
ERROR: required extension "gp_toolkit" is not installed
Добавили extension, запустили снова — база создалась, но метаданные легли не полностью из-за ошибок с arenadata_toolkit. В итоге таблицы создались, но часть объектов отсутствовала.

Потом выяснилось кое-что похуже: мы запускали psql -d template1 -f metadata.sql — и все таблицы создались в template1, а не в целевой базе! gprestore при заливке данных искал таблицы в нужной базе — и не находил их. Результат: база есть, схема есть, данных нет.

Несколько итераций дропа и пересоздания базы — и мы пришли к выводу что --create-db для наших условий не подходит.

Новый алгоритм: три шага

После всех экспериментов выработали надёжный порядок действий который работает стабильно:

Шаг 1. Создать базу вручную с нужным owner:

psql -d template1 -c "CREATE DATABASE mydb OWNER myowner;"

psql -d mydb -c "CREATE EXTENSION IF NOT EXISTS gp_toolkit;"

psql -d mydb -c "CREATE EXTENSION IF NOT EXISTS arenadata_toolkit;"

Шаг 2. Залить метаданные строго в контекст целевой базы — не template1!

⚠️САМОЕ ВАЖНОЕ: psql -d mydb (не template1!). При запуске в template1 все таблицы создаются там и данные потом не заливаются.


psql -d mydb -f gpbackup_<TS>_metadata.sql > ~/metadata_mydb.log 2>&1


Фильтруем реальные проблемы в логе (некритичные ошибки версионных различий игнорируем):


grep -iE "ERROR" ~/metadata_mydb.log \

 | grep -v "already exists" \

 | grep -v "arenadata_toolkit" \

 | grep -v "CPU_RATE_LIMIT" \

 | grep -v "MEMORY_" | head -20


Проверяем что таблицы создались:


psql -d mydb -c "

SELECT schemaname, count(*) FROM pg_tables

WHERE schemaname NOT IN ('pg_catalog','information_schema',

'gp_toolkit','arenadata_toolkit')

GROUP BY schemaname ORDER BY schemaname;"

Шаг 3. Запустить gprestore только для данных:

gprestore --backup-dir /backup/mydb \

--timestamp <TS> \

--resize-cluster \

--jobs 16 \

--data-only \

--on-error-continue \

--verbose \

2>&1 | tee ~/gprestore_mydb_$(date +%Y%m%d_%H%M%S).log


Флаг --data-only ключевой: схема уже есть, заливаем только данные.

Почему это работает и лучше чем --create-db

--create-db делает всё в одном прогоне — и если что-то пошло не так на этапе создания схемы, данные либо не заливаются вовсе, либо заливаются в неполную структуру. Три отдельных шага дают контроль на каждом этапе: видишь ошибки метаданных до заливки данных, можешь исправить структуру до того как пойдут гигабайты.

Для баз размером в терабайты это критично — перезапускать многочасовой рестор из-за проблемы которую можно было исправить за минуту очень болезненно.

Итоги и выводы

Что мы узнали

  • --resize-cluster работает по round-robin: new_seg = old_seg % NEW_COUNT
  • gprestore считает только реальные файлы (-type f), симлинки не работают
  • Shared storage + hardlinks = самое элегантное решение (мгновенно, без копирования)
  • metadata.sql ВСЕГДА запускать в контексте целевой базы, не template1
  • strace -f -e trace=execve -s 500 — незаменимый инструмент диагностики gprestore
  • .bash_profile критичен при переключении кластеров: export недостаточно, нужен exit + логин
  • --create-db удобен только для простых случаев; для сложных сред — три отдельных шага

Рекомендации

  • Убедитесь что директория бэкапов — shared storage для всех узлов
  • Проверьте .bash_profile перед началом работ
  • Сделайте тестовую миграцию на небольшой базе перед продом!
  • Используйте --on-error-continue при ресторе и анализируйте логи
  • Для больших баз используйте --jobs 16 и более
  • Заранее проверьте роли и extensions в новом кластере до запуска рестора

Надёжность в каждом процессе

Вашему кластеру Greenplum нужна профессиональная поддержка? Или Monitoring? Эксперты DB Serv обеспечивают стабильность даже в самых сложных сценариях. Узнайте больше о наших услугах администрирования Greenplum
Оставить заявку Читать другие кейсы