Индексы в PostgreSQL: какие бывают и как они живут в Rails

Индексы в PostgreSQL — это мощный инструмент для ускорения запросов, но их эффективность зависит от правильного выбора типа. В Rails можно легко настраивать B-tree для стандартных условий, GIN для работы с JSON и массивами, GiST для геоданных и BRIN для больших таблиц с упорядоченными значениями. Разберёмся, как подобрать оптимальный индекс под конкретную задачу и избежать типичных ошибок.


Если у тебя в проде уже больше 100К записей, индексы — это твой самый верный друг.
Но друзья бывают разные. Кто-то всегда рядом (btree), кто-то появляется в трудную минуту (gin), а кто-то живёт тихо в подвале, пока не наступит апокалипсис (brin).

Разбираемся, что за индексы бывают в PostgreSQL и как правильно их подключать в Rails.

🧱 btree: универсальный солдат

Это индекс по умолчанию, если ты пишешь:

add_index :users, :email

PostgreSQL создаёт сбалансированное дерево (B-tree), где значения сортируются и хранятся как в телефонной книге.

  • Работает идеально с =, <, >, BETWEEN
  • Отлично поддерживает ORDER BY
  • Быстрый, но при большом размере — может разрастаться

📌 Rails использует его по умолчанию в 99% случаев. Он стабилен и предсказуем, как дедовский скрипт на Perl.


🌲 GIN: супергерой для JSON и массивов

Если ты когда-либо писал:

where("metadata @> ?", { status: "ok" }.to_json)

…и при этом не добавил GIN-индекс — база ненавидит тебя молча. GIN (Generalized Inverted Index) строит обратный индекс, как поисковая система: «вот ключ — вот где он встречается».

Используй:

add_index :logs, :metadata, using: :gin
  • Для jsonb, ARRAY, tsvector
  • Очень быстро ищет по значениям
  • Медленно создаётся, зато потом — летает

📌 Под капотом: вместо дерева — таблица в стиле “словарь → позиции”, как в Google Search.


🌀 GiST: если у тебя PostGIS и шпионские координаты

GiST нужен для нестандартных типов: гео-точек, диапазонов (int4range), деревьев. Он не самый быстрый, но гибкий.

add_index :places, :location, using: :gist
  • Работает с PostGIS, range, ltree
  • Можно расширять под свои нужды

📌 Под капотом: структура похожа на B-tree, но с расширяемыми правилами.


🔎 BRIN: спящий агент

Если у тебя таблица на миллионы записей, и почти все записи идут по порядку (created_at, id) — зови BRIN.

add_index :events, :created_at, using: :brin

BRIN (Block Range INdex) — это как записная книжка: “в этом блоке строки с created_at от A до B”.

  • Почти ничего не весит
  • Молниеносно работает на больших таблицах
  • Но может давать неточные результаты (Postgres проверяет дальше вручную)

📌 Под капотом: просто карта диапазонов по блокам. Минимум памяти — максимум пользы.


🧠 Вся суть — в выборе под задачу

Тип Лучшее применение
B-tree =, <, >, ORDER BY, BETWEEN
GIN jsonb, ARRAY, full-text поиск
GiST гео, диапазоны, деревья, ltree
BRIN огромные таблицы с возрастающим created_at

🛠️ Как в Rails?

Rails позволяет использовать все типы:

add_index :logs, :metadata, using: :gin
add_index :events, :created_at, using: :brin

И даже на выражениях:

add_index :documents, "to_tsvector('english', content)", using: :gin, name: :idx_fts

💡 Индексы ≠ магия. Проверяй EXPLAIN

Хочешь понять, работает ли индекс? Используй:

EXPLAIN ANALYZE SELECT * FROM logs WHERE metadata @> '{"status": "ok"}';

Ищи Index Scan using ... вместо Seq Scan.


🚨 Частые ошибки

  • Добавил индекс, но тип не подходит ➜ Seq Scan всё равно
  • Используешь GIN без jsonb ➜ бесполезно
  • Не обновляешь статистику (ANALYZE) ➜ планировщик ошибается
  • Думаешь, что индекс — это бесплатно ➜ нет, они тормозят INSERT/UPDATE

📌 Вывод: индекс — как кастомный велосипед. Подбери под себя, не копируй бездумно из StackOverflow.

🗓 Дата публикации: 02.02.2025, но это не точно...

PostgreSQL индексы Rails B-tree GIN BRIN