Дизайн БД
Для такой большой таблицы («1k ключей и 5m значений на ключ») я бы предложил оптимизировать хранилище следующим образом:
CREATE TABLE hist_keys (
key_id serial PRIMARY KEY
, key text NOT NULL UNIQUE
);
CREATE TABLE hist_values (
hist_value_id bigserial PRIMARY KEY -- optional, see below!
, key_id int NOT NULL REFERENCES hist_keys
, value numeric
, from_time timestamptz NOT NULL
, to_time timestamptz NOT NULL
, CONSTRAINT range_valid CHECK (from_time <= to_time) -- or < ?
);
Также помогает индексировать производительность.
И рассмотрим разбиение .Разделение списка на key_id
.Может быть, даже добавить подразделение на (диапазон на этот раз) на from_time
. Прочтите руководство здесь.
С одним разделом на key_id
, (и исключение ограничений включено!) Postgres будет смотреть только на небольшой раздел (и индекс)для данного ключа, а не всей большой таблицы.Крупный выигрыш.
Но я бы настоятельно рекомендовал сначала обновить хотя бы до Postgres 10 , добавив "декларативное разбиение" .Упрощает управление разделами.
Еще лучше, перейдите к Postgres 11 (в настоящее время бета-версия), которая добавляет значительные улучшения для разбиения (в том числе улучшения производительности).В частности, для вашей цели , чтобы получить наилучшую производительность поиска , цитируйте главу по разбиению в примечаниях к выпуску для Postgres 11 (в настоящее время бета) :
Разрешить более быстрое удаление разделов во время обработки запросов (Амит Ланготе, Дэвид Роули, Дилип Кумар)
Это ускоряет доступ к многораздельным таблицам с несколькими разделами.
Разрешить удаление разделов во время выполнения запроса (Дэвид Роули, Бина Эмерсон)
Ранее удаление разделов могло происходить только во время планирования, то есть многие объединения и подготовленные запросы не могли использовать удаление разделов.
Индекс
С точки зрения столбца value
небольшое подмножество выбранных строк является произвольным для каждого нового запроса.Не думаю, что вы найдете полезный способ поддержки ORDER BY value DESC
с помощью индекса.Я бы сосредоточился на других столбцах. Может быть добавить value
в качестве последнего столбца к каждому индексу, если вы можете получить из него сканирование только по индексу (возможно для btree и GiST).
Без разделения:
CREATE UNIQUE INDEX hist_btree_idx ON hist_values (key_id, from_time, to_time <b>DESC</b>);
UNIQUE
необязательно, но см. Ниже.
Обратите внимание на важность противоположных порядков сортировки для from_time
и to_time
.См. (Тесно связанный!):
Это почти тот же показатель, что иодин реализующий ваш ПК на (key_id, from_time, to_time)
.К сожалению, мы не можем использовать его как индекс PK. Цитирование руководства:
Кроме того, это должен быть индекс b-дерева с порядком сортировки по умолчанию.
Поэтому я добавил bigserial
в качестве суррогатного первичного ключа в предложенной выше схеме таблицы и ограничениях NOT NULL
плюс индекс UNIQUE
для обеспечения соблюдения вашего правила уникальности.
В Postgres 10 или более поздних версиях вместо столбца IDENTITY
рассмотрите:
В этом исключительном случае можно даже использовать ограничение PK, чтобы избежать дублирования индекса и сохранить минимальный размер таблицы.Зависит от полной ситуации.Вам может понадобиться для ограничений FK или аналогичных.См .:
A Индекс GiST Как вы уже подозревали, может быть даже быстрее.Я предлагаю сохранить исходные столбцы timestamptz
в таблице (16 байтов вместо 32 байтов для tstzrange
) и добавить key_id
после установки дополнительного модуля btree_gist
:
CREATE INDEX hist_gist_idx ON hist_values
USING GiST (key_id, tstzrange(from_time, to_time, '[]'));
.выражение tstzrange(from_time, to_time, '[]')
создает диапазон , включая верхнюю и нижнюю границы. Прочтите руководство здесь.
Ваш запрос должен соответствовать индексу:
SELECT value
FROM hist_values
WHERE key = [KEY]
AND tstzrange(from_time, to_time, '[]') @> tstzrange([TIME_FROM], [TIME_TO], '[]')
ORDER BY value DESC;
Это эквивалентно вашему оригиналу.
@>
, являющийся диапазоном, содержит оператор.
С разделением списка на key_id
Используя отдельную таблицу для каждой key_id
, мы можем опустить key_id
из индекса, улучшив его размер и производительность - особенно для индекса GiST - для которого мы затемтакже не нужен дополнительный модуль btree_gist
.В результате получается ~ 1000 разделов и соответствующие им индексы:
CREATE INDEX hist999_gist_idx ON hist_values USING GiST (tstzrange(from_time, to_time, '[]'));
Похожие: