36.1 Сводка вместо адресов
B-tree хранит запись на каждую строку - отсюда его размер. BRIN
(Block Range INdex) хранит по одной маленькой сводке на целый
диапазон блоков. По умолчанию диапазон - 128 страниц
(pages_per_range). Для каждого такого куска BRIN запоминает
минимальное и максимальное значение колонки.
диапазон блоков 0-127: min=2026-01-01 max=2026-01-05
диапазон блоков 128-255: min=2026-01-05 max=2026-01-09
...
Это всё. Индекс на таблицу из миллионов строк - это несколько сотен таких строчек min/max. Отсюда крошечный размер: BRIN не масштабируется с числом строк, только с числом диапазонов блоков.
36.2 Как идёт поиск
Запрос WHERE at = '2026-01-06' BRIN обрабатывает так: пройти по
сводкам и оставить только те диапазоны блоков, чей интервал min/max
мог бы содержать искомое значение. Остальные диапазоны отбрасываются
целиком, не читаясь.
ищем 2026-01-06:
диапазон 0-127 [01-01..01-05] → пропустить (06 вне интервала)
диапазон 128-255 [01-05..01-09] → проверить (06 внутри)
Дальше Bitmap Heap Scan читает страницы оставшихся диапазонов и перепроверяет условие на каждой строке (BRIN неточный - в диапазоне есть и неподходящие строки). То есть BRIN отсекает крупные куски таблицы, но внутри отобранных кусков читает всё подряд с recheck. Разбор - в brin-index.
36.3 Главное условие: физическая корреляция
BRIN полезен ровно тогда, когда физический порядок строк на диске совпадает с порядком значений колонки. Идеальный случай - append-only таблица с временно́й колонкой: новые строки дописываются в конец, и время растёт вместе с номером блока. Тогда у каждого диапазона блоков узкий интервал min/max, и сводки хорошо разделяются.
SELECT correlation FROM pg_stats
WHERE tablename = 'events' AND attname = 'at';
-- близко к 1 (или -1) → BRIN будет эффективен
correlation около ±1 значит, что порядок значений почти совпадает
с физическим - зелёный свет для BRIN. Около 0 - данные перемешаны,
и BRIN не поможет.
36.4 Размер: где BRIN недосягаем
Главный козырь BRIN - размер. На большой append-only таблице он меньше B-tree в тысячи раз, потому что хранит сводки, а не записи.
SELECT pg_size_pretty(pg_relation_size('events_at_btree')) AS btree, pg_size_pretty(pg_relation_size('events_at_brin')) AS brin;-- btree: 214 MB, brin: 48 kB
Такая разница меняет картину: B-tree на гигабайтную таблицу сам занимает сотни мегабайт и борется за место в кеше, а BRIN целиком помещается в несколько страниц и всегда в памяти. Если запросы - это диапазоны по времени на огромном append-only массиве, BRIN часто и есть правильный выбор: почти бесплатный по месту, достаточно точный по блокам.
36.5 Когда BRIN вредит
Перемешай данные - и BRIN превращается в бесполезный груз. Если строки лежат на диске в случайном порядке, то в каждый диапазон блоков попадают значения со всего диапазона. Тогда min/max каждого куска - почти весь домен, и сводка ничего не отсекает: «искомое значение может быть здесь» верно для всех диапазонов.
перемешанные данные:
диапазон 0-127 [01-01..12-31] → проверить
диапазон 128-255 [01-01..12-31] → проверить
...все диапазоны → проверить
В итоге BRIN заставляет прочитать почти всю таблицу плюс ещё пройти сам индекс - хуже, чем просто Seq Scan. Поэтому BRIN - не универсальная замена B-tree, а инструмент под конкретный профиль данных.
36.5.1 Подводный камень: UPDATE и потеря порядка
Корреляция, на которой держится BRIN, хрупкая. Append-only вставка её сохраняет, но обновления и удаления с последующей вставкой новых версий в произвольные свободные места её разрушают - MVCC кладёт новую версию туда, где есть место, а не «по порядку».
Поэтому BRIN хорош на таблицах, которые в основном дописываются и
почти не обновляются: журналы событий, метрики, исторические данные.
Как только таблица становится «горячей» по UPDATE, физический порядок
расходится с логическим, и BRIN деградирует. Новые диапазоны блоков
обобщает VACUUM или brin_summarize_new_values; параметр
pages_per_range регулирует баланс «размер индекса против точности
отсечения».
36.6 Место BRIN среди индексов
Сравнение по одной оси - «точность против размера»:
- B-tree - точный, размер пропорционален числу строк;
- BRIN - неточный (сводки по диапазонам), размер ничтожный, работает только при физической корреляции.
BRIN не конкурирует с B-tree на произвольных данных - он выигрывает
в нише: огромные, в основном append-only таблицы с запросами по
диапазону коррелированной колонки (чаще всего время). Вне этой ниши
бери B-tree. Перед созданием BRIN всегда смотри correlation в
pg_stats: это однострочный тест на пригодность.
Уроки в sandbox
lab-36.1. BRIN на временно́м ряде против B-tree
Построим BRIN и B-tree на append-only колонке времени, сравним размер, потом перемешаем данные и увидим деградацию BRIN. Перед сравнением размеров предскажи разницу в порядках.
Создай упорядоченную append-only таблицу:
CREATE TABLE events AS SELECT g id, '2026-01-01'::timestamptz + (g||' seconds')::interval AS at FROM generate_series(1, 1000000) g;.Построй оба индекса:
CREATE INDEX e_btree ON events USING btree (at); CREATE INDEX e_brin ON events USING brin (at); ANALYZE events;.Сравни размеры:
SELECT pg_size_pretty(pg_relation_size('e_btree')), pg_size_pretty(pg_relation_size('e_brin'));- предскажи разницу в порядках.Проверь корреляцию:
SELECT correlation FROM pg_stats WHERE tablename='events' AND attname='at';- близко к 1, BRIN эффективен.Диапазонный запрос через BRIN:
EXPLAIN SELECT count(*) FROM events WHERE at BETWEEN '2026-01-05' AND '2026-01-06';- Bitmap Heap Scan через BRIN.Перемешай:
CREATE TABLE shuffled AS SELECT * FROM events ORDER BY random(); CREATE INDEX s_brin ON shuffled USING brin (at); ANALYZE shuffled;- сравни correlation и план: BRIN больше не отсекает блоки.
sandbox с автопроверкой - открыть в песочнице
Резюме
- BRIN хранит сводку min/max на диапазон блоков (по умолчанию 128 страниц), а не запись на строку - поэтому индекс крошечный.
- Поиск отбрасывает диапазоны блоков, чей интервал min/max не содержит искомое; оставшиеся читает Bitmap Heap Scan с recheck (BRIN неточен).
- BRIN полезен только при физической корреляции: порядок строк на диске совпадает с порядком значений (append-only время, монотонный id).
- По размеру BRIN недосягаем: на гигабайтной таблице он в тысячи раз меньше B-tree и целиком помещается в кеш.
- На перемешанных данных min/max каждого диапазона - почти весь домен, сводки ничего не отсекают, и BRIN становится хуже Seq Scan.
- Корреляцию разрушают UPDATE/DELETE (MVCC кладёт новые версии в произвольные места); BRIN хорош на append-only таблицах.
- Перед созданием BRIN смотри correlation в pg_stats (около ±1 - годится); pages_per_range балансирует размер индекса против точности отсечения.
Контрольные вопросы
Почему BRIN-индекс на той же таблице в тысячи раз меньше B-tree?
Показать ответ
Потому что B-tree хранит запись на каждую строку, а BRIN - одну маленькую сводку (min/max) на целый диапазон блоков (по умолчанию 128 страниц). Размер BRIN растёт не с числом строк, а с числом диапазонов блоков, которых на много порядков меньше. На таблице из миллионов строк это несколько сотен строчек min/max - килобайты против сотен мегабайт у B-tree. Платой за компактность идёт точность: BRIN знает лишь, в каком куске таблицы могло быть значение.
Почему BRIN бесполезен на перемешанных данных?
Показать ответ
Потому что он опирается на совпадение физического порядка строк с порядком значений. На перемешанных данных в каждый диапазон блоков попадают значения со всего домена, поэтому min/max любого диапазона - почти весь диапазон значений. Сводка перестаёт что-либо отсекать: «искомое значение может быть здесь» оказывается верно для всех диапазонов. BRIN заставляет прочитать почти всю таблицу плюс пройти сам индекс - получается хуже, чем простой Seq Scan.
Какой однострочный тест показывает, подойдёт ли BRIN?
Показать ответ
Значение
correlationв pg_stats для нужной колонки:SELECT correlation FROM pg_stats WHERE tablename='t' AND attname='c'. Оно показывает, насколько физический порядок строк совпадает с порядком значений. Близко к +1 или -1 - данные упорядочены на диске, BRIN будет эффективно отсекать диапазоны блоков. Около 0 - данные перемешаны, BRIN не поможет. Этот один запрос отвечает на вопрос пригодности до создания индекса.Почему BRIN хорош именно на append-only таблицах?
Показать ответ
Потому что append-only вставка сохраняет корреляцию: новые строки дописываются в конец, и монотонная колонка (время, растущий id) растёт вместе с номером блока, давая узкие интервалы min/max по диапазонам. UPDATE и DELETE её разрушают: MVCC кладёт новую версию строки туда, где есть свободное место, а не «по порядку», и физический порядок расходится с логическим. Поэтому BRIN держат на журналах, метриках, исторических данных - тех, что в основном дописываются и почти не обновляются.
Что происходит после того, как BRIN отобрал диапазоны блоков?
Показать ответ
Bitmap Heap Scan читает страницы отобранных диапазонов и перепроверяет условие на каждой строке. BRIN неточен: внутри подходящего по min/max диапазона есть и строки, не удовлетворяющие условию, поэтому нужен recheck на самих данных. То есть BRIN отсекает крупные куски таблицы целиком (не читая их), но внутри оставшихся кусков читает все строки и фильтрует. Выигрыш тем больше, чем больше диапазонов удалось отбросить по сводкам.