Индекс, который занимает 8 КБ и ускоряет выборку в 65–80 раз по сравнению с обычным heap — звучит как маркетинговый слоган, но это реальные цифры теста нового метода доступа iHeap для PostgreSQL на таблице с миллионом записей. При этом пользователю не нужно создавать, обслуживать и продумывать стратегию индексирования: iHeap встраивает поддержку BRIN‑индексов прямо в метод доступа к таблице, автоматически покрывая индексами все совместимые столбцы. Разбираемся, как это работает, где даёт максимальный выигрыш и какую цену приходится платить за скорость выборки.

Общая информация

iHeap — это альтернативный табличный метод доступа, основанный на стандартном методе доступа heap, но со встроенной поддержкой BRIN‑индексирования. Если для ускорения выборки из обычных heap‑таблиц индексы создаются и обслуживаются как отдельные объекты, то в iHeap поддержка BRIN‑индексов встроена в метод доступа, и для всех совместимых столбцов индексы создаются автоматически.

Совместимыми являются столбцы фиксированной и переменной длины, если их размер не превышает 64 байт (для столбцов переменной длины в это ограничение также входит заголовок значения). Кроме того, тип данных должен поддерживаться индексом B‑tree, поскольку iHeap использует его операторы сравнения.

BRIN (Block Range Index) — это компактный тип индекса, который хранит диапазоны значений для блоков страниц таблицы, а не значения каждой отдельной строки. Благодаря этому индекс занимает мало места и позволяет быстро определить, могут ли страницы содержать строки, удовлетворяющие условию поиска. Такой подход особенно эффективен для больших таблиц, где значения в столбцах изменяются последовательно или близки к порядку хранения данных. В iHeap страницы таблицы объединяются в блоки, размер которых задается параметром pages_per_index. Для каждого блока формируется группа BRIN‑индексов по всем совместимым столбцам. Эти группы объединяются в единый внутренний индексный слой таблицы — iHeap Index.

При последовательном сканировании сначала анализируется iHeap индекс. Для каждого блока heap страниц по соответствующей группе BRIN‑индексов определяется, содержатся ли на страницах блока строки, удовлетворяющие условиям поиска. Если нет, весь блок пропускается и не читается. За счет сокращения объема читаемых данных уменьшается количество операций ввода‑вывода, что позволяет существенно ускорить последовательное сканирование.

Использование iHeap позволяет ускорить Sequence Scan в отдельных сценариях до нескольких десятков раз.

Особенности:

  • Пользователь может менять метод доступа к таблице с heap на iHeap и обратно; 

  • BRIN‑индексы встроены в метод доступа iHeap, и не управляются пользователем;

  • Для каждой iHeap‑таблицы можно задать параметр pages_per_index, определяющий количество страниц в одном блоке. По умолчанию значение параметра равно 256 и может быть задано в диапазоне от 64 до 512;

  • Группа BRIN‑индексов должна полностью помещаться на одну страницу iHeap индекса. Это ограничение примерно соответствует таблице с 1000 столбцами типа int. Если группа не помещается на страницу, при создании таблицы выдается ошибка «too many columns for using one page per iheap index tuple (size of data of columns:%u bytes)» с указанием объема данных, который требуется разместить. На практике таблицы с сотнями столбцов встречаются редко. Поэтому на одной странице iHeap индекса обычно размещаются десятки или даже сотни групп BRIN‑индексов;

  • Перестроение индекса происходит во время выполнения команд VACUUM и AUTOVACUUM:

    • При выполнении операций UPDATE и DELETE данные на heap‑страницах таблицы изменяются. Соответствующие группы BRIN‑индексов помечаются как устаревшие. При этом диапазоны BRIN‑индексов при необходимости расширяются, но не сужаются. Так как диапазоны могут только расширяться, данными устаревших групп по‑прежнему можно пользоваться. Устаревшие группы BRIN‑индексов (и только они) перестраиваются при выполнении команд VACUUM или AUTOVACUUM, после чего диапазоны принимают актуальные значения;

    • Команда VACUUM, если она выполняется с параметром DISABLE_PAGE_SKIPPING, обновляет все страницы iHeap индекса, а не только устаревшие.

Кейсы применения

