F.45. pg_shardman

pg_shardman — встроенное экспериментальное расширение Postgres Pro Enterprise, реализующее горизонтальное сегментирование (шардинг) — разделение больших таблиц на отдельные сегменты, распределяемые между разными серверами. Это расширение обеспечивает масштабируемость и отказоустойчивость с поддержкой ACID и рассчитано прежде всего на нагрузку OLTP. pg_shardman предоставляет следующие возможности:

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

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

  • Настраиваемый уровень избыточности — число реплик, создаваемых для каждого сегмента. Для поддержания актуальности реплик pg_shardman использует логическую репликацию. При этом поддерживается как синхронная, так и асинхронная репликация.

  • Атомарная модификация данных, хранящихся на нескольких узлах, в одной транзакции с использованием двухфазной фиксации.

  • Поддержка уровня изоляции транзакций Repeatable Read для кластера в целом.

  • Замена узлов с переключением реплики вручную.

F.45.1. Ограничения

В настоящее время расширение pg_shardman имеет следующие ограничения:

  • Контроллером сегментирования не может быть рабочий узел. Более подробно типы узлов описаны в Подразделе F.45.2.

  • Число сегментов после сегментирования таблицы изменить нельзя.

  • pg_shardman реализует только ограниченную поддержку DDL, как описано в Подразделе F.45.4.2. Поддержка DCL практически отсутствует, поддерживается только один пользователь.

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

  • Все ограничения логической репликации. Например, операторы TRUNCATE в сегментированных таблицах не реплицируются.

  • pg_shardman в настоящее время не создаёт идентификаторы реплики. Во избежание проблем с операциями UPDATE и DELETE в качестве ключа сегментирования настоятельно рекомендуется использовать первичный ключ. Кроме того, первичный ключ необходим для синхронизации нескольких реплик после отказа кластера.

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

  • pg_shardman не был протестирован в Windows.

F.45.2. Архитектура

Сегментированный кластер баз данных образуют несколько узлов с установленными экземплярами Postgres Pro Enterprise:

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

    В настроенном кластере контроллер сегментирования сохраняет все метаданные, описывающие конфигурацию кластера, в своих локальных таблицах. В этих таблицах хранится информация обо всех узлах кластера, а также о первичных сегментах и репликах. Более подробно об этих метаданных рассказывается в Подразделе F.45.5.3.

  • Рабочие узлы — эти узлы хранят собственно сегменты таблиц и их реплики. Каждый рабочий узел может принимать запросы на чтение и запись, передавая их соседним узлам, если требуется. Общие рекомендации по выбору числа узлов и сегментов приведены в Подразделе F.45.3.3.1.

F.45.2.1. Транзакции

Для транзакций, затрагивающих только один рабочий узел, атомарность, устойчивость и требуемые уровни изоляции обеспечиваются сервером Postgres Pro обычным образом. Однако для выполнения распределённых транзакций pg_shardman задействует протокол двухфазной фиксации (2PC) для обеспечения атомарности и алгоритм Clock-SI для изоляции транзакций.

Для включения поддержки 2PC используется переменная postgres_fdw.use_twophase. Когда включён режим 2PC, каждая транзакция перед фиксацией подготавливается на всех узлах. Успешное выполнение PREPARE на узле означает, что он готов зафиксировать транзакцию, но она будет зафиксирована, только если это могут сделать все остальные узлы. В противном случае транзакция будет прервана. Этот подход позволяет обеспечить атомарность выполнения транзакции на нескольких узлах.

Этот параметр можно изменить в любое время. Так как поведение транзакции определяется значением postgres_fdw.use_twophase в момент фиксации, одни распределённые транзакции могут использовать 2PC, а другие не использовать. Вне зависимости от этого параметра, 2PC никогда не используется для пишущих транзакций, затрагивающих только один узел.

Хорошо известным недостатком 2PC является то, что это блокирующий протокол: в случае сбоя координатора транзакций некоторые транзакции могут зависнуть в состоянии PREPARE. Для решения этой проблемы pg_shardman предоставляет функцию shardman.recover_xacts() .

Для обеспечения изоляции транзакций на уровне кластера предусмотрены следующие переменные:

  • Переменная track_global_snapshots включает менеджер распределённых транзакций (построенный на алгоритме Clock-SI), который обеспечивает изоляцию транзакций в рамках кластера на уровне Repeatable Read. При изменении этого параметра требуется перезапустить сервер.

  • Переменная postgres_fdw.use_global_snapshots определяет, должны ли использоваться глобальные снимки для текущей транзакции. Если вы устанавливаете для неё значение on, вы должны также включить менеджер распределённых транзакций с помощью переменной track_global_snapshots. Значение postgres_fdw.use_global_snapshots можно изменить в любое время — оно проверяется в момент фиксации транзакций.

Важно

Операторы PREPARE не передаются при логической репликации, из-за чего может нарушиться согласованность данных, если какой-то узел не работает постоянно. Часть распределённой транзакции может потеряться и результат окажется неатомарным, если координатор подготовит транзакцию везде, затем начнёт фиксировать её, а один из узлов откажет, не успев передать и зафиксировать транзакцию на реплике.

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

F.45.2.2. Репликация

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

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

