Партиционирование в Greenplum 7: что нового
Greenplum 7 - это важное событие для мира партицированных таблиц. Помимо ряда важных улучшений и исправлений, это первая версия Greenplum, которая совместима с партицированными талицами из мира PostgreSQL.
Немного предыстории: до появления PostgreSQL 10 партиционирование таблиц в PostgreSQL могло быть выполнено в очень ограниченном виде, что, по сути, являлось всего лишь вариантом наследования таблиц. В PostgreSQL 10 и более поздних версиях пользователи могут использовать декларативный синтаксис для определения своей парадигмы партиционирования. Например:
CREATE TABLEsales (idint,date date, amtdecimal(10,2))PARTITION BY RANGE(date);
С другой стороны, партиционирование таблиц в том виде, в котором мы знаем его сегодня, существует в Greenplum уже давно. В рамках слияния с PostgreSQL 12 Greenplum 7 вобрал в себя весь синтаксис PostgreSQL для партиционирования таблиц, при этом сохранив поддержку унаследованного синтаксиса Greenplum. В результате у Greenplum 7 появился шанс взять самое лучшее из обоих миров.
В этой статье речь пойдет в основном о различиях между Greenplum 7 и Greenplum 6. Поэтому, если Вы - совсем новичок в Greenplum (или даже в PostgreSQL) и никогда раньше не использовали разметку в Greenplum 6, то Вам, скорее всего, будет интереснее прочитать статьи, ссылки на которые Вы найдете в конце данного поста.
Итак, приступим к делу.
1. Новый синтаксис
Прежде чем мы начнем рассматривать новинки Greenplum 7, давайте разберемся в том, что осталось по-прежнему: поскольку в PostgreSQL используется то же объявление для ключа разделения, что и в Greenplum, а именно предложение PARTITION BY, оно осталось таким же и в Greenplum 7. Более того, в PostgreSQL также есть стратегии партиционирования RANGE и LIST, которые Greenplum продолжает поддерживать в своих новых версиях.
Однако есть одно важное отличие, которое заключается в том, что Greenplum 7 теперь позволяет определять таблицу с разделами без определения дочерних разделов, например:
CREATE TABLEsales (idint,date date, amtdecimal(10,2))DISTRIBUTEDBY(id)PARTITION BY RANGE(date);
Команда CREATE TABLE ... PARTITION BY, приведенная выше, создает только родительскую таблицу с разделами без дочерних разделов. Дочерние разделы в Greenplum 7 являются таблицами первого класса и могут создаваться с помощью отдельных команд, которые будут рассмотрены позже.
1.1. Стратегия партиционирования по хэш-значению
Помимо существующих стратегий RANGE и LIST Greenplum 7 также поддерживает партиционирование по хэш-значению. Оно работает так же, как и в PostgreSQL. Пример:
-- создание таблицы, разделенной по хэш-значению CREATE TABLEsales_by_hour (idint,date date,hour int, amtdecimal(10,2))DISTRIBUTEDBY(id)PARTITION BYHASH (hour);-- каждый модуль хэш-раздела должен быть в раз больше следующего модуля CREATE TABLEsales_by_hour_1PARTITION OFsales_by_hourFOR VALUES WITH(MODULUS24, REMAINDER0);CREATE TABLEsales_by_hour_2PARTITION OFsales_by_hourFOR VALUES WITH(MODULUS24, REMAINDER1);CREATE TABLEsales_by_hour_3PARTITION OFsales_by_hourFOR VALUES WITH(MODULUS24, REMAINDER2);......
1.2. Новое партиционирование DDL
Итак, основными дополнениями в Greenplum 7 являются новое DDL партиционирование, такое же, как и в PostgreSQL. Подробнее о нем мы поговорим позже, а пока давайте посмотрим, что оно делает:
(1) CREATE TABLE PARTITION OF
Для создания новой таблицы и добавления ее в качестве нового дочернего раздела:
CREATE TABLEjan_salesPARTITION OFsalesFOR VALUES FROM('2023-01-01')TO('2023-02-01');
(2) ALTER TABLE ATTACH PARTITION
Для добавления существующей таблицы в качестве нового дочернего раздела:
CREATE TABLEfeb_sales (LIKEsales);ALTER TABLEsales ATTACHPARTITIONfeb_salesFOR VALUES FROM('2024-02-01')TO('2024-03-01');
(3) ALTER TABLE DETACH PARTITION
Для удаления таблицы из иерархии партиционирования (без сброса самой таблицы):
ALTER TABLEsales DETACHPARTITIONjan_sales;
1.3. Новый каталог и вспомогательные функции
Теперь информация касательно партиционирования хранится в каталоге pg_partitioned_table, а также в дополнительных полях в pg_class (relispartition и relpartbound). Вы также можете воспользоваться следующими вспомогательными функциями: pg_partition_ancestors(rel)), pg_partition_root(rel) and pg_partition_tree(rel).
В связи с этим исчезли старые таблицы каталогов, связанные с разделением, pg_partition и pg_partition_rule, а также функция pg_partition_def().
-- новый каталог для разделенных таблиц select * frompg_partitioned_tablewherepartrelid= 'sales'::regclass;partrelid|partstrat|partnatts|partdefid|partattrs|partclass|partcollation|partexprs-----------+-----------+-----------+-----------+-----------+-----------+---------------+----------- 156181 |r| 1 | 0 | 2 | 3122 | 0 |(1 row)-- Вспомогательная процедура для проверки иерархии разделов selectpg_partition_tree('sales');pg_partition_tree-----------------------------------(sales,,f,0)(jan_sales,sales,t,1)(sales_1_prt_feb_sales,sales,t,1)(sales_1_prt_mar_sales,sales,t,1)(4 rows)
2. Новые рабочие процессы
Для существующих пользователей Greenplum одним из наиболее важных моментов, которые следует усвоить о новых синтаксисах, является то, что они предоставляют альтернативные рабочие процессы. Обратите внимание, что это не означает, что новый синтаксис сложнее. На самом деле, все наоборот: новый синтаксис более простой в использовании. В большинстве случаев реализовать определенные парадигмы партиционирования стало гораздо проще. Ниже мы рассмотрим несколько примеров.
Однако стоит отметить, что новые синтаксисы не заменяют старые. Если человек хорошо понимает обе группы синтаксисов, особенно их различия, он всегда сможет сделать правильный и наиболее оптимальный выбор.
Создайте дочерний раздел вместе с родительским
Greenplum удалось создать дочерние разделы вместе с родительской таблицей. Например:
CREATE TABLEsales (idint,date date, amtdecimal(10,2))DISTRIBUTEDBY(id)PARTITION BY RANGE(date)(PARTITIONjan_salesSTART('2023-01-01')END('2023-02-01'),PARTITIONfeb_salesSTART('2023-02-01')END('2023-03-01'),DEFAULT PARTITIONother_sales);
В PostgreSQL нет аналогичной команды. Вместо этого сначала создается родительская таблица с разделами, а затем отдельно добавляются дочерние разделы:
CREATE TABLEsales (idint,date date, amtdecimal(10,2))DISTRIBUTEDBY(id)PARTITION BY RANGE(date);-- Add partition individually CREATE TABLEjan_salesPARTITION OFsalesFOR VALUES FROM('2023-01-01')TO('2023-02-01');CREATE TABLEfeb_salesPARTITION OFsalesFOR VALUES FROM('2023-02-01')TO('2023-03-01');CREATE TABLEother_salesPARTITION OFsalesDEFAULT;
Создание и добавление дочерних разделов
ALTER TABLE ... ADD PARTITION и CREATE TABLE ... PARTITION OF одновременно создают и добавляют новую дочернюю таблицу.
Однако, поскольку CREATE TABLE PARTITION OF - это команда CREATE TABLE, в отличие от ADD PARTITION, которая является подкомандой ALTER TABLE, в CREATE TABLE ... PARTITION OF можно указать больше параметров создания таблиц. ADD PARTITION в общем случае наследует только то, что есть у родительской таблицы.
CREATE TABLE также позволяет Вам указать большое количество параметров.
CREATE TABLEjan_salesPARTITION OFsalesFOR VALUES FROM('2023-01-01')TO('2023-02-01')USINGao_rowWITH(compresstype=zlib);-- ADD PARTITION создает разделение, но параметры нужно указывать в отдельных командах ALTER TABLEsalesADD PARTITIONjan_salesSTART('2023-01-01')END('2023-02-01');ALTER TABLEsales_1_prt_jan_salesSETACCESSMETHODao_row;ALTER TABLEsales_1_prt_jan_salesSET WITH(compresstype=zlib);
Поменяйте существующий раздел на другую таблицу
Команда EXCHANGE PARTITION в устаревшем синтаксисе меняет местами существующий дочерний раздел с обычной таблицей. В новом синтаксисе для достижения того же самого результата достаточно использовать DETACH PARTITION и ATTACH PARTITION.
-- 1. Using EXCHANGE PARTITION ALTER TABLEsales EXCHANGE jan_salesWITH TABLEjan_sales_new;-- 2. Using ATTACH PARTITION ALTER TABLEsales DETACHPARTITIONjan_sales;ALTER TABLEsales ATTACHPARTITIONjan_sales_new;
Удаление дочернего раздела
Раньше было довольно сложно удалить дочерний раздел без сброса таблицы. В устаревшей версии Greenplum есть команда ALTER TABLE ... DROP PARTITION, которая также уничтожает таблицу. Сначала нужно было создать фиктивную таблицу, поменять раздел, который Вы хотите удалить, на фиктивную таблицу, а затем сбросить помененный раздел. Теперь эту задачу можно выполнить с помощью команды ALTER TABLE ... DETACH PARTITION:
-- Длительные операции по удалению нежелательного дочернего раздела без его сброса: CREATE TABLEdummy (LIKEsales);ALTER TABLEsales EXCHANGEPARTITIONarchived_salesWITHdummy;ALTER TABLEsalesDROP PARTITIONarchived_sales;DROP TABLEdummy;-- Теперь достаточно "DETACH PARTITION": ALTER TABLEsales DETACHPARTITIONarchived_sales;
Как Вы уже могли заметить, ALTER TABLE ... DROP PARTITION по сути выполняет ту же задачу, что и DROP TABLE. Тогда почему ALTER TABLE ... DROP PARTITION все еще существует? Потому что эти два синтаксиса по-разному относятся к имени таблицы. См. раздел «Имя раздела vs имя таблицы».
Разделение дочернего раздела
SPLIT PARTITION - это специальная команда, выполняющая достаточно интересную задачу: партиционирование листа раздела и создание из него двух разделов. Это еще один синтаксис, не имеющий простой альтернативы в PostgreSQL. Вам придется вручную отсоединять раздел и добавлять два раздела, соответствующих разделенным диапазонам. Но есть и хорошая новость: если Вы не хотите выполнять эти действия, Вы можете просто использовать SPLIT PARTITION.
Вы также можете разделить раздел по умолчанию, что является достаточно распространенной практикой, когда данные сначала вставляются в раздел по умолчанию, а затем добавляются в объявленные разделы. Но стоит отметить, что если раздел по умолчанию не содержит никаких данных, то для добавления новых разделов лучше использовать ATTACH PARTITION, поскольку ATTACH PARTITION имеет менее ограниченный тип блокировки (см. подробнее в разделе 3.3). Если раздел по умолчанию содержит данные, то, скорее всего, ATTACH PARTITION будет выполнен с ошибкой, поскольку данные в разделе по умолчанию нарушают ограничения нового раздела.
Сравнительная таблица:
|
Действие |
Традиционный синтаксис |
Альтернатива |
|---|---|---|
|
Создание дочернего раздела вместе с родительским |
|
|
|
Создание и добавление раздела |
|
|
|
Смена дочерних разделов на обычные таблицы |
|
|
|
Удаление разделов |
|
|
|
Разбиение разделов |
|
|
В целом, новый синтаксис менее специализирован, но имена эта универсальность и позволит Вам легче реализовывать более сложные иерархии.
3. Другие различия
3.1. Имя раздела vs Имя таблицы
Исторически сложилось так, что в DDL-файлах Greenplum для разделов указывается так называемое «имя раздела», которое не совпадает с именем таблицы. В основном, Greenplum добавляет к имени дочерней таблицы специальный префикс, соответствующий родительскому разделу. Например, при использовании унаследованного синтаксиса для добавления разделов:
CREATE TABLEsales (idint,date date, amtdecimal(10,2))DISTRIBUTEDBY(id)PARTITION BY RANGE(date)(PARTITIONjan_salesSTART('2023-01-01')END('2023-02-01'));ALTER TABLEsalesADD PARTITIONfeb_salesSTART('2023-02-01')END('2023-03-01');\d+salesPartitionedtable"public.sales"Column |Type| Collation |Nullable| Default |Storage|Stats target|Description--------+---------------+-----------+----------+---------+---------+--------------+-------------id| integer | | | |plain| | date | date | | | |plain| |amt| numeric(10,2)| | | |main| | Partitionkey:RANGE(date)Partitions: sales_1_prt_feb_salesFOR VALUES FROM('2023-02-01')TO('2023-03-01'),sales_1_prt_jan_salesFOR VALUES FROM('2023-01-01')TO('2023-02-01')Distributedby: (id)Accessmethod: heap
Как показано выше, оба дочерних раздела имеют префикс sales_1_prt_ к именам, которые мы для них указали (jan_sales и feb_sales). В отличие от этого, новый синтаксис рассматривает указанные имена как имя таблицы:
CREATE TABLEsales (idint,date date, amtdecimal(10,2))DISTRIBUTEDBY(id)PARTITION BY RANGE(date);CREATE TABLEjan_salesPARTITION OFsalesFOR VALUES FROM('2023-01-01')TO('2023-02-01');CREATE TABLEfeb_sales (LIKEsales);ALTER TABLEsales ATTACHPARTITIONfeb_salesFOR VALUES FROM('2024-02-01')TO('2024-03-01');\d+salesPartitionedtable"public.sales"Column |Type| Collation |Nullable| Default |Storage|Stats target|Description--------+---------------+-----------+----------+---------+---------+--------------+-------------id| integer | | | |plain| | date | date | | | |plain| |amt| numeric(10,2)| | | |main| | Partitionkey:RANGE(date)Partitions: feb_salesFOR VALUES FROM('2024-02-01')TO('2024-03-01'),jan_salesFOR VALUES FROM('2023-01-01')TO('2023-02-01')Distributedby: (id)Accessmethod: heap
Однако это различие сохраняется между старым и новым синтаксисами. Например, нам не нужно указывать префикс при использовании старого синтаксиса DROP PARTITION. Но если мы используем DETACH PARTITION для выполнения того же действия, то это необходимо:
Предположим, у нас есть разделение 'sales' с дочерними разделениями по Февралю и Январю, созданные с помощью традиционного синтаксиса.
\d+salesPartitionedtable"public.sales"Column |Type| Collation |Nullable| Default |Storage|Stats target|Description--------+---------------+-----------+----------+---------+---------+--------------+-------------id| integer | | | |plain| | date | date | | | |plain| |amt| numeric(10,2)| | | |main| | Partitionkey:RANGE(date)Partitions: sales_1_prt_feb_salesFOR VALUES FROM('2023-02-01')TO('2023-03-01'),sales_1_prt_jan_salesFOR VALUES FROM('2023-01-01')TO('2023-02-01')Distributedby: (id)Accessmethod: heap-- сбрасываем разделение 'jan_sales', без проблем ALTER TABLEsalesDROP PARTITIONjan_sales;-- сбросить не смогли, так как таблицы 'feb_sales'не существует ALTER TABLEsales DETACHPARTITIONfeb_sales;ERROR: relation "feb_sales" doesnotexist-- необходимо указать полное имя, используя DETACH ALTER TABLEsales DETACHPARTITIONsales_1_prt_feb_sales;
Поэтому настоятельно рекомендуется, по крайней мере, для одной и той же разбитой на разделы таблицы, использовать либо новый, либо старый синтаксис (чтобы избежать двусмысленности в названиях).
Для удобства давайте воспользуемся таблицей, чтобы наглядно увидеть разницу:
|
Синтаксис |
Добавляем префикс или нет |
Традиционный или новый |
|---|---|---|
|
|
да |
традиционный |
|
|
да |
традиционный |
|
|
да |
традиционный |
|
|
да |
традиционный |
|
|
да |
традиционный |
|
|
да |
традиционный |
|
|
нет |
новый |
|
|
нет |
новый |
|
|
нет |
новый |
3.2. START|END vs FROM|TO
На примерах SQL, которые были показаны ранее, Вы, вероятно, заметили, что в новом синтаксисе также присутствуют различные ключевые слова для определения раздела диапазона: FOR VALUES FROM ... TО ..... В унаследованном синтаксисе Greenplum есть START ... END (). Оба определения будут работать только в соответствующих старых или новых DDL:
ALTER TABLEsalesADD PARTITIONmar_salesSTART('2023-03-01')END('2023-03-31');CREATE TABLEmar_salesPARTITION OFsalesFOR VALUES FROM('2023-03-01')TO('2023-03-31');-- Это не сработает: ALTER TABLEsalesADD PARTITIONmar_salesFOR VALUES FROM('2023-03-01')TO('2023-03-31');CREATE TABLEmar_salesPARTITION OFsalesSTART('2023-03-01')END('2023-03-31');
В устаревшем синтаксисе также есть ключевые слова EXCLUSIVE и INCLUSIVE для разделения диапазона. В PostgreSQL этого нет, начальная граница всегда инклюзивная, а конечная эксклюзивная. Greenplum 7 продолжает поддерживать EXCLUSIVE|INCLUSIVE, неявно добавляя «+1» к начальному или конечному диапазону. В результате они теперь работают только для типов данных, имеющих подходящий оператор «+», таких как integer и timestamp, но не float или text.
ALTER TABLEsalesADD PARTITIONmar_salesSTART('2023-03-01') INCLUSIVEEND('2023-03-31') INCLUSIVE;
3.3. блокировка с меньшим ограничением в ATTACH PARTITION
Поведение блокировки в разделе заслуживает отдельной статьи, но одна из самых важных вещей, о которой должны знать пользователи, - это легкая блокировка с помощью ATTACH PARTITION. ATTACH PARTITION требует только Share Update Exclusive Lock на родительской таблице. Этот тип блокировки является относительно легким и не конфликтует со многими другими запросами, включая SELECT, INSERT и иногда UPDATE.
Это означает, что только в Greenplum 7 стало возможным добавлять разделы в иерархию разделов, не нарушая при этом выполнения многих запросов к разделу (и наоборот). Например:
-- Предположим, что мы имеем дело с долговременной вставкой INSERT INTOsalesSELECT * FROMext_sales_data;-- Такое решение будет заблокировано: ALTER TABLEsalesADD PARTITIONmarch_salesSTART('2023-03-01')END('2023-04-01');-- А это сработает: ALTER TABLEsales ATTACHPARTITIONmarch_salesFOR VALUES FROM('2023-03-01')TO('2023-04-01');
Чтобы увидеть все это на практике, просмотрите это демо-видео.
В этой таблице показаны блокировки во время различных DDL разделов.
|
Команда |
Самый серьезный тип блокировки |
Разрешенный запрос |
|---|---|---|
|
|
AccessExclusiveLock |
нет |
|
|
AccessExclusiveLock |
нет |
|
|
AccessExclusiveLock |
нет |
|
|
AccessExclusiveLock |
нет |
|
|
AccessExclusiveLock |
нет |
|
|
ShareUpdateExclusiveLock |
SELECT, INSERT, UPDATE* |
|
|
AccessExclusiveLock |
нет |
* Когда включен gp_enable_global_deadlock_detector, а таблица не оптимизирована для добавления.
3.4. Ограничения раздела и ограничения проверки
Границы разделов больше не представлены в виде ограничений CHECK. Теперь ограничения разделов - это совершенно отдельная концепция.
-- одинаковое определение разделов в Greenplum 6 и 7 -- Greenplum 6\d+sales_1_prt_jan_salesTable"public.sales_1_prt_jan_sales"Column |Type|Modifiers|Storage|Stats target|Description--------+---------------+-----------+---------+--------------+-------------id| integer | |plain| | date | date | |plain| |amt| numeric(10,2)| |main| | Checkconstraints:"sales_1_prt_jan_sales_check"CHECK(date >= '2023-01-01'::date AND date < '2023-02-01'::date)Inherits: salesDistributedby: (id)-- Greenplum 7\d+jan_salesTable"public.jan_sales"Column |Type| Collation |Nullable| Default |Storage|Stats target|Description--------+---------------+-----------+----------+---------+---------+--------------+-------------id| integer | | | |plain| | date | date | | | |plain| |amt| numeric(10,2)| | | |main| | Partition of: salesFOR VALUES FROM('2023-01-01')TO('2023-02-01')Partition constraint: ((date IS NOT NULL)AND(date >= '2023-01-01'::date)AND(date < '2023-02-01'::date))Distributedby: (id)
3.5. PARTITION BY для нескольких столбцов
Разбиение списков на несколько столбцов больше не поддерживается. В качестве обходного пути можно создать составной тип и использовать его в качестве ключа разметки, например:
-- Это больше не работает: CREATE TABLEfoo (aint, bint, cint)PARTITION BYlist (b,c);ERROR: cannot use "list"partitionstrategywithmore thanone column -- Альтернатива: CREATETYPE partkeyas(bint, cint);CREATE TABLEfoo (aint, bint, cint)PARTITION BYLIST ((row(b, c)::partkey));
Полезные ссылки
касательно использования партиционирования PostgreSQL:
- Документация PostgreSQL касательно партиционирования таблиц: https://www.postgresql.org/docs/12/ddl-partitioning.html
- Руководство по партиционированию PostgreSQL: https://www.youtube.com/watch?v=oJj-pltxBUM
- Гид по партиционированию таблиц в PostgreSQL для новичков: https://medium.com/swlh/beginners-guide-to-table-partitioning-in-postgresql-5a014229042