iHeap эффективен в следующих случаях:

  1. Индексирование монотонно возрастающих или убывающих значений. iHeap хорошо подходит для индексирования монотонно возрастающих/убывающих значений столбца, а также значений, которые отличаются для каждого блока страниц данных, то есть удобны для фильтрации. Например, чтение первых нескольких процентов строк из таблицы логов, упорядоченной по полю timestamp. В данной ситуации iHeap показал некоторое преимущество даже относительно B‑tree индекса (до 2 раз). Прирост же относительно обычного heap или BRIN индекса составляет до нескольких десятков раз, постепенно снижаясь с увеличением процента результирующей выборки от общего размера таблицы. На диаграмме ниже приведен пример изменения времени чтения в зависимости от процента читаемых данных из таблицы для heap, heap+B‑tree и iHeap.

  2. Запросы с условиями «больше/меньше» и достаточно большими выборками. iHeap эффективен для запросов с условиями на больше/меньше и с достаточно большими выборками (10%-15% таблицы для части предикатов), для которых B‑tree индекс не подходит. Время выполнения SELECT‑запроса для таблицы с методом доступа iHeap примерно в 65–80 раз меньше, чем при использовании метода доступа heap и BRIN‑индекса, а также в 20 раз меньше, чем при использовании B‑tree индекса для таблицы на 1 млн. записей, состоящей из трех столбцов типа Int, с уникальными значениями и индексами B‑tree или BRIN по каждому столбцу.

    Параметры таблицы

    Запрос с тремя условиями «больше» на индексируемые поля, сек

    Без индексов, heap

    0.08086

    iHeap

    0.00126

    B‑tree индекс, 3 индекса на 3 столбца

    0.02543

    BRIN индекс, 3 индекса на 3 столбца

    0.10354

  3. Сценарии, в которых критичен размер индекса. iHeap имеет существенное (до десятков тысяч раз) преимущество в размере перед B‑tree индексом и до нескольких десятков раз перед BRIN индексом. Для таблицы test (i1 int, i2 int, i3 int, b1 bigint, b2 bigint) на 1 млн. записей с уникальными значениями для каждого из столбцов размер индекса B‑tree на первый столбец составляет 21960КБ (1/3 от размера таблицы), а размер индекса iHeap составляет 8КБ. При создании B‑tree индексов по всем столбцам таблицы, их размер составляет уже 109800КБ, размер индекса iHeap при этом остается 8КБ. При этом время выборки записи по индексируемому полю в приведенном примере для iHeap примерно в 10 раз больше чем для B‑tree индекса и примерно в 50 раз меньше чем для обычного heap или BRIN индекса. В таблице ниже приведены результаты теста с индексированием одного столбца и выполнением SELECT‑запроса с условием по этому столбцу.

    Параметры таблицы

    Размер индекса, КБ

    1 запрос с условием на индексируемое поле, сек

    Без индексов, heap

    0

    0.17595

    iHeap

    8

    0.00367

    B‑tree индекс, 1 столбец

    21 960

    0.00035

    BRIN индекс, 1 столбец

    48

    0.17769

    Ниже приведены результаты теста с индексированием пяти столбцов и выполнением пяти SELECT‑запросов — по одному для каждого индексируемого столбца.

    Параметры таблицы

    Размер индекса, КБ

    5 запросов с условиями на индексируемые поля, сек

    Без индексов, heap

    0

    0.97478

    iHeap

    8

    0.01836

    B‑tree индекс, 5 столбцов

    109 800

    0.00075

    BRIN индекс, 5 столбцов

    240

    0.99939

При использовании iHeap следует учитывать следующие моменты:

  1. Время вставки при работе с iHeap превышает время вставки для heap примерно на 70% и меньше времени вставки для одного B‑tree индекса примерно на 13%. Это объясняется тем, что для iHeap помимо добавления новой записи, нужно модифицировать индекс. Ниже приведена таблица сравнения вставки для heap, iHeap, B‑tree и BRIN индекса.

    Параметры таблицы

    Время вставки 1 млн. записей, сек.

    Без индексов, heap

    2.66

    iHeap

    4.47

    B‑tree индекс, 1 столбец

    5.12

    BRIN индекс, 1 столбец

    2.83

    B‑tree индекс, 5 столбцов

    14.95

    BRIN индекс, 5 столбцов

    3.95

  2. Если возможно, выгоднее сначала произвести вставку большого количества записей в таблицу heap, и после этого преобразовать метод доступа в iHeap. Создание индекса iHeap займет ~8-9% от времени добавления данных. Ниже приведена диаграмма времени вставки данных, конвертации heap → iHeap и vacuum для iHeap, heap → iHeap и heap.

  3. Если данные распределены равномерно (блоки heap страниц содержат данные приблизительно в одном и том же диапазоне значений) — использование iHeap не рекомендуется, лучше использовать B‑tree индекс для поиска. 

Пример использования расширения

-- Добавить библиотеку расширения в параметр shared_preload_libraries 
shared_preload_libraries = 'pgpro_iheap'
-- Создание расширения pgpro_iheap
CREATE EXTENSION pgpro_iheap;
-- Создание таблицы с методом доступа "iheap"
CREATE TABLE test (i int, t text, b bigint) using iheap;

Комментарии (0)