pg_shardman синхронизирует данные между первичными сегментами и их репликами, используя логическую репликацию. При этом репликация может быть как синхронной, так и асинхронной. Плюсы и минусы хорошо известны: синхронная репликация медленнее, но потеря зафиксированных транзакций менее вероятна. Асинхронная репликация быстрее, но допускает отставание реплик от первичных источников на произвольное время, что может привести к потере некоторых недавно зафиксированных транзакций или переполнению WAL в случае отказа реплики.

Тип репликации определяется переменной shardman.sync_replication. Если она имеет значение off (по умолчанию), включается асинхронная репликация. В противном случае задействуется синхронная репликация: хотя транзакции на первичном узле будут фиксироваться локально сразу после запроса COMMIT, клиент не получит подтверждения транзакции, пока она не будет зафиксирована на всех репликах.

По умолчанию уровень избыточности кластера равен 0, так что реплики не создаются. Уровень избыточности можно изменить следующим образом:

F.45.3. Установка и подготовка

Расширение pg_shardman включено в состав Postgres Pro Enterprise. После установки Postgres Pro Enterprise на всех узлах, которые будут образовывать сегментируемый кластер, вам нужно подготовить эти узлы и создать расширение pg_shardman вместе с требуемыми зависимостями.

F.45.3.1. Подготовка сегментируемого кластера

Чтобы подготовить узлы, необходимо изменить файл postgresql.conf на всех узлах, как описано ниже.

  1. Выберите узел, который будет контроллером сегментирования, и настройте следующие специфичные для него параметры:

    • Назначьте этот узел контроллером сегментирования:

      shardman.shardlord = on
    • Укажите тип репликации, который будет применяться при создании реплик сегментов:

      shardman.sync_replication = on

      По умолчанию применяется асинхронная репликация.

      Важно

      Переменную конфигурации shardman.sync_replication не следует менять после настройки кластера. Если вы захотите изменить выбранный ранее тип репликации, вам придётся удалить все реплики и создать их заново с помощью функции shardman.set_redundancy(rel_name regclass, redundancy int) .

  2. На всех узлах, включая контроллер, измените следующие параметры:

    • Убедитесь в том, что переменная shared_preload_libraries включает значения postgres_fdw, pg_pathman и pg_shardman. Библиотека pg_pathman должна всегда указываться последней в этом списке:

      shared_preload_libraries = 'postgres_fdw, pg_shardman, pg_pathman'
    • Укажите параметры, требуемые для подключения к контроллеру сегментирования с правами суперпользователя. В строках подключения можно использовать параметры, которые принимает libpq, как описано в Подразделе 33.1.2.

      shardman.shardlord_connstring = connstring

      Рабочие узлы будут использовать эту строку подключения при передаче команд контроллеру. Контроллер использует эту строку при сегментировании таблиц.

  3. На рабочих узлах настройте следующие параметры:

    • Установите для переменной shardman.shardlord значение off:

      shardman.shardlord = off
    • Установите в wal_level значение logical для включения логической репликации:

      wal_level = logical

      Другие параметры логической репликации зависят от числа узлов в группе репликации, к которой относится узел. Для группы с N узлами рекомендуются следующие минимальные значения:

      max_replication_slots = 2N + 1
      max_wal_senders = N + 1
      max_logical_replication_workers = N + 1
      max_worker_processes = max_logical_replication_workers + 1
    • Включите поддержку двухфазной фиксации транзакций (2PC) и менеджер распределённых транзакций для обеспечения изоляции транзакций на уровне кластера. Также важно увеличить значение max_prepared_transactions для поддержки подготовленных транзакций:

      postgres_fdw.use_twophase = on
      track_global_snapshots = on
      postgres_fdw.use_global_snapshots = on
      max_prepared_transactions = 1000
    • Убедитесь в том, что в переменной synchronous_commit выбран нужный вам тип репликации. Для синхронной репликации она должна иметь значение on. Изменить это значение можно в любой момент, так что для разных транзакций могут использоваться разные режимы репликации.

      synchronous_commit = on

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

CREATE EXTENSION pg_shardman CASCADE;

Postgres Pro Enterprise устанавливает pg_shardman вместе с требующимися для него расширениями pg_pathman и postgres_fdw. Расширение pg_shardman устанавливается в схему shardman, название которой на данный момент предопределено и не может быть изменено. Теперь вы можете подключить все рабочие узлы к контролеру, как рассказывается в Подразделе F.45.3.2.

F.45.3.2. Добавление и удаление узлов

Прежде чем приступить к сегментированию таблиц, необходимо объединить все рабочие узлы в один кластер с контроллером сегментирования. За общими рекомендациями по выбору оптимального числа узлов обратитесь к Подразделу F.45.3.3.1.

Чтобы добавить рабочий узел в кластер, выполните на контроллере следующую функцию:

shardman.add_node(super_conn_string text, conn_string text DEFAULT NULL, repl_group text DEFAULT 'default')

