|
| 1 | +#pg_pathman |
| 2 | + |
| 3 | +Модуль`pg_pathman` предоставляет оптимизированный механизм секционирования, а также функции для создания и управления секциями. |
| 4 | + |
| 5 | +##Концепция pg_pathman |
| 6 | + |
| 7 | +Секционирование -- это способ разбиения одной большой таблицы на множество меньших по размеру. Для каждой записи можно однозначно определить секцию, в которой она должна храниться посредством вычисления ключа. Традиционно выделяют три стратегии секционирования: |
| 8 | + |
| 9 | +* HASH - данные равномерно распределяются по секциям в соответствии со значениями hash-функции, вычисленными по некоторому атрибуту; |
| 10 | +* RANGE - данные распределяются по секциям, каждая из которых ответственна за заданный диапазон значений аттрибута; |
| 11 | +* LIST - для каждой секции определяется набор конкретных значений атрибута. |
| 12 | + |
| 13 | +Секционирование в postgres основано на механизме наследования. Каждому наследнику задается условие CHECK CONSTRAINT. Например: |
| 14 | + |
| 15 | +``` |
| 16 | +CREATE TABLE test (id SERIAL PRIMARY KEY, title TEXT); |
| 17 | +CREATE TABLE test_1 (CHECK ( id >= 100 AND id < 200 )) INHERITS (test); |
| 18 | +CREATE TABLE test_2 (CHECK ( id >= 200 AND id < 300 )) INHERITS (test); |
| 19 | +``` |
| 20 | + |
| 21 | +Несмотря на гибкость, этот механизм обладает недостатками. Так при фильтрации данных оптимизатор вынужден перебирать все дочерние секции и сравнивать условие запроса с CHECK CONSTRAINT-ами секции, чтобы определить из каких секций ему следует загружать данные. При большом количестве секций это создает дополнительные накладные расходы, которые могут свести на нет выигрыш в производительности от применения секционирования. |
| 22 | + |
| 23 | +Модуль`pg_pathman` предоставляет функции для создания и управления |
| 24 | +секциями (см. следующий раздел) и механизм секционирования, |
| 25 | +оптимизированный с учетом знания о структуре дочерних таблиц. Конфигурация сохраняется таблице`pathman_config`, каждая строка которой содержит запись для одной секционированной таблицы (название таблицы, атрибут и тип разбиения). В процессе инициализации модуля в разделяемую память сохраняется конфигурация дочерних таблиц в удобном для поиска формате. Получив запрос типа`SELECT` к секционированной таблице,`pg_pathman` анализирует дерево условий запроса и выделяет из него условия вида: |
| 26 | + |
| 27 | +``` |
| 28 | +ПЕРЕМЕННАЯ ОПЕРАТОР КОНСТАНТА |
| 29 | +``` |
| 30 | +где`ПЕРЕМЕННАЯ` -- это атрибут, по которому было выполнено разбиение,`ОПЕРАТОР` -- оператор сравнения (поддерживаются =, <, <=, >, >=),`КОНСТАНТА` -- скалярное значение. Например: |
| 31 | + |
| 32 | +``` |
| 33 | +WHERE id = 150 |
| 34 | +``` |
| 35 | +Затем основываясь на стратегии секционирования и условиях запроса`pg_pathman` выбирает соответствующие секции и строит план. |
| 36 | + |
| 37 | +##Installation |
| 38 | + |
| 39 | +Для установки pg_pathman выполните в командной строке: |
| 40 | +``` |
| 41 | +CREATE SCHEMA pathman; |
| 42 | +CREATE EXTENSION pg_pathman SCHEMA pathman; |
| 43 | +
|
| 44 | +``` |
| 45 | +Затем модифицируйте параметр shared_preload_libraries в конфигурационном файле postgres.conf: |
| 46 | +``` |
| 47 | +shared_preload_libraries = 'pg_pathman' |
| 48 | +``` |
| 49 | +Для вступления изменений в силу потребуется перезагрузка сервера PostgreSQL. |
| 50 | + |
| 51 | +##Функции pg_pathman |
| 52 | + |
| 53 | +###Создание секций |
| 54 | +``` |
| 55 | +create_hash_partitions( |
| 56 | + relation TEXT, |
| 57 | + attribute TEXT, |
| 58 | + partitions_count INTEGER) |
| 59 | +``` |
| 60 | +Выполняет HASH-секционирование таблицы`relation` по целочисленному полю`attribute`. Создает`partitions_count` дочерних секций, а также триггер на вставку. Данные из родительской таблицы не копируются автоматически в дочерние. Миграцию данных можно выполнить с помощью функции`partition_data()` (см. ниже), либо вручную. |
| 61 | + |
| 62 | +``` |
| 63 | +create_range_partitions( |
| 64 | + relation TEXT, |
| 65 | + attribute TEXT, |
| 66 | + start_value ANYELEMENT, |
| 67 | + interval ANYELEMENT, |
| 68 | + premake INTEGER) |
| 69 | +``` |
| 70 | +Выполняет RANGE-секционирование таблицы`relation` по полю`attribute`. Аргумент`start_value` задает начальное значение,`interval` -- диапазон значений внутри одной секции,`premake` -- количество заранее создаваемых секций (если 0, то будет создана единственная секция). |
| 71 | +``` |
| 72 | +create_range_partitions( |
| 73 | + relation TEXT, |
| 74 | + attribute TEXT, |
| 75 | + start_value ANYELEMENT, |
| 76 | + interval INTERVAL, |
| 77 | + premake INTEGER) |
| 78 | +``` |
| 79 | +Аналогично предыдущей с тем лишь отличием, что данная функция предназначена для секционирования по полю типа`DATE` или`TIMESTAMP`. |
| 80 | + |
| 81 | +###Утилиты |
| 82 | +``` |
| 83 | +partition_data(parent text) |
| 84 | +``` |
| 85 | +Копирует данные из родительской таблицы`parent` в дочерние секции. |
| 86 | +``` |
| 87 | +create_hash_update_trigger(parent TEXT) |
| 88 | +``` |
| 89 | +Создает триггер на UPDATE для HASH секций. По-умолчанию триггер на обновление данных не создается, т.к. это создает дополнительные накладные расходы. Триггер полезен только в том случае, когда меняется значение ключевого аттрибута. |
| 90 | +``` |
| 91 | +create_hash_update_trigger(parent TEXT) |
| 92 | +``` |
| 93 | +Аналогично предыдущей, но для RANGE секций. |
| 94 | + |
| 95 | +###Управление секциями |
| 96 | +``` |
| 97 | +split_range_partition(partition TEXT, value ANYELEMENT) |
| 98 | +``` |
| 99 | +Разбивает RANGE секцию`partition` на две секции по значению`value`. |
| 100 | +``` |
| 101 | +merge_range_partitions(partition1 TEXT, partition2 TEXT) |
| 102 | +``` |
| 103 | +Объединяет две смежные RANGE секции. Данные из`partition2` копируются в`partition1`, после чего секция`partition2` удаляется. |
| 104 | +``` |
| 105 | +append_partition(p_relation TEXT) |
| 106 | +``` |
| 107 | +Добавляет новую секцию в конец списка секций. Диапазон значений устанавливается равным последней секции. |
| 108 | +``` |
| 109 | +prepend_partition(p_relation TEXT) |
| 110 | +``` |
| 111 | +Добавляет новую секцию в начало списка секций. |
| 112 | +``` |
| 113 | +disable_partitioning(relation TEXT) |
| 114 | +``` |
| 115 | +Отключает механизм секционирования`pg_pathman` для заданной таблицы и удаляет триггер на вставку. При этом созданные ранее секции остаются без изменений. |
| 116 | + |
| 117 | +##Примеры использования |
| 118 | +###HASH |
| 119 | +Рассмотрим пример секционирования таблицы, используя HASH-стратегию на примере таблицы. |
| 120 | +``` |
| 121 | +CREATE TABLE hash_rel ( |
| 122 | + id SERIAL PRIMARY KEY, |
| 123 | + value INTEGER); |
| 124 | +INSERT INTO hash_rel (value) SELECT g FROM generate_series(1, 10000) as g; |
| 125 | +``` |
| 126 | +Разобьем таблицу`hash_rel` на 100 секций по полю`value`: |
| 127 | +``` |
| 128 | +SELECT create_hash_partitions('hash_rel', 'value', 100); |
| 129 | +``` |
| 130 | +Перенесем данные из родительской таблицы в дочерние секции. |
| 131 | +``` |
| 132 | +SELECT partition_data('hash_rel'); |
| 133 | +``` |
| 134 | +###RANGE |
| 135 | +Пример секционирования таблицы с использованием стратегии RANGE. |
| 136 | +``` |
| 137 | +CREATE TABLE range_rel ( |
| 138 | + id SERIAL PRIMARY KEY, |
| 139 | + dt TIMESTAMP); |
| 140 | +INSERT INTO range_rel (dt) SELECT g FROM generate_series('2010-01-01'::date, '2015-12-31'::date, '1 day') as g; |
| 141 | +``` |
| 142 | +Разобьем таблицу на 60 секций так, чтобы каждая секция содержала данные за один месяц: |
| 143 | +``` |
| 144 | +SELECT create_range_partitions('range_rel', 'dt', '2010-01-01'::date, '1 month'::interval, 59); |
| 145 | +``` |
| 146 | +>Значение`premake` равно 59, а не 60, т.к. 1 секция создается независимо от значения`premake` |
| 147 | +
|
| 148 | +Перенесем данные из родительской таблицы в дочерние секции. |
| 149 | +``` |
| 150 | +SELECT partition_data('range_rel'); |
| 151 | +``` |
| 152 | +Объединим секции первые две секции: |
| 153 | +``` |
| 154 | +SELECT merge_range_partitions('range_rel_1', 'range_rel_2'); |
| 155 | +``` |
| 156 | +Разделим первую секцию на две по дате '2010-02-15': |
| 157 | +``` |
| 158 | +SELECT split_range_partition('range_rel_1', '2010-02-15'::date); |
| 159 | +``` |
| 160 | +Добавим новую секцию в конец списка секций: |
| 161 | +``` |
| 162 | +SELECT append_partition('range_rel') |
| 163 | +``` |