Выбор правильного правила разбиения - PullRequest
5 голосов
/ 25 ноября 2011

Я настраиваю новую базу данных PostgreSQL 9, которая будет содержать миллионы (или, возможно, миллиарды) строк. Поэтому я решил разделить данные, используя наследование PostgreSQL.

Я создал основную таблицу следующим образом (например, упрощенно):

CREATE TABLE mytable
(
  user_id integer,
  year integer,
  CONSTRAINT pk_mytable PRIMARY KEY (user_id, year)
);

И 10 таблиц разделов:

CREATE TABLE mytable_0 () INHERITS (mytable);
CREATE TABLE mytable_1 () INHERITS (mytable);
...
CREATE TABLE mytable_9 () INHERITS (mytable);

Я знаю, что строки всегда будут доступны из приложения с использованием уникального условия user_id. Поэтому я хотел бы равномерно распределить данные по 10 таблицам, используя правило, основанное на user_id.

Для настройки запросов по основной таблице моей первой идеей было использование ограничения проверки модуля:

ALTER TABLE mytable_0 ADD CONSTRAINT mytable_user_id_check CHECK (user_id % 10 = 0);
ALTER TABLE mytable_1 ADD CONSTRAINT mytable_user_id_check CHECK (user_id % 10 = 1);
...

Проблема в том, что, когда я запрашиваю основную таблицу «mytable» с условием user_id, анализатор PostgreSQL проверяет все таблицы и не извлекает выгоду из ограничения проверки:

EXPLAIN SELECT * FROM mytable WHERE user_id = 12345;

"Result  (cost=0.00..152.69 rows=64 width=36)"
"  ->  Append  (cost=0.00..152.69 rows=64 width=36)"
"        ->  Seq Scan on mytable  (cost=0.00..25.38 rows=6 width=36)"
"              Filter: (user_id = 12345)"
"        ->  Seq Scan on mytable_0 mytable  (cost=0.00..1.29 rows=1 width=36)"
"              Filter: (user_id = 12345)"
"        ->  Seq Scan on mytable_1 mytable  (cost=0.00..1.52 rows=1 width=36)"
"              Filter: (user_id = 12345)"
...
"        ->  Seq Scan on mytable_9 mytable  (cost=0.00..1.52 rows=1 width=36)"
"              Filter: (user_id = 12345)"

Принимая во внимание, что если я использую классическое ограничение CHECK, как это (и перераспределение, соответствующее этому правилу):

ALTER TABLE mytable_0 ADD CONSTRAINT mytable_user_id_check CHECK (user_id BETWEEN 1 AND 10000);
ALTER TABLE mytable_1 ADD CONSTRAINT mytable_user_id_check CHECK (user_id BETWEEN 10001 AND 20000);
...

будет сканироваться только таблицы, соответствующие условию (в этом примере mytable и mytable_1):

"Result  (cost=0.00..152.69 rows=64 width=36)"
"  ->  Append  (cost=0.00..152.69 rows=64 width=36)"
"        ->  Seq Scan on mytable  (cost=0.00..25.38 rows=6 width=36)"
"              Filter: (user_id = 12345)"
"        ->  Seq Scan on mytable_1 mytable  (cost=0.00..1.52 rows=1 width=36)"
"              Filter: (user_id = 12345)"

Но использование такого проверочного ограничения трудно поддерживать, поскольку диапазон пользователей, которые будут заполняться в таблицах, будет меняться с годами. тысячи первых, может быть, миллионы или больше в ближайшем будущем ...

Какое правило я мог бы использовать для равномерного разделения моих данных на 10 таблиц, которые могли бы воспользоваться проверочным ограничением, чтобы SELECT в основной таблице сканировал только нужную таблицу ...?

Спасибо

Nico

Ответы [ 2 ]

5 голосов
/ 25 ноября 2011

Ограничение касается планировщика, а не самого разбиения. Это подробно описано в руководстве:

http://www.postgresql.org/docs/9.1/static/ddl-partitioning.html

Есть две вещи, которые вы упомянули, но это нужно учитывать.

Во-первых, вы говорите, что весь доступ будет осуществляться через первичный ключ. Это означает, что вы не получите никаких преимуществ в производительности от разбиения (по крайней мере, при обычном использовании). Индекс для каждого раздела будет меньше, но PG нужно выбрать, какой раздел проверять первым. Вы получите выгоду, если вам нужно переиндексировать или подобное - вы можете переиндексировать каждый раздел отдельно.

Во-вторых, вы говорите, что у вас может быть что угодно от тысяч до миллиардов строк. Это приводит меня к двум выводам:

  1. Возможно, оставьте решение на потом. Подождите, пока вам не понадобится раздел.
  2. Вы вряд ли захотите ровно 10 разделов с двумя тысячами строк и двумя миллиардами.

Если вы собираетесь разделить, делайте это по диапазону - скажем, 100 000 строк или 1 миллион на раздел. Добавьте задание cron, чтобы проверить максимальный используемый идентификатор, и при необходимости создайте новый раздел (возможно, один раз в день).

Лично я бы оставил это, пока мне это не понадобилось. Возможно, вам нужно будет использовать один раздел, если вы считаете, что он скорее всего вам понадобится позже.

1 голос
/ 01 апреля 2015

WHERE должно быть в том же выражении, что и CHECK, т.е. планировщик запросов не поймет, что user_id = 12345 позволяет сделать вывод, что user_id % 10 = 5. Попробуйте

EXPLAIN SELECT * FROM mytable WHERE user_id = 12345 AND user_id % 10 = 5;

Тем не менее, я хотел бы на секунду ответить Ричарда Хэкстона , что вы можете отложить разбиение до тех пор, пока у вас не будет больше информации о размере набора данных, т.е. о том, чтобы избежать преждевременной оптимизации. Postgres может быть очень быстрым на довольно больших таблицах, он довольно далеко унесет вас без разбиения.

...