Подходы и инструменты масштабирования Greengage

Андрей Савицкий7 февраля 2025
GREENGAGE SHRINK
В данной статье мы рассматриваем подходы к масштабированию кластера в Greengage и позиционируем основные требования к разрабатываемым утилитам

Введение

В этой статье мы хотим вынести на обсуждение сообщества вопрос: что означает полноценно реализовать в Greenplum возможность уменьшения числа сегментов и/или хостов?

Наш базовый тезис таков: возможность масштабирования кластера как в меньшую, так и большую сторону реализована полноценно в решении только тогда, когда кластер приводится в состояние сбалансированного (или остаётся таковым) как после выполнения операции shrink, так и expand.

Отличие этой статьи от предыдущих (например, отличная статья Романа Ескина по выделению в Greengage планировщика ORCA в расширение) в том, что работа на момент её написания в самом разгаре. На текущем этапе обсуждаются общие границы решения, основные тезисы и требования, которые неизбежно могут претерпеть некоторые изменения по мере продвижения.

Текущее положение дел

В чем же сложность сократить количество сегментов или хостов: вроде как, ломать — не строить?

Как известно, сегменты в Greenplum нумеруются по content (номер сегмента, одинаковый для пары primary-mirror). В данном случае на Рис. 1 значение content представляет собой постфикс после идентификатора seg, цветом обозначены primary- и mirror-сегменты, тип зеркалирования — group.

Пример кластера из 3 хостов и 3 сегментов c group-зеркалированием
Рис. 1. Пример кластера из 3 хостов и 3 сегментов c group-зеркалированием

Когда кластер расширяется gpexpand, то в кластер добавляются новые сегменты (content и dbid инкрементируются и присваиваются новым сегментам). При этом gpexpand в интерактивном режиме не даст нарушить доступные схемы зеркалирования, в том числе накладывая требования по количеству новых хостов. Для сохранения стратегии зеркалирования gpexpand запрашивает добавление двух и более хостов (на Рис. 2 они выделены пунктиром).

Добавление сегментов/хостов gpexpand
Рис. 2. Добавление сегментов/хостов gpexpand

В режиме передачи в gpexpand файла конфигурации расширения кластера (опция --i) допускается гораздо большая свобода действий, но все они остаются на совести пользователя.

Если бы утилита была более продвинутой с точки зрения сохранения конфигурации зеркалирования, то можно было бы добиться сбалансированного расширения, довольствуясь одним хостом. Это бы потребовало ребаланса, например, группы [seg6, seg7, seg8] c sdw1 на новый хост sdw4, в то время как на хост sdw1 приехала бы как раз новая введенная в кластер группа [seg9, seg10, seg11].

Ребаланс сегментов
Рис. 3. Ребаланс сегментов

Это является ограничивающим фактором использования интерактивного режима gpexpand. Однако, сформировав файл конфигурации вручную, равномерность распределения можно скорректировать (или сломать), например, такой утилитой, как gpmovemirrors.

Перераспределение строк между сегментами

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

Вычисленный хеш для строки (если точнее, то подсчитанный для ключей распределения данной таблицы) хранится в поле hash структуры CdbHash, там же хранится и число сегментов numsegs:

typedef struct CdbHash
{
	uint32        hash;         /* The result hash value                               */
	int           numsegs;      /* number of segments in Greenplum Database used for partitioning */
	CdbHashReduce reducealg;    /* the algorithm used for reducing to buckets          */
	bool          is_legacy_hash;

	int           natts;
	FmgrInfo     *hashfuncs;
} CdbHash;

Чтобы получить итоговое значение хеша, которое определит сегмент-источник/приёмник для данной строки, происходит финальное схлопывание (reduce) хеша до нужного значения функцией cdbhashreduce. Эта функция отвечает за вычисление номера целевого сегмента. Начиная с версии 6 вычисление номера базируется на алгоритме jump consistent hash.

Данный алгоритм требует использования непрерывной последовательности идентификаторов "бакетов" (в терминах общего описания алгоритма), в нашем случае сегментов (0, num_segments). Таким образом, основным ограничением использования данного алгоритма является добавление или удаление начиная со "старших" сегментов. Выкинуть из кластера сегмент где-то "посередине" в такой картине мира нельзя.