Вы можете задать два варианта строк подключения. Строка super_conn_string является обязательной, так как для настройки логической репликации между узлами требуются права суперпользователя. Необязательный параметр conn_string используется для настройки обёрток сторонних данных; это позволяет обращаться к данным без прав суперпользователя. Если этот параметр не задан, pg_shardman использует super_conn_string для всех целей. Параметр repl_group определяет группу репликации, в которую будет добавлен узел. Если он не задан, узел добавляется в новую автоматически создаваемую группу. Подробнее группы репликации описаны в Подразделе F.45.3.3.2.

Каждый узел кластера получает уникальный номер и добавляется в Подразделе F.45.5.3.1. Номера узлов начинаются с 1 и увеличиваются на 1 для каждого следующего узла.

Если узел ранее принадлежал кластеру, управляемому другим контроллером, его состояние будет сброшено. Добавив в кластер все узлы, вы можете начать сегментирование таблиц, как описано в Подразделе F.45.4.1.

Подсказка

Чтобы узнать номер узла, выполните на этом узле следующую функцию:

SELECT shardman.get_my_id();

Чтобы удалить узел из кластера, выполните на контроллере сегментации:

shardman.rm_node(rm_node_id int, force bool DEFAULT false)

Здесь ид_узла — номер удаляемого узла. По умолчанию узел не исключается из кластера, если на нём располагается хотя бы один первичный сегмент. Чтобы переопределить это поведение, передайте в параметре force значение true. В этом случае узел будет исключён из кластера, и место его первичных сегментов займут наилучшие реплики с других узлов.

Функция shardman.rm_node() не удаляет таблицы с данными и сторонние таблицы на удаляемом узле. Удалить таблицу со всеми данными, включая реплики сегментов, позволяет функция shardman.rm_table(rel_name regclass) . Внимание: эта операция не требует подтверждения и отменить её нельзя.

F.45.3.3. Выбор стратегий сегментирования и репликации

F.45.3.3.1. Определение числа сегментов и узлов

Сегментировав таблицу, вы не сможете изменить число её сегментов, поэтому необходимо определить подходящее число сегментов с самого начала. Очевидно, что для эффективного использования имеющихся аппаратных ресурсов число сегментов должно быть не меньше числа узлов. В противном случае pg_shardman не сможет распределять данные по всем узлам кластера. Размещение одного сегмента на узле обычно обеспечивает наилучшую производительность, особенно если используется синхронная репликация. Однако в этом случае вы не сможете перераспределить данные при добавлении новых узлов или устранить перекос нагрузки, если к одним данным обращения происходят чаще, чем к другим.

Число рабочих узлов в сегментируемом кластере зависит от числа имеющихся у вас серверов, объёма данных, который вы намерены хранить, и ожидаемой нагрузки. Увеличение числа узлов позволяет хранить больше данных и даёт большую производительность для некоторых запросов, не затрагивающих сразу все узлы. Число узлов в кластере можно увеличить в любое время.

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

F.45.3.3.2. Организация групп репликации

Группа репликации представляет собой подмножество узлов кластера, которые могут создавать реплики сегментов друг друга. Каждый узел в кластере принадлежит какой-либо группе репликации, и эти группы не пересекаются. При этом узлы не могут реплицировать свои сегменты в другие группы репликации. Разделение узлов кластера по группам репликации даёт следующие преимущества:

  • Минимизация влияния логической репликации на производительность. Логическая репликация в продуктах на базе PostgreSQL работает относительно медленно при большом количестве передатчиков WAL на одном узле, так как каждый передатчик декодирует весь WAL. Если вы применяете синхронную репликацию, большое количество передатчиков WAL может негативно сказаться на быстродействии. Увеличение числа синхронных ведомых серверов может повлечь почти линейное снижение производительности. Группы репликации ограничивают число узлов, на которых могут размещаться реплики. Например, в группе репликации с тремя узлами требуется, чтобы на каждом узле работали только два передатчика WAL, осуществляющих логическую репликацию с двумя другими узлами, вне зависимости от числа размещённых на них сегментов и реплик.

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

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

pg_shardman настраивает каналы логической репликации между узлами в группах репликации. Даже в отсутствие реплик каналы логической репликации могут привносить значительные издержки, так как передатчики WAL по-прежнему должны декодировать весь WAL и передавать декодированные пустые транзакции подписчикам. По умолчанию каждый узел добавляется в уникальную группу репликации с именем, совпадающим с уникальным системным идентификатором узла, а значит такая группа состоит из одного узла и каналы логической репликации не образуются: при этом вы не можете создавать реплики, но и никаких издержек нет.

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

F.45.4. Администрирование сегментируемого кластера

F.45.4.1. Сегментирование таблиц

pg_shardman позволяет сегментировать только пустые таблицы, созданные на контроллере, поэтому типичная процедура сегментирования таблицы выглядит так:

  1. Создайте пустую таблицу на контроллере сегментирования.

    Для создания таблицы на контроллере используйте обычный синтаксис CREATE TABLE. Например:

    CREATE TABLE films (id int PRIMARY KEY, title varchar(40));
  2. Разбейте созданную таблицу на сегменты.

    Выполните следующую команду на контроллере:

    SELECT shardman.create_hash_partitions(
        relation regclass,
        expression text, part_count int,
        redundancy int DEFAULT 0);

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

    pg_shardman распределяет сегменты по узлам кластера, используя алгоритм выбора по кругу. Реплики создаются в той же группе репликации на случайно выбираемых узлах. Каждый узел может содержать только одну реплику определённого сегмента.

    Например, чтобы разделить таблицу films на 30 сегментов по столбцу id и создать по одной реплике для каждого сегмента, выполните:

    SELECT create_hash_partitions('films', 'id', 30, redundancy = 1);

    При успешном выполнении команды все сегменты будут равномерно распределены между доступными рабочими узлами. Между узлами, содержащими первичные сегменты и их реплики, будут организованы каналы логической репликации.

  3. Импортируйте данные в сегментированную таблицу.

    После того как сегменты распределены по узлам кластера, вы можете наполнить их данными. Так как для сегментирования используются встроенные хеш-функции Postgres Pro, на уровне клиентского приложения невозможно предсказать, на каком узле будет сохранена конкретная запись.

    Наиболее эффективно загрузить данные в таблицу можно с помощью формы COPY FROM команды COPY. pg_shardman принимает только текстовый формат и CSV, двоичный формат не поддерживается. Импортировать данные можно параллельно с нескольких узлов.

    Вы также можете наполнять сегменты, используя обычные команды INSERT, которые pg_pathman будет передавать соответствующему узлу. Однако выполнение множества команд INSERT будет менее эффективным, чем использование COPY FROM.

    Подсказка

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

F.45.4.2. Работа с данными кластера

Когда таблица сегментирована и заполнена данными, вы можете производить операции DML с этой таблицей на любом рабочем узле pg_shardman. Операторы DML при этом могут обращаться к нескольким сегментам, удалённым или локальным. Это реализуется с использованием стандартного механизма наследования Postgres Pro: все сегменты наследуются от родительской таблицы. Если требующийся сегмент находится на другом узле, к нему происходит обращение через postgres_fdw.

Примечание

Наследование и сторонние таблицы имеют некоторые ограничения, не позволяющие строить эффективные планы выполнения для некоторых запросов. Хотя Postgres Pro может передавать агрегатные функции сторонним серверам, объединение неполных агрегатных значений с разных узлов не поддерживается. Кроме того, запросы не могут выполняться на разных узлах параллельно: обёртки сторонних данных не поддерживают параллельное сканирование из-за использования курсоров. Таким образом, pg_shardman предназначен в первую очередь для нагрузки OLTP. Запросы OLAP с ним будут выполняться неэффективно.

Поддержка DDL весьма ограниченная. Функция shardman.alter_table(relation regclass, alter_clause text) меняет определение родительской таблицы на всех узлах в метаданных кластера; её можно использовать для добавления, удаления или переименования столбцов, а также создания ограничений NOT NULL. Однако модификация столбца, служащего ключом сегментирования, не поддерживается. Также не поддерживаются внешние ключи, ссылающиеся на сегментированные таблицы. Хотя вы можете создать ограничение UNIQUE, оно будет распространяться только на каждый отдельный сегмент.

Если вы создадите индекс в родительской таблице до сегментирования, он войдёт в определение таблицы и будет создан во всех сегментах и репликах автоматически. Простого способа создать индекс в уже сегментированной таблице не существует — его нужно создавать отдельно в каждом сегменте, как описано на вики-странице pg_pathman. Чтобы выполнить один оператор на всех узлах, вы можете воспользоваться функцией shardman.forall(sql text, use_2pc bool DEFAULT false, including_shardlord bool DEFAULT false) .

Удалить таблицу со всеми данными, включая реплики сегментов, позволяет функция shardman.rm_table(rel_name regclass) . Внимание: эта операция не требует подтверждения и отменить её нельзя.

F.45.4.3. Настройка локальных и общих таблиц

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

Локальные таблицы хранят данные, уникальные для каждого отдельного узла. Например, это могут быть данные временных таблиц. К локальным таблицам можно обращаться минуя pg_shardman. Вы можете создавать их и использовать локально на каждом узле, как и в обычных кластерах Postgres Pro.

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

Чтобы создать общую таблицу, выполните следующую функцию:

shardman.create_shared_table( rel regclass, master_node_id int DEFAULT 1)

Параметр master_node_id задаёт уникальный идентификатор, назначенный узлу при добавлении в кластер.

Подсказка

Чтобы узнать номер узла, выполните на этом узле следующую функцию:

SELECT shardman.get_my_id();

F.45.4.4. Балансировка нагрузки

Когда в кластер добавляются новые узлы, изначально они пусты. Чтобы начать использовать их, необходимо перераспределить данные. Процедура распределения данных выполняется в фоновом режиме. Для уменьшения влияния этой процедуры на обычную работу кластера, сегменты и реплики перемещаются последовательно, один за другим.

pg_shardman предоставляет следующие функции для перераспределения первичных сегментов и реплик, соответственно:

shardman.rebalance(table_pattern text DEFAULT '%')

shardman.rebalance_replicas(table_pattern text DEFAULT '%')

Все сегменты/реплики с именами, соответствующими заданному шаблону, равномерно распределяются между всеми узлами в каждой группе репликации. Шаблон именования для первичных сегментов и реплик одинаковый:

${имя_сегментируемой_таблицы}_${номер_сегмента}

Например, если вы сегментировали таблицу films, следующая команда перераспределит все её первичные сегменты:

SELECT shardman.rebalance('films%')

Подобным образом, чтобы перераспределить все реплики, выполните:

SELECT shardman.rebalance_replicas('films%')

Если аргумент отсутствует, будут перераспределены сегменты или реплики для всех сегментированных таблиц в кластере.

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

shardman.mv_partition( part_name text, dst_node_id int)

shardman.mv_replica( part_name text, src_node_id int, dst_node_id int)

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

Примечание

pg_shardman может перемещать данные только в рамках той же группы репликации. Подробнее о группах репликации рассказывается в Подразделе F.45.3.3.2.

F.45.4.5. Обнаружение сбоя и восстановление

pg_shardman не поддерживает автоматическое обнаружение сбоев и восстановление. Если узел выходит из строя, его нужно вручную исключить из кластера, как описано ниже.

Для проверки и разрешения распределённых взаимоблокировок вы можете воспользоваться функцией shardman.monitor(). За подробностями обратитесь к Подразделу F.45.4.5.3.

F.45.4.5.1. Отработка отказа рабочего узла

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

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

Если вы не планируете восстанавливать отказавший узел, его следует вручную исключить из кластера следующим образом:

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

  2. Выполните следующую команду для исключения узла из кластера:

    SELECT shardman.rm_node(${failed_node_id}, force => true);

    Если уровень избыточности больше нуля, pg_shardman пытается заменить все первичные сегменты, хранящиеся на исключаемом узле, их репликами. Из всех реплик выбирается наилучшая и с ней синхронизируются остальные реплики.

    Подсказка

    Задержку репликации можно наблюдать в представлении shardman.replication_lag; она может быть важна при асинхронной репликации.

  3. Чтобы убедиться в отсутствии зависших транзакций 2PC, выполните:

    SELECT shardman.recover_xacts();

Заметьте, что недавние транзакции или части распределённых транзакций всё же могут быть потеряны; это разъясняется в Подразделе F.45.2.1.

F.45.4.5.2. Отработка отказа контроллера

Контроллер сегментирования содержит только метаданные кластера, как описано в Подраздел F.45.5.3.2. Эти метаданные используются лишь при изменении узлов в кластере или перераспределении данных. Таким образом, даже если контроллер выходит из строя, вы можете продолжать выполнять запросы на чтение и запись с существующими сегментами.

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

  1. Настройте новый контроллер сегментирования, следуя инструкциям в Подразделе F.45.3.1. При этом важно воспроизвести те же настройки репликации, что имели место на исходном контроллере.

  2. Измените параметр shardman.shardlord_connstring на всех рабочих узлах.

Важно

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

F.45.4.5.3. Выявление распределённых взаимоблокировок и отказавших узлов

Для постоянного наблюдения за отказами узлов и распределёнными взаимоблокировками в кластере вы можете выполнить следующую функцию:

shardman.monitor(check_timeout_sec int DEFAULT 5, rm_node_timeout_sec int DEFAULT 60)

Эта функция запускает бесконечный цикл, в котором опрашиваются все узлы кластера и с них собираются локальные графы блокировок. Интервал опроса задаётся параметром check_timeout_sec. По умолчанию он равен 5 сек. Локальные графы блокировок объединяются в глобальный граф, который анализируется на предмет образования циклов. Цикл в графе блокировок означает, что возможна распределённая взаимоблокировка.

Так как собираемые с различных узлов локальные графы могут не образовывать согласованный глобальный снимок, возможны ложные срабатывания: рёбра в цикле взаимоблокировок соответствуют разным моментам времени. Для исключения ложных срабатываний pg_shardman сравнивает результаты двух последовательных проверок, прежде чем констатировать взаимоблокировку. Если циклы в двух итерациях совпадают, взаимоблокировка считается подтверждённой.

pg_shardman пытается разрешить выявленные взаимоблокировки, прерывая один или несколько обслуживающих процессов, вовлечённых в цикл взаимоблокировки. Для этого вызывается функция pg_cancel_backend, которая пытается отменить текущий запрос, не уничтожая процесс. Процесс, который будет прерываться, выбирается в цикле взаимоблокировки случайным образом.

В случае недоступности узла pg_shardman выводит соответствующее сообщение об ошибке и пытается повторно обратиться к нему до истечения тайм-аута rm_node_timeout_sec. Если узел остаётся недоступным в заданном временном интервале, pg_shardman исключает его из кластера следующим образом:

  1. Функция shardman.rm_node() удаляет узел из кластера. Если уровень избыточности больше нуля, первичные сегменты отключаемого узла заменяются репликами.

  2. pg_shardman пытается восстановить выполняемые на отказавшем узле распределённые транзакции, вызывая функцию shardman.recover_xacts. Если узел, начавший транзакцию, остаётся в кластере, pg_shardman проверяет состояние транзакции на этом узле. В противном случае pg_shardman проверяет состояние этой транзакции на всех остальных узлах. Если имела место минимум одна успешная фиксация и не было прерывания, транзакция фиксируется. Если имело место прерывание и не было успешной фиксации, транзакция прерывается. Чтобы эта функция могла разрешить транзакцию, должны функционировать все узлы в кластере.

