Индексы в 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.