Индекс для запроса отсортированных значений во временном диапазоне - PullRequest
0 голосов
/ 11 июня 2018

Предположим, у меня есть кортежи key / value / timerange, например:

CREATE TABLE historical_values(
  key TEXT,
  value NUMERIC,
  from_time TIMESTAMPTZ,
  to_time TIMESTAMPTZ
)

, и я хотел бы иметь возможность эффективно запрашивать значения (отсортированные по убыванию) для определенного ключа и времени, например:

SELECT value
FROM historical_values
WHERE
  key = [KEY]
  AND from_time <= [TIME]
  AND to_time >= [TIME]
ORDER BY value DESC

Какой индекс / типы следует использовать, чтобы получить наилучшую производительность поиска?Я подозреваю, что мое решение будет включать в себя индексы tstzrange и gist, но я не уверен, как сделать так, чтобы это соответствовало требованиям соответствия ключей и упорядочения значений.

Редактировать: Вот еще немного информации об использовании.

  • Идеально использует функции, доступные в Postgres v9.6.

  • Отношение будет содержать ок.1к ключей и 5м значений на ключ.Значения представляют собой большие целые числа (до 32 байт), в основном уникальные.Время колеблется от нескольких часов до пары лет.Временной горизонт 5 лет.Не допускаются значения NULL, но некоторые временные диапазоны имеют открытый конец (можно использовать NULL или время в далеком будущем для to_time).

  • Первичный ключявляется ключом и диапазоном времени (поскольку существует только одно историческое значение для диапазона времени на ключ).

  • Обычные операции: а) обновление to_time для «закрытия» исторического значения и б) вставка нового значения с помощью from_time = NOW.

  • Все значения могут быть запрошены.Разметка является опцией.

1 Ответ

0 голосов
/ 12 июня 2018

Дизайн БД

Для такой большой таблицы («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, '[]'));

Похожие:

...