F.45.5. Справочная информация

F.45.5.1. Переменные GUC

shardman.shardlord (boolean)

Определяет, является ли экземпляр Postgres Pro Enterprise контроллером сегментирования. Изменение этой переменной требует перезапуска сервера.

По умолчанию: false

Задать этот параметр можно только в postgresql.conf или в командной строке при запуске сервера.

shardman.sync_replication (boolean)

Если установлено значение on, pg_shardman добавляет реплики в список synchronous_standby_names, тем самым включая синхронную репликацию.

По умолчанию: off

Задать этот параметр можно только в postgresql.conf или в командной строке при запуске сервера.

shardman.shardlord_connstring (text)

Строка подключения для контроллера сегментирования. В ней можно использовать все параметры, которые принимает libpq в строках подключения, описанные в Подраздел 33.1.2. Подключение к узлу должно выполняться от имени суперпользователя. Устанавливать эту переменную необходимо на самом контроллере. Вы можете также дополнительно задать её на рабочих узлах, если хотите выполнять на них функции управления кластером. В таком случае эти команды будут автоматически передаваться контроллеру.

Задать этот параметр можно только в postgresql.conf или в командной строке при запуске сервера.

F.45.5.2. Функции

Для управления сегментированным кластером pg_shardman предоставляет обычные функции Postgres Pro в схеме shardman. Все функции, за исключением shardman.get_my_id() , должны выполняться на контроллере сегментирования. Функции pg_shardman выдают результат немедленно, не дожидаясь завершения операции, за исключением shardman.ensure_redundancy() , которая предназначена именно для ожидания завершения синхронизации данных.

F.45.5.2.1. Административные функции
shardman.get_redundancy_of_partition(pname text)

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

Аргументы:

  • pname — имя сегмента, для которого возвращается число реплик.

shardman.get_min_redundancy(rel_name regclass)

Возвращает минимальный уровень избыточности для заданной таблицы.

Аргументы:

  • rel_name — имя сегментированной таблицы.

shardman.get_node_partitions_count(node int)

Возвращает число сегментов на заданном узле.

Аргументы:

  • node — идентификатор узла, на котором подсчитываются сегменты.

shardman.get_node_replicas_count(node int)

Возвращает число реплик на заданном узле.

Аргументы:

  • node — идентификатор узла, на котором подсчитываются реплики.

shardman.get_my_id()

Возвращает идентификатор текущего узла. Эту функцию можно выполнять только на рабочих узлах.

F.45.5.2.2. Функции для управления членством
shardman.add_node(super_conn_string text, conn_string text DEFAULT NULL, repl_group text DEFAULT 'default')

Добавляет узел с заданной строкой подключения в кластер и возвращает идентификатор узла. Если узел содержит состояние pg_shardman от другого кластера, управляемого другим контроллером, это состояние будет потеряно. Сразу после добавления в кластер узел не содержит никакие данные. Чтобы переместить сегмент или реплику на этот узел, вы должны использовать функции перераспределения. Однако узел немедленно получает информацию о сегментированных таблицах в кластере и может принимать и перенаправлять запросы.

Аргументы:

  • super_conn_string — строка подключения, позволяющая обратиться к данному узлу с правами суперпользователя. Эта строка подключения используется для настройки логической репликации между узлами кластера. В ней можно использовать все параметры, которые принимает libpq в строках подключения, описанные в Подразделе 33.1.2.

  • conn_string — строка подключения к узлу, которая будет использоваться для операций DDL и настройки postgres_fdw без прав суперпользователя. Если вы собираетесь применять pgbouncer между узлами кластера, вы должны задать в этой строке адрес pgbouncer, а в super_conn_string указывать фактические адреса узлов. В противном случае логическая репликация между узлами будет нарушена, так как pgbouncer не поддерживает репликацию. Вы можете использовать все параметры, которые принимает libpq в строках подключения, описанные в Подразделе 33.1.2.

    Если в качестве conn_string передаётся NULL, для всех целей применяется строка подключения super_conn_string.

  • repl_group — группа репликации, в которую будет добавляться новый узел. Если этот параметр опущен, узел добавляется в новую автоматически создаваемую группу. После добавления узла в кластер его нельзя переместить в другую группу репликации. Для этого его нужно будет удалить и добавить вновь.

shardman.rm_node(rm_node_id int, force bool DEFAULT false)

Удаляет заданный узел из сегментированного кластера. Если параметр force имеет значение false (по умолчанию), узел, содержащий первичные сегменты, не удаляется. Если force имеет значение true, узел удаляется из кластера, даже если он содержит такие сегменты. Если удаляемый узел содержит первичные сегменты с ненулевым уровнем избыточности, pg_shardman выбирает лучшую реплику на других узлах и делает её первичной.

Эта функция не удаляет таблицы с данными и сторонние таблицы на удаляемом узле. Если удаляемый узел функционирует, pg_shardman выполняет shardman.wipe_state(force bool DEFAULT true) .

Аргументы:

  • rm_node_id — идентификатор удаляемого узла.

  • force — определяет, как поступить с узлом, содержащим первичные сегменты:

    • true — удалить узел и сделать первичными сегментами лучшие реплики удаляемых его сегментов, найденные на других узлах.

    • false — запретить удаление узла, если он содержит первичные сегменты.

shardman.create_shared_table( rel regclass, master_node_id int DEFAULT 1)

Создаёт таблицу, которая будет представлена на всех рабочих узлах. Эту функцию нужно выполнять на контроллере сегментирования. Заранее созданная пустая таблица должна присутствовать на контроллере, но не на рабочих узлах.

F.45.5.2.3. Сегменты и реплики
shardman.create_hash_partitions( rel_name regclass, expr text, part_count int, redundancy int DEFAULT 0)

Сегментирует таблицу rel_name, используя разбиение по хешу по ключу expr, создавая part_count сегментов и распределяя сегменты равномерно по всем узлам кластера. Сегментировать можно только пустую таблицу, созданную на контроллере командой CREATE TABLE. За подробностями обратитесь к Подразделу F.45.4.1.

Аргументы:

  • rel_name — пустая таблица, которая будет сегментироваться.

  • expr — ключ разбиения, по которому будет сегментироваться таблица. Столбец (столбцы), указываемый в expr, должен иметь характеристику NOT NULL. В качестве ключа разбиения настоятельно рекомендуется использовать первичный ключ во избежание проблем при операциях UPDATE и DELETE.

  • part_count — число создаваемых сегментов.

  • redundancy — число реплик, создаваемых для каждого сегмента. По умолчанию pg_shardman реплики не создаёт.

shardman.rm_table(rel_name regclass)

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

Аргументы:

  • rel_name — таблица, подлежащая удалению.

shardman.set_redundancy(rel_name regclass, redundancy int)

Создаёт реплики для сегментов заданной таблицы до получения для каждого сегмента количества реплик, которое задаётся параметром redundancy. Узлы для размещения реплик выбираются случайным образом из группы репликации, к которой относится соответствующий первичный сегмент. Если текущий уровень избыточности больше заданного, эта функция ничего не делает.

Эта функция только запускает репликацию и не ждёт копирования данных во все реплики. Чтобы дождаться полной синхронизации таблицы, выполните shardman.ensure_redundancy() .

Аргументы:

  • rel_name — таблица, подлежащая репликации.

  • redundancy — число реплик, создаваемых для каждого сегмента.

shardman.ensure_redundancy()

Ждёт завершения начальной синхронизации данных для всех подписчиков. Эта функция может вызываться после set_redundancy(), когда нужна уверенность в том, что все данные из первичных сегментов скопированы в реплики.

F.45.5.2.4. Функции для перераспределения данных

pg_shardman предоставляет набор функций для перераспределения сегментов и реплик между узлами кластера.

shardman.rebalance(table_pattern text DEFAULT '%')

Перераспределяет сегменты между узлами кластера. Эта функция пытается равномерно распределить сегменты таблиц с именами, удовлетворяющими выражению LIKE 'table_pattern', между всеми узлами соответствующих групп репликации, поэтому её нужно вызывать после удаления/добавления узлов. Эта функция не может перемещать сегменты между группами репликации. Сегменты перемещаются последовательно для минимизации влияния на производительность системы. Так как pg_shardman использует логическую репликацию, вы можете продолжать выполнять запросы на запись в процессе перераспределения данных кластера.

Аргументы:

  • table_pattern — шаблон имён таблиц, подлежащих перераспределению.

shardman.rebalance_replicas(table_pattern text DEFAULT '%')

Перераспределяет реплики сегментов между узлами кластера. Эта функция пытается равномерно распределить сегменты таблиц с именами, удовлетворяющими выражению LIKE 'table_pattern', между всеми узлами соответствующих групп репликации, поэтому её нужно вызывать после добавления/удаления узлов. Эта функция не может перемещать реплики между группами репликации. Реплики перемещаются последовательно для минимизации влияния на производительность системы. Так как pg_shardman использует логическую репликацию, вы можете продолжать выполнять запросы на запись в процессе перераспределения данных кластера.

Аргументы:

  • table_pattern — шаблон имён таблиц, подлежащих перераспределению.

shardman.mv_partition( part_name text, dst_node_id int)

Перемещает первичный сегмент с именем part_name на заданный узел в той же группе репликации. Для перераспределения нескольких сегментов вместо этой функции можно использовать shardman.rebalance().

shardman.mv_replica( part_name text, src_node_id int, dst_node_id int)

Перемещает реплику part_name на заданный узел в той же группе репликации. Эта команда выдаст ошибку, если целевой узел уже содержит реплику этого сегмента. Для перераспределения нескольких реплик вместо этой функции можно использовать shardman.rebalance_replicas().

shardman.forall(sql text, use_2pc bool DEFAULT false, including_shardlord bool DEFAULT false)

Выполняет SQL-оператор на всех узлах.

Аргументы:

  • sql — оператор, который будет выполнен.

  • use_2pc — определяет, будет ли применяться двухфазная фиксация. Значение по умолчанию определяется параметром postgres_fdw.use_twophase.

  • including_shardlord — определяет, выполнять ли этот SQL-оператор на контроллере. По умолчанию оператор выполняется только на рабочих узлах.

shardman.alter_table(relation regclass, alter_clause text)

Изменяет определение сегментированных или общих таблиц.

