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