Всё бы хорошо и гладко, но размещены эти старшие сегменты могут быть на любых хостах. Чем это может в итоге завершиться? Несбалансированным кластером (Рис. 4).

Пример несбалансированного кластера
Рис. 4. Пример несбалансированного кластера

По итогу на sdw1 остаются 3 primary-сегмента, на sdw3 уже 3 mirror-сегмента, на sdw2 пара групп по 3 сегмента. Выглядит не очень сбалансировано. Хост sdw3 остаётся только на применение WAL-записей от sdw2.

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

Что предлагаем мы в рамках Greengage

Обобщить задачу увеличения или сокращения числа сегментов таким образом, что главным процессом будет ребаланс сегментов на некоторое множество хостов и сегментов. Тем самым перед нами открываются возможности:

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

  • Сокращение и числа сегментов, и числа хостов. Например, с целью высвобождения хостов и передачи их в другой кластер.

  • Осуществление ребаланса ровно в той же конфигурации с точки зрения количества хостов и сегментов. По сути, приведение в сбалансированное состояние по какой-то причине ранее несбалансированной конфигурации с учетом стратегии зеркалирования.

  • Облегчение жизни пользователя за счет предоставления в его распоряжение одной утилиты, которая будет решать комлексную задачу масштабирования кластера, в частности, заменяя собой gpexpand.

Для тех пользователей, которые захотят по каким-то причинам продолжить использовать имеющиеся утилиты, известный нам gpexpand и его предполагаемый "антипод" — gpshrink — можно уже рассматривать как обёртки над обобщенным решением в виде gprebalance. Технически они будут использовать gprebalance.

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

Оптимизация INSERT SELECT при переносе строк

Возможный вариант оптимизации шага перераспределения таблиц с "jump consistent-распределением" при сокращении числа сегментов — это отказ от подхода CREATE TABLE AS SELECT и реализации переноса данных с выводимых сегментов через INSERT SELECT. Действительно, так как из эксплуатации выводятся старшие сегменты (а это означает, что строки на существующих сегментах останутся, как есть), то можно получить, предположительно, весомый выигрыш времени на перераспределении только подмножества данных старших сегментов, а не полного пересоздания таблицы. Я думаю, что по мере продвижения работы по этой подзадаче мы поделимся с вами результатами сравнительных тестов производительности.

Минимизация времени простоя реплики

Второй момент, на который мы обратили внимание, что при перемещении сегментов реплика отключается почти сразу и может быть долгое время недоступна. Это не является большой проблемой при восстановлении сегментов или каких-то единичных перемещений, но для задачи полноценной и потенциально масштабной ребалансировки кластера это может оказаться критичным. Мы также работаем и в этом направлении.

Как итог, хочу привести список тезисов, которые можно считать основными требованиями к утилите, которую мы пока называем gprebalance:

  • Утилита должна обеспечивать возможность приведения кластера в сбалансированное состояние.

  • Должны поддерживаться конфигурации зеркалирования group или spread.

  • В идеале утилита должна поддерживать балансировку точек монтирования, которые могут быть назначены пользователям для сегментов в качестве каталогов размещения данных. В противном случае пользователь может удивиться, что на одном узле сгруппировались все сегменты с определенной точкой монтирования.

  • Утилита должна поддерживать ребаланс на заданный список хостов.

  • Утилита должна поддерживать режим моделирования всех действий "в памяти", без реального перемещения (dry run).

  • Утилита должна иметь возможность представить пользователю план перемещений сегментов в виде списка действий по перемещению сегментов между узлами.

  • Утилита должна поддерживать режим отката проделанных изменений кластера.

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

  • При возникновении необходимости переключить роль primary → mirror или обратно утилита (сама или с помощью других утилит) может это сделать в автоматическом режиме, либо утилита должна прерывать своё выполнение и запрашивать явное подтверждение пользователя допустимости выполнения шага переключения ролей.

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

  • Утилита должна поддерживать режим отображения информации о прогрессе выполнения ребаланса.

  • Утилита должна обрабатывать возможные ситуации failover-сегментов, выхода из строя узлов.