Аргументы:

  • relation — таблица, подлежащая изменению.

  • alter_clause — выполняемая команда изменения.

Пример:

   SELECT shardman.alter_table('films', 'ADD COLUMN author text');
shardman.recover()

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

shardman.monitor(check_timeout_sec int DEFAULT 5, rm_node_timeout_sec int DEFAULT 60)

Контролирует состояние кластера в целях обнаружения распределённых взаимоблокировок и отказов узлов. Если выявляется взаимоблокировка, pg_shardman пытается разрешить её, отменяя один или несколько запросов в соответствующих обслуживающих процессах. За подробностями обратитесь к Подразделу F.45.4.5.3. При попытке выполнить эту функцию на рабочем узле она передаётся на контроллер.

Аргументы:

  • check_timeout_sec — интервал опроса, в секундах. pg_shardman проверяет блокировки, опрашивая через заданное время каждый узел. По умолчанию интервал составляет 5 сек.

  • rm_node_timeout_sec — временной интервал, в секундах, в течение которого pg_shardman пытается обратиться к этому узлу. Если узел не отвечает за это время, вызывается функция shardman.rm_node для исключения этого узла из кластера. Если в rm_node_timeout_sec передаётся NULL, pg_shardman не исключает отказавший узел.

shardman.recover_xacts()

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

  • Если начавший транзакцию узел (координатор) по-прежнему работает в кластере, pg_shardman узнаёт результат транзакции и распространяет его на весь кластер.

  • Если координатор уже исключён из кластера, pg_shardman проверяет состояние этой транзакции на всех остальных узлах. Если имела место минимум одна фиксация и не было прерывания, транзакция фиксируется. Если имело место минимум одно прерывание и не было успешной фиксации, транзакция прерывается. В противном случае вам может потребоваться разрешить транзакцию вручную.

  • Если координатор выключен, но по-прежнему является членом кластера, транзакцию нужно будет разрешить вручную.

Так как pg_shardman не контролирует перезапись WAL, shardman.recover_xacts использует для проверки транзакций clog. В редких случаях shardman.recover_xacts может не суметь получить состояние транзакции и разрешить её, и тогда её придётся разрешать вручную.

shardman.wipe_state(force bool DEFAULT true)

Удаляет все публикации, подписки, слоты репликации, сторонние серверы и сопоставления пользователей, которые создаёт на рабочем узле pg_shardman. Postgres Pro не позволяет удалять слоты репликации с активными подключениями. Если параметр force равен true, pg_shardman пытается уничтожить процессы-передатчики WAL, прежде чем удалять слоты. При этом данные, находящиеся на узле, не затрагиваются. После фиксации транзакции переменной synchronous_standby_names присваивается пустая строка. Это не транзакционное действие, так что есть небольшая вероятность, что оно не будет завершено.

Эту функцию может иметь смысл выполнить перед DROP EXTENSION pg_shardman.

F.45.5.3. Таблицы pg_shardman

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

F.45.5.3.1. Таблица shardman.nodes

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

Таблица F.32. Таблица shardman.nodes

Имя столбцаТип столбцаОписание
idserialИдентификатор узла. Идентификаторы начинаются с 1 для первого узла и увеличиваются на 1 для каждого нового добавляемого узла.
system_idbigintИдентификатор системы.
super_connection_stringtextСтрока для подключения к этому узлу с правами суперпользователя. Эта строка подключения используется для настройки логической репликации между узлами.
connection_stringtextСтрока для подключения к этому узлу без прав суперпользователя. Эта строка подключения используется для выполнения операций DDL и настройки postgres_fdw.
replication_grouptextГруппа узлов, в которой размещаются реплики сегментов.

F.45.5.3.2. Таблица shardman.tables

Содержит список всех сегментированных таблиц.

Таблица F.33. Таблица shardman.tables

Имя столбцаТип столбцаОписание
relationtextИмя сегментированной или общей таблицы.
sharding_keytextВыражение разбиения, по которому сегментируется таблица.
master_nodeintИдентификатор узла, на котором находится общая таблица.
partitions_countintЧисло разделов этой таблицы.
create_sqltextКоманда SQL, используемая для воссоздания таблицы на других узлах.
create_rules_sqltextКоманда SQL, создающая правила для общей таблицы.

F.45.5.3.3. Таблица shardman.partitions

Содержит список первичных сегментов всех сегментированных таблиц.

Таблица F.34. Таблица shardman.partitions

Имя столбцаТип столбцаОписание
part_nametextИмя первичного сегмента.
node_idintИдентификатор узла, на котором находится первичный сегмент.
relationtextРодительская таблица для этого сегмента.

F.45.5.3.4. Таблица shardman.replicas

Содержит все реплики сегментов.

Таблица F.35. Таблица shardman.replicas

Имя столбцаТип столбцаОписание
part_nametextИмя реплики сегмента.
node_idintИдентификатор узла, на котором размещена реплика.
relationtextРодительская таблица для соответствующего первичного сегмента.

F.45.5.3.5. Представление shardman.replication_lag

Это представление содержит информацию о задержках репликации, которая может быть важна при асинхронной репликации.

F.45.6. Авторы

Postgres Professional, Москва, Россия