Распределение данных
Обзор типов распределения данных
Распределение данных (distribution) — одна из самых важных концепций, лежащих в основе ADB. Она означает хранение данных каждой таблицы на различных сегментах кластера. При этом чем более равномерно данные распределяются между сегментами, тем выше производительность всего кластера. Для этого сегменты должны содержать примерно одинаковые порции данных.
Все таблицы в ADB распределяются между сегментами. При создании или изменении таблицы можно выбрать одну из доступных политик распределения данных (distribution policy) — для определения алгоритма распределения табличных данных между сегментами. Возможные политики приведены в таблице ниже.
Тип | Описание | Пример |
---|---|---|
DISTRIBUTED BY (<column(-s)>) |
Хеш-распределение. Конкретный сегмент выбирается на основе хешей, которые рассчитываются по полям, указанным в скобках. Рекомендуется использовать для таблиц, имеющих первичные ключи ( |
|
DISTRIBUTED REPLICATED |
Распределение данных, при котором копия таблицы сохраняется на каждом сегменте кластера. Рекомендуется для небольших таблиц (например, таблиц-справочников). Позволяет избежать любых перемещений данных (Motion) при JOIN-запросах |
|
DISTRIBUTED RANDOMLY |
Случайное распределение данных с использованием алгоритма round-robin. Поскольку система выбирает сегменты случайным образом, равномерность распределения данных между ними не гарантируется. Рекомендуется для иных случаев — когда в таблицах нет столбцов с уникальными значениями, а размер таблиц достаточно большой |
|
Рисунок ниже показывает в упрощенном виде, как выбрать наиболее оптимальную политику распределения данных.
Рекомендации по равномерному распределению данных
Для достижения максимальной производительности все сегменты кластера должны содержать примерно одинаковые порции данных. В противном случае может произойти "перекос данных" — skew. Это разбалансировка, во время которой на сегменты, хранящие большее количество данных по сравнению с другими сегментами, приходится более высокая нагрузка по обработке запросов. Чтобы избежать skew и сделать распределение данных более эффективным, необходимо следовать рекомендациям, перечисленным ниже:
-
Всегда явно указывать политику распределения данных при создании таблицы. При отсутствии выражения
DISTRIBUTED
распределение данных в таблице в последующем будет регулироваться конфигурационным параметромgp_create_table_random_default_distribution
. А это в отдельных случаях может привести к выбору произвольного столбца в качестве ключа распределения — если в параметре не указано значениеON
. Дополнительную информацию можно получить в документации Greenplum. -
Не выбирать в качестве ключей распределения столбцы, данные в которых распределяются неравномерно:
-
Даты и временные метки.
-
Поля, содержащие множество значений
NULL
. -
Поля с кастомными типами данных, для которых неизвестно дальнейшее распределение значений.
Вместо этого рекомендуется выбирать ключ распределения, значение которого будет уникально для каждой строки таблицы — например, первичный ключ или любой уникальный столбец без значений
NULL
.При необходимости можно настраивать пользовательские хеш-функции для вычисления ключа распределения. Для этого заполните аргумент
<opclass>
в выраженииDISTRIBUTED BY
— именем соответствующего оператора (operator). Более подробную информацию о синтаксисе можно найти в статье Таблицы. -
-
Всегда проверять, что данные равномерно распределяются между сегментами после первоначальной загрузки данных и последующих обновлений. Чтобы узнать сегмент, в котором хранится текущая строка таблицы, можно выполнить следующий запрос:
SELECT gp_segment_id, * FROM test_distr_key;
Результат команды:
gp_segment_id | a | b ---------------+---+------- 4 | 1 | first (1 row)
Возвращаемое поле
gp_segment_id
соответствует номеру сегмента, хранимому в полеcontent
системной таблицыpg_catalog.gp_segment_configuration
. Эта таблица содержит подробную информацию о всех сегментах кластера. По умолчанию поле скрыто. Оно может быть получено для всех таблиц, за исключением тех, для которых выбрана политикаDISTRIBUTED REPLICATED
(так как в этом случае данные дублируются на всех сегментах).ВНИМАНИЕНе рекомендуется использовать системное полеgp_segment_id
в запросах к большим таблицам на регулярной основе. -
При необходимости можно изменить выбранный ранее тип распределения данных одним из описанных способов.
-
Изменение политики:
ALTER TABLE test_distr_replicated SET DISTRIBUTED RANDOMLY;
-
Перераспределение данных без изменения ключа распределения:
ALTER TABLE test_distr_key SET WITH (REORGANIZE=TRUE);
-
-
Правильное распределение данных влияет на скорость выполнения запросов, особенно запросов
JOIN
. Для обеспечения максимальной эффективности запросов рекомендуется:-
Не выбирать в качестве ключей распределения поля, которые будут активно использоваться в предикатах
WHERE
. В оптимальном случае все сегменты кластера должны выполнять равную долю работ по обработке каждого запроса данных. Но если предикатWHERE
в запросе ссылается на ключ распределения, обработка запроса будет фактически сосредоточена только на одном сегменте (в котором хранятся все строки с запрошенным значением ключа распределения). -
Учитывать, что соединение таблиц с использованием ключей распределения самое быстрое, поскольку одинаковые данные хранятся на одних и тех же сегментах — даже если эти данные принадлежат различным таблицам. Когда таблицы используют общий ключ распределения, соединение и сортировка по этому ключу выполняется локально — без необходимости распределения кортежей данных по другим сегментам. А локальные операции всегда быстрее, чем распределенные.
-
Использовать политику
DISTRIBUTED REPLICATED
для небольших и часто используемых таблиц-справочников. Это повышает производительность запросов, позволяя избежать копирования таблицы во все сегменты (так называемый Broadcast Motion). -
Использовать одинаковые типы данных в полях, на основе которых выполняется соединение таблиц в запросах
JOIN
.
-
Примеры
-
Создайте две таблицы со структурой, описанной ниже:
CREATE TABLE t1 (id INT, msg TEXT) DISTRIBUTED BY(id); CREATE TABLE t2 (id INT, msg TEXT) DISTRIBUTED BY(id);
-
Добавьте в таблицы следующие данные:
INSERT INTO t1 (id, msg) VALUES (1, 'text1'), (2, 'text2'); INSERT INTO t2 (id, msg) VALUES (1, 'text3'), (2, 'text4');
-
Убедитесь, что данные с одинаковым значением ключа распределения в обеих таблицах сохранены на одном сегменте:
SELECT gp_segment_id, * FROM t1;
Результат команды:
gp_segment_id | id | msg ---------------+----+------- 4 | 1 | text1 9 | 2 | text2 (2 rows)
SELECT gp_segment_id, * FROM t2;
Результат команды ниже. Идентификаторы сегментов идентичны приведенным ранее:
gp_segment_id | id | msg ---------------+----+------- 9 | 2 | text4 4 | 1 | text3 (2 rows)
-
Используйте команду
EXPLAIN
для вывода плана JOIN-соединения двух таблиц.EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.id = t2.id;
Результат команды:
QUERY PLAN --------------------------------------------------------------------------------- Gather Motion 16:1 (slice1; segments: 16) (cost=0.00..862.00 rows=2 width=20) -> Hash Join (cost=0.00..862.00 rows=1 width=20) Hash Cond: (t1.id = t2.id) -> Seq Scan on t1 (cost=0.00..431.00 rows=1 width=10) -> Hash (cost=431.00..431.00 rows=1 width=10) -> Seq Scan on t2 (cost=0.00..431.00 rows=1 width=10) Optimizer: Pivotal Optimizer (GPORCA) (7 rows)
Вывод команды показывает, что для этого запроса не требуется никаких перемещений данных (Motion), кроме Gather Motion — это стандартное действие, означающее отправку результатов запроса из сегментов в Master Server.
Различные типы данных в ключах
-
Создайте дополнительную таблицу:
CREATE TABLE t3 (id INT, msg TEXT) DISTRIBUTED BY(msg);
-
Добавьте данные в таблицу:
INSERT INTO t3 (id, msg) VALUES (1, 'text5'), (2, 'text6');
-
Получите список сегментов, в которых хранятся данные таблицы:
SELECT gp_segment_id, * FROM t3;
Результат команды:
gp_segment_id | id | msg ---------------+----+------- 2 | 1 | text5 10 | 2 | text6 (2 rows)
Обратите внимание, что идентификаторы сегментов отличаются от тех, что выводились для таблицы
t1
в предыдущем примере. -
Используйте команду
EXPLAIN
для вывода плана JOIN-соединения таблицt3
иt1
.EXPLAIN SELECT * FROM t1 JOIN t3 ON t1.id = t3.id;
Результат команды:
QUERY PLAN ------------------------------------------------------------------------------------------------------- Gather Motion 16:1 (slice2; segments: 16) (cost=0.00..862.00 rows=2 width=20) -> Hash Join (cost=0.00..862.00 rows=1 width=20) Hash Cond: (t1.id = t3.id) -> Seq Scan on t1 (cost=0.00..431.00 rows=1 width=10) -> Hash (cost=431.00..431.00 rows=2 width=10) -> Broadcast Motion 16:16 (slice1; segments: 16) (cost=0.00..431.00 rows=2 width=10) -> Seq Scan on t3 (cost=0.00..431.00 rows=1 width=10) Optimizer: Pivotal Optimizer (GPORCA) (8 rows)
По сравнению с предыдущим примером, в выводе команды добавляется действие Broadcast Motion. Во время этой операции происходит копирование таблицы во все сегменты кластера — поскольку в них нет необходимых данных для выполнения JOIN. Такое поведение некритично для таблиц малого размера. Однако в случае соединения больших таблиц оно может вызвать серьезные проблемы с производительностью. Чтобы устранить Broadcast Motion в нашем примере, достаточно изменить ключ распределения в таблице
t3
сmsg
наid
.
Выбор неуникального поля в качестве ключа
-
Создайте дополнительную таблицу:
CREATE TABLE t4 (id INT, msg TEXT) DISTRIBUTED BY(id);
-
Добавьте данные в таблицу. Обратите внимание, что значения первого столбца (выбранного в качестве ключа распределения) неуникальны:
INSERT INTO t4 (id, msg) VALUES (1, 'text7'), (1, 'text8');
-
Получите список сегментов, в которых хранятся данные таблицы:
SELECT gp_segment_id, * FROM t4;
Результат команды:
gp_segment_id | id | msg ---------------+----+------- 4 | 1 | text7 4 | 1 | text8 (2 rows)
Вывод команды наглядно демонстрирует, что обе записи таблицы хранятся на одном сегменте, поскольку для них задано одно и то же значение ключа распределения. На практике крайне важно избегать таких ситуаций, так как они могут вызвать увеличение нагрузки на отдельных сегментах. Чтобы устранить проблему в нашем примере, достаточно указать в качестве ключа распределения другой, уникальный столбец таблицы. Если это невозможно — изменить политику распределения данных на
DISTRIBUTED RANDOMLY
.