linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Главы
  • How it worksскоро
  • Уроки
  • База знаний
  • Собеседование
Часть VII — Индексы

$ глава 36 · 45 минут

BRIN: маленький неточный индекс

Все индексы до сих пор были точными: они знают, где именно лежит строка. BRIN идёт на радикальный размен - он не знает точно, он знает примерно, и за это занимает в тысячи раз меньше места. Вместо «где строка» он хранит «в этом куске таблицы значения от и до». Для огромных append-only таблиц (логи, метрики, временны́е ряды) это иногда лучший выбор: индекс на гигабайтную таблицу умещается в килобайты.

Но BRIN капризен: он работает, только когда физический порядок строк совпадает с порядком значений. Стоит данным перемешаться - и он бесполезен. В этой главе разберём, как он устроен, когда блистает и когда вредит.

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, и сводки хорошо разделяются.

sql
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 в тысячи раз, потому что хранит сводки, а не записи.

sql
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. Перед сравнением размеров предскажи разницу в порядках.

  1. Создай упорядоченную append-only таблицу: CREATE TABLE events AS SELECT g id, '2026-01-01'::timestamptz + (g||' seconds')::interval AS at FROM generate_series(1, 1000000) g;.

  2. Построй оба индекса: CREATE INDEX e_btree ON events USING btree (at); CREATE INDEX e_brin ON events USING brin (at); ANALYZE events;.

  3. Сравни размеры: SELECT pg_size_pretty(pg_relation_size('e_btree')), pg_size_pretty(pg_relation_size('e_brin')); - предскажи разницу в порядках.

  4. Проверь корреляцию: SELECT correlation FROM pg_stats WHERE tablename='events' AND attname='at'; - близко к 1, BRIN эффективен.

  5. Диапазонный запрос через BRIN: EXPLAIN SELECT count(*) FROM events WHERE at BETWEEN '2026-01-05' AND '2026-01-06'; - Bitmap Heap Scan через BRIN.

  6. Перемешай: 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 балансирует размер индекса против точности отсечения.

Контрольные вопросы

  1. Почему BRIN-индекс на той же таблице в тысячи раз меньше B-tree?

    Показать ответ

    Потому что B-tree хранит запись на каждую строку, а BRIN - одну маленькую сводку (min/max) на целый диапазон блоков (по умолчанию 128 страниц). Размер BRIN растёт не с числом строк, а с числом диапазонов блоков, которых на много порядков меньше. На таблице из миллионов строк это несколько сотен строчек min/max - килобайты против сотен мегабайт у B-tree. Платой за компактность идёт точность: BRIN знает лишь, в каком куске таблицы могло быть значение.

  2. Почему BRIN бесполезен на перемешанных данных?

    Показать ответ

    Потому что он опирается на совпадение физического порядка строк с порядком значений. На перемешанных данных в каждый диапазон блоков попадают значения со всего домена, поэтому min/max любого диапазона - почти весь диапазон значений. Сводка перестаёт что-либо отсекать: «искомое значение может быть здесь» оказывается верно для всех диапазонов. BRIN заставляет прочитать почти всю таблицу плюс пройти сам индекс - получается хуже, чем простой Seq Scan.

  3. Какой однострочный тест показывает, подойдёт ли BRIN?

    Показать ответ

    Значение correlation в pg_stats для нужной колонки: SELECT correlation FROM pg_stats WHERE tablename='t' AND attname='c'. Оно показывает, насколько физический порядок строк совпадает с порядком значений. Близко к +1 или -1 - данные упорядочены на диске, BRIN будет эффективно отсекать диапазоны блоков. Около 0 - данные перемешаны, BRIN не поможет. Этот один запрос отвечает на вопрос пригодности до создания индекса.

  4. Почему BRIN хорош именно на append-only таблицах?

    Показать ответ

    Потому что append-only вставка сохраняет корреляцию: новые строки дописываются в конец, и монотонная колонка (время, растущий id) растёт вместе с номером блока, давая узкие интервалы min/max по диапазонам. UPDATE и DELETE её разрушают: MVCC кладёт новую версию строки туда, где есть свободное место, а не «по порядку», и физический порядок расходится с логическим. Поэтому BRIN держат на журналах, метриках, исторических данных - тех, что в основном дописываются и почти не обновляются.

  5. Что происходит после того, как BRIN отобрал диапазоны блоков?

    Показать ответ

    Bitmap Heap Scan читает страницы отобранных диапазонов и перепроверяет условие на каждой строке. BRIN неточен: внутри подходящего по min/max диапазона есть и строки, не удовлетворяющие условию, поэтому нужен recheck на самих данных. То есть BRIN отсекает крупные куски таблицы целиком (не читая их), но внутри оставшихся кусков читает все строки и фильтрует. Выигрыш тем больше, чем больше диапазонов удалось отбросить по сводкам.

← Предыдущая35-ginСледующая →37-index-design
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки