[Перевод] Партицирование в Greenplum 7: что нового
Greenplum 7 — первая версия СУБД, совместимая с секционированными таблицами из PostgreSQL: World DB.
Небольшая предыстория: до PostgreSQL 10 партицирование в PostgreSQL выполнялось в очень ограниченной форме и по сути, было просто вариантом наследования таблиц. Начиная с PostgreSQL 10, мы можем использовать декларативный синтаксис для определения парадигмы разделов.
В рамках слияния с PostgreSQL 12 Greenplum 7 вобрал в себя весь синтаксис PostgreSQL для разбиения таблиц, сохранив при этом синтаксис Greenplum. В результате у Greenplum 7 есть шанс взять лучшее из обоих миров — что именно, обсудим в этой статье.
Новый синтаксис
Прежде чем начнём разбирать, что нового в Greenplum 7, предлагаю обсудить, что осталось без изменений.
PostgreSQL имеет то же объявление для ключа партицирования, что и Greenplum, — PARTITION BY
. Оно же присутствует в Greenplum 7. Также у PostgreSQL есть стратегии разбиения RANGE
и LIST
, которые Greenplum продолжает поддерживать в новой версии. Но здесь есть важное отличие: Greenplum 7 позволяет определять секционированную таблицу без определения дочерних разделов, например:
CREATE TABLE sales (id int, date date, amt decimal(10,2))
DISTRIBUTED BY (id)
PARTITION BY RANGE (date);
Команда CREATE TABLE ... PARTITION BY
создаёт только родительскую секционированную таблицу без каких-либо дочерних разделов. Дочерние разделы в Greenplum 7 — это таблицы первого класса, которые можно создавать с помощью отдельных команд (их мы рассмотрим в следующих разделах).
«DWH на основе GreenPlum»
Стратегия разделения хеша
Помимо существующих стратегий партицирования RANGE
и LIST
Greenplum 7 поддерживает партицирование HASH
. Эти стратегии работают так же, как и в PostgreSQL. Пример создания хэш-секционированной таблицы и её дочерних разделов:
-- create a tabled partitioned by hash strategy
CREATE TABLE sales_by_hour (id int, date date, hour int, amt decimal(10,2))
DISTRIBUTED BY (id)
PARTITION BY HASH (hour);
-- every hash partition modulus must be a factor of the next larger modulus
CREATE TABLE sales_by_hour_1 PARTITION OF sales_by_hour FOR VALUES WITH (MODULUS 24, REMAINDER 0);
CREATE TABLE sales_by_hour_2 PARTITION OF sales_by_hour FOR VALUES WITH (MODULUS 24, REMAINDER 1);
CREATE TABLE sales_by_hour_3 PARTITION OF sales_by_hour FOR VALUES WITH (MODULUS 24, REMAINDER 2);
......
Новый DDL
Обсудим основные дополнения Greenplum 7, связанные с DDL.
CREATE TABLE PARTITION OF. Как создать новую таблицу и добавить её в качестве дочернего раздела:
CREATE TABLE jan_sales PARTITION OF sales
FOR VALUES FROM ('2023-01-01') TO ('2023-02-01');
ALTER TABLE ATTACH PARTITION. Как добавить существующую таблицу в качестве нового дочернего раздела:
CREATE TABLE feb_sales (LIKE sales);
ALTER TABLE sales ATTACH PARTITION feb_sales
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
ALTER TABLE DETACH PARTITION. Как удалить таблицу из иерархии партицирования (без удаления самой таблицы):
ALTER TABLE sales DETACH PARTITION jan_sales;
Новый каталог и вспомогательные функции
Информация о разделах теперь хранится в каталоге pg_partitioned_table
и в дополнительных полях pg_class
(relispartition и relpartbound). Также мы можем использовать новые вспомогательные функции из основной ветки разработки: pg_partition_ancestors(rel))
, pg_partition_root(rel)
и pg_partition_tree(rel)
. При этом старые таблицы каталогов, связанные с партицированием, pg_partition и pg_partition_rule
ушли, как и функция pg_partition_def()
.
-- New catalog for partitioned tables
select * from pg_partitioned_table where partrelid = 'sales'::regclass;
partrelid | partstrat | partnatts | partdefid | partattrs | partclass | partcollation | partexprs
-----------+-----------+-----------+-----------+-----------+-----------+---------------+-----------
156181 | r | 1 | 0 | 2 | 3122 | 0 |
(1 row)
-- A convenient helper routine to check the partition hierarchy
select pg_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)
Новые воркфлоу
Одна из самых важных вещей, которую нужно знать о новых синтаксисах, заключается в том, что они предоставляют рабочие процессы, альтернативные тем, что раньше делали легаси-команды Greenplum. И это не значит, что новый синтаксис стал более сложным. Всё наоборот: новые синтаксисы более простые в использовании. В большинстве случаев с их помощью проще реализовать определённые парадигмы разделов, включая выполнение задач, которые решали старые команды. Ниже мы приведём несколько примеров.
Но важно понимать, что новые синтаксисы не заменяют старые. Поэтому вы должны хорошо понимать обе группы синтаксисов (особенно их различия), чтобы подбирать наиболее подходящие команды под свои нужды.
Создание дочернего раздела вместе с родительским
Greenplum может создавать дочерние разделы вместе с родительской таблицей:
CREATE TABLE sales (id int, date date, amt decimal(10,2))
DISTRIBUTED BY (id)
PARTITION BY RANGE (date)
(PARTITION jan_sales START ('2023-01-01') END ('2023-02-01'),
PARTITION feb_sales START ('2023-02-01') END ('2023-03-01'),
DEFAULT PARTITION other_sales);
В PostgreSQL нет аналогичной команды. Вместо этого сначала создаётся родительская секционированная таблица, а затем отдельно добавляются дочерние секции:
CREATE TABLE sales (id int, date date, amt decimal(10,2))
DISTRIBUTED BY (id)
PARTITION BY RANGE (date);
-- Add partition individually
CREATE TABLE jan_sales PARTITION OF sales FOR VALUES FROM ('2023-01-01') TO ('2023-02-01');
CREATE TABLE feb_sales PARTITION OF sales FOR VALUES FROM ('2023-02-01') TO ('2023-03-01');
CREATE TABLE other_sales PARTITION OF sales DEFAULT;
Создание и добавление дочерних разделов
ALTER TABLE... ADD PARTITION
и CREATE TABLE... PARTITION OF
одновременно создают и добавляют новую дочернюю таблицу. Однако, поскольку CREATE TABLE PARTITION OF
является командой CREATE TABLE
, в отличие от ADD PARTITION
, которая является подкомандой ALTERTABLE
, с помощью CREATE TABLE ... PARTITION OF
можно указать дополнительные параметры создания таблиц. ADD PARTITION
обычно наследует только то, что есть в родительской таблице.
-- CREATE TABLE allows you to specify more options
CREATE TABLE jan_sales PARTITION OF sales FOR VALUES FROM ('2023-01-01') TO ('2023-02-01')
USING ao_row
WITH(compresstype=zlib);
-- ADD PARTITION does the creation but have to specify options in separate commands
ALTER TABLE sales ADD PARTITION jan_sales START ('2023-01-01') END ('2023-02-01');
ALTER TABLE sales_1_prt_jan_sales SET ACCESS METHOD ao_row;
ALTER TABLE sales_1_prt_jan_sales SET WITH (compresstype=zlib);
Поменять местами существующий раздел с другой таблицей
Команда EXCHANGE PARTITION
в легаси-синтаксисе заменяет существующий дочерний раздел обычной таблицей. С новым синтаксисом можно просто использовать DETACH PARTITION
и ATTACHPARTITION
, чтобы добиться того же. Внутри Greenplum 7 выполняется DETACH
, а затем ATTACHPARTITION
с некоторой заменой имен.
-- 1. Using EXCHANGE PARTITION
ALTER TABLE sales EXCHANGE jan_sales WITH TABLE jan_sales_new;
-- 2. Using ATTACH PARTITION
ALTER TABLE sales DETACH PARTITION jan_sales;
ALTER TABLE sales ATTACH PARTITION jan_sales_new;
Удалить дочерний раздел
Раньше было довольно сложно удалить дочерний раздел, не удаляя таблицу. В устаревшем Greenplum есть только команда ALTER TABLE ... DROP PARTITION
, которая также удаляет таблицу. Сначала вам нужно создать фиктивную таблицу, заменить раздел, который вы хотите удалить, на фиктивную таблицу, а затем удалить замененный раздел. Теперь с помощью ALTER TABLE ... DETACH PARTITION
можно легко выполнить ту же задачу:
-- Lengthy operations to remove an unwanted child partition w/o dropping it:
CREATE TABLE dummy (LIKE sales);
ALTER TABLE sales EXCHANGE PARTITION archived_sales WITH dummy;
ALTER TABLE sales DROP PARTITION archived_sales;
DROP TABLE dummy;
-- Can just "DETACH PARTITION" now:
ALTER TABLE sales DETACH PARTITION archived_sales;
ALTER TABLE ... DROP PARTITION
по сути выполняет ту же задачу, что и DROP TABLE
. Тогда почему ALTER TABLE ... DROP PARTITION
все ещё существует? Это связано с тем, что два синтаксиса по-разному обрабатывают имя таблицы.
Разделить дочерний раздел
SPLIT PARTITION
— специальная команда, которая выполняет интересную задачу: разделяет конечный раздел и создаёт из него два раздела. Это ещё один синтаксис без простой альтернативы в PostgreSQL. Вы должны вручную отсоединить раздел и добавить два раздела, соответствующих разделённым диапазонам.
Кейс | Легаси-синтаксис | Альтернативный новый/ существующий синтаксис |
Создать дочерний раздел вместе с родительским | CREATE TABLE … (PARTITION …) | CREATE TABLE … PARTITION BY and CREATE TABLE … PARTITION OF |
Создать и добавить раздел | ALTER TABLE … ADD PARTITION | CREATE TABLE … PARTITION OF |
Замените существующий дочерний раздел обычной таблицей | ALTER TABLE … EXCHANGE PARTITION | ALTER TABLE … DETACH PARTITIONand ATTACH PARTITION |
Удалить раздел | ALTER TABLE … DROP PARTITION | DROP TABLE |
Разделить раздел | ALTER TABLE … SPLIT PARTITION | DETACH partition and ATTACH new ones separately |
Другие отличия
Имя раздела vs. имя таблицы
Исторически сложилось так, что имя, которое указывается в DDL Greenplum, является так называемым «именем раздела» и не совпадает с именем таблицы. По сути, Greenplum добавляет к имени дочерней таблицы специальный префикс, соответствующий родительскому разделу. Например, при использовании устаревшего синтаксиса для добавления разделов:
CREATE TABLE sales (id int, date date, amt decimal(10,2))
DISTRIBUTED BY (id)
PARTITION BY RANGE (date)
(PARTITION jan_sales START ('2023-01-01') END ('2023-02-01'));
ALTER TABLE sales ADD PARTITION feb_sales START ('2023-02-01') END ('2023-03-01');
\d+ sales
Partitioned table "public.sales"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------------+-----------+----------+---------+---------+--------------+-------------
id | integer | | | | plain | |
date | date | | | | plain | |
amt | numeric(10,2) | | | | main | |
Partition key: RANGE (date)
Partitions: sales_1_prt_feb_sales FOR VALUES FROM ('2023-02-01') TO ('2023-03-01'),
sales_1_prt_jan_sales FOR VALUES FROM ('2023-01-01') TO ('2023-02-01')
Distributed by: (id)
Access method: heap
У дочерних разделов есть префикс sales_1_prt_
к именам, которые мы для них указываем (jan_sales
и feb_sales
). Новый синтаксис просто обрабатывает имена, указанные в качестве имени таблицы:
CREATE TABLE sales (id int, date date, amt decimal(10,2))
DISTRIBUTED BY (id)
PARTITION BY RANGE (date);
CREATE TABLE jan_sales PARTITION OF sales
FOR VALUES FROM ('2023-01-01') TO ('2023-02-01');
CREATE TABLE feb_sales (LIKE sales);
ALTER TABLE sales ATTACH PARTITION feb_sales
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
\d+ sales
Partitioned table "public.sales"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------------+-----------+----------+---------+---------+--------------+-------------
id | integer | | | | plain | |
date | date | | | | plain | |
amt | numeric(10,2) | | | | main | |
Partition key: RANGE (date)
Partitions: feb_sales FOR VALUES FROM ('2024-02-01') TO ('2024-03-01'),
jan_sales FOR VALUES FROM ('2023-01-01') TO ('2023-02-01')
Distributed by: (id)
Access method: heap
Однако это различие согласуется между старым и новым синтаксисами. Например, нам не нужно указывать префикс при использовании устаревшего синтаксиса DROP PARTITION
. Но нужно, еслииспользуем раздел DETACH
:
-- Assuming we have the 'sales' partition with Jan and Feb
-- child partitions, created using legacy syntax.
\d+ sales
Partitioned table "public.sales"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------------+-----------+----------+---------+---------+--------------+-------------
id | integer | | | | plain | |
date | date | | | | plain | |
amt | numeric(10,2) | | | | main | |
Partition key: RANGE (date)
Partitions: sales_1_prt_feb_sales FOR VALUES FROM ('2023-02-01') TO ('2023-03-01'),
sales_1_prt_jan_sales FOR VALUES FROM ('2023-01-01') TO ('2023-02-01')
Distributed by: (id)
Access method: heap
-- drop partition 'jan_sales', no problem
ALTER TABLE sales DROP PARTITION jan_sales;
-- but couldn't detach, because there's no table named 'feb_sales'
ALTER TABLE sales DETACH PARTITION feb_sales;
ERROR: relation "feb_sales" does not exist
-- have to specify full name using DETACH
ALTER TABLE sales DETACH PARTITION sales_1_prt_feb_sales;
Примечание: для одной и той же секционированной таблицы старайтесь использовать либо новые синтаксисы, либо легаси-синтаксисы, чтобы избежать двусмысленности имён.
Для удобства воспользуемся таблицей, чтобы наглядно увидеть разницу:
Синтаксис | Добавлять префикс или нет | Старый или новый |
ADD PARTITION | да | Legacy |
DROP PARTITION | да | Legacy |
EXCHANGE PARTITION | да | Legacy |
SPLIT PARTITION | да | Legacy |
CREATE TABLE (PARTITION …) | да | Legacy |
CREATE TABLE (EVERY …) | да | Legacy |
CREATE TABLE … PARTITION OF | нет | New |
ATTACH PARTITION | нет | New |
DETACH PARTITION | нет | New |
START|END vs FROM|TO
Из примеров SQL, которые были показаны ранее, вы, вероятно, заметили, что в новом синтаксисе также есть другие ключевые слова для определения раздела диапазона: FOR VALUES FROM ... TO ...
. Легаси-синтаксис Greenplum имеет START ... END ()
. Оба определения работают только в соответствующих легаси или новых DDL:
-- Both these work:
ALTER TABLE sales ADD PARTITION mar_sales
START ('2023-03-01') END ('2023-03-31');
CREATE TABLE mar_sales PARTITION OF sales
FOR VALUES FROM ('2023-03-01') TO ('2023-03-31');
-- But these won't:
ALTER TABLE sales ADD PARTITION mar_sales
FOR VALUES FROM ('2023-03-01') TO ('2023-03-31');
CREATE TABLE mar_sales PARTITION OF sales
START ('2023-03-01') END ('2023-03-31');
В легаси-синтаксисе также есть ключевые слова EXCLUSIVE
и INCLUSIVE
для разделения диапазонов. В PostgreSQL такого нет, и начальная граница всегда включающая, а конечная — исключающая. Greenplum 7 поддерживает EXCLUSIVE|INCLUSIVE
, неявно добавляя »+1» к начальному или конечному диапазону. В результате они работают с типами данных, у которых подходящий оператор »+». Например, integer
и timestamp
, но float
или text
.
ALTER TABLE sales ADD PARTITION mar_sales
START ('2023-03-01') INCLUSIVE END ('2023-03-31') INCLUSIVE;
Ограниченная блокировка в ATTACH PARTITION
ATTACH PARTITION
требует монопольной блокировки общего обновления только для родительской таблицы. Этот тип блокировки считается менее строгим и не конфликтует со многими другими запросами, включая SELECT
, INSERT
и иногда UPDATE
. По сути, это значит, что только до Greenplum 7 можно добавлять раздел в иерархию разделов без нарушения обычных операций выполнения запросов к разделу (и наоборот). Например:
-- Assuming there's long-running insert
INSERT INTO sales SELECT * FROM ext_sales_data;
-- This will be blocked
ALTER TABLE sales ADD PARTITION march_sales START ('2023-03-01') END ('2023-04-01');
-- This will go through
ALTER TABLE sales ATTACH PARTITION march_sales FOR VALUES FROM ('2023-03-01') TO ('2023-04-01');
Ограничения раздела и ограничения проверки
Границы разделов больше не представляются в виде CHECK
ограничений. Ограничения разделов теперь — отдельная концепция.
-- same partition definition in Greenplum 6 and 7
-- Greenplum 6
\d+ sales_1_prt_jan_sales
Table "public.sales_1_prt_jan_sales"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------------+-----------+---------+--------------+-------------
id | integer | | plain | |
date | date | | plain | |
amt | numeric(10,2) | | main | |
Check constraints:
"sales_1_prt_jan_sales_check" CHECK (date >= '2023-01-01'::date AND date < '2023-02-01'::date)
Inherits: sales
Distributed by: (id)
-- Greenplum 7
\d+ jan_sales
Table "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: sales FOR 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))
Distributed by: (id)
PARTITION BY с несколькими столбцами
Разбиение списка на несколько столбцов больше не поддерживается. В качестве обходного пути вы можете создать составной тип и использовать его в качестве ключа разделения, например:
-- This is no longer working:
CREATE TABLE foo (a int, b int, c int) PARTITION BY list (b,c);
ERROR: cannot use "list" partition strategy with more than one column
-- Alternatively:
CREATE TYPE partkey as (b int, c int);
CREATE TABLE foo (a int, b int, c int)
PARTITION BY LIST ((row(b, c)::partkey));
Коротко о главном
В статье рассмотрели основные отличия Greenplum 7 от предыдущих версий СУБД. А также разобрали, на что стоит обратить внимание при работе с легаси- и новым синтаксисом.
«DWH на основе GreenPlum»