PostgreSQL

GIN и GiST: полнотекстовый поиск и гео

Booking.com обрабатывает 1.5М запросов в секунду. Часть из них - поиск отелей по геолокации и фильтры по тегам ('wifi', 'pool', 'pet-friendly'). B-Tree здесь не подходит. GIN и GiST - специализированные индексы, которые делают такие запросы за миллисекунды.

  • **Uber/Lyft**: поиск ближайших водителей - GiST (PostGIS ST_DWithin) на таблице с геопозициями. Обновление локации ~4 раза/сек на миллионах устройств
  • **GitHub Search**: поиск по коду и issues - GIN на tsvector. Индексирование 200М+ репозиториев. Альтернатива Elasticsearch для транзакционных данных
  • **StackOverflow**: теги вопросов - GIN на массиве тегов. Запросы вида 'найди вопросы с тегами [postgresql] И [indexing]' через @>-оператор

GIN: инвертированный индекс как в поисковиках

Elasticsearch внутри - инвертированный индекс. PostgreSQL GIN (Generalized Inverted Index) - та же идея, встроенная в СУБД. Структура: словарь ключей → posting list (список heap-блоков где ключ встречается). Для строки 'postgresql database' создаются два входа: 'postgresql' и 'database', каждый ссылается на строку.

GIN оптимизирован для типов с несколькими значениями внутри одной строки: массивы, JSONB, tsvector, hstore. B-Tree строит один индексный ключ на строку; GIN строит N ключей. При запросе `@>` или `&&` PostgreSQL находит пересечение posting list'ов - это быстро даже на миллионах строк.

ML-параллель: TF-IDF и BM25 - это поверх инвертированного индекса. GIN - структура данных, ранжирование - отдельный слой. pgvector для векторного поиска использует другой подход (HNSW/IVFFlat), но GIN остаётся лучшим для точных многозначных совпадений (tags, ACL, JSON-paths).

Почему GIN быстрее B-Tree для запроса `tags @> '{go,rust}'` на таблице с 10М строк?

GIN для JSONB и массивов: операторы и индексируемость

Два класса операторов GIN. Для массивов: `@>` (содержит), `&&` (пересечение), `<@` (содержится). Для JSONB: те же плюс `?` (ключ exists), `?|` (любой из ключей), `?&` (все ключи). GIN автоматически индексирует все ключи и значения JSONB - поэтому запрос `data ? 'email'` попадает в индекс.

Два opclass для JSONB: `jsonb_ops` (default) индексирует ключи и значения отдельно, поддерживает все операторы. `jsonb_path_ops` индексирует только пути к значениям, не поддерживает `?`/`?|`/`?&`, но в 2-3 раза компактнее и быстрее для `@>`. Выбор зависит от паттерна запросов.

Какой opclass выбрать для GIN на JSONB, если запросы только вида `data @> '{"status":"active"}'`?

GIN для полнотекстового поиска: tsvector и tsquery

PostgreSQL fulltext search: документ преобразуется в `tsvector` (нормализованные лексемы с позициями), запрос - в `tsquery`. Оператор `@@` проверяет совпадение. GIN-индекс на tsvector-колонке превращает это в мгновенный поиск по posting lists.

Почему не Elasticsearch? Для объёмов до 10-50М документов PostgreSQL FTS конкурентоспособен: нет дополнительной инфраструктуры, транзакционная консистентность, JOIN с основными данными. Elasticsearch выигрывает при: шардировании на несколько машин, сложном scoring (BM25 с полями), real-time индексировании >100K doc/sec.

Зачем использовать `setweight()` при построении tsvector?

GiST для пространственных типов и PostGIS

GiST (Generalized Search Tree) - расширяемый фреймворк для индексов, где каждый узел дерева хранит не точный ключ, а предикат (bounding box, диапазон, круг). Поиск - рекурсивный обход: пропускаем поддеревья, чей предикат несовместим с запросом. Это R-Tree на стероидах.

GiST vs GIN: выбор определяется типом запроса. GiST - для пространственных предикатов (ST_Within, KNN, range overlap), всегда один ключ на строку. GIN - для многозначных containment-запросов (массивы, JSONB, FTS). Один тип данных может поддерживать оба: tsvector поддерживает GIN (быстрее для поиска) и GiST (меньше памяти при записи).

Какой индекс использует PostGIS для KNN-запроса 'найди 10 ближайших точек'?

GiST для range-типов и exclusion constraints

PostgreSQL range-типы: `int4range`, `tsrange`, `daterange` и др. GiST-индекс на range-типе поддерживает операторы `&&` (overlap), `@>` (contains), `<@` (contained by). Это позволяет строить запросы типа 'найди все брони, пересекающиеся с интервалом [check_in, check_out]' за O(log N).

Exclusion constraint - мощнее UNIQUE: UNIQUE = 'нет двух равных значений', EXCLUDE = 'нет двух строк, удовлетворяющих произвольному оператору'. Классический кейс - calendar booking без double-booking. Constraint проверяется через GiST-индекс на вставке/обновлении. Производительность вставки: O(log N) против O(N) для триггерного решения.

GIN и GiST - просто разные названия одного типа специализированного индекса

GIN - инвертированный индекс для многозначных типов (N ключей на строку); GiST - расширяемое дерево с предикатами (1 ключ/предикат на строку). Разные структуры для разных задач

GIN: posting list per key → быстрый поиск по членству в множестве. GiST: balanced tree with lossy keys → быстрый поиск по пространственным/диапазонным предикатам. Tsvector поддерживает оба, но GIN быстрее для поиска, GiST - при частых обновлениях

Чем EXCLUDE USING gist лучше CHECK-ограничения с подзапросом для предотвращения пересечений бронирований?

Связанные темы

GIN и GiST - специализированные индексы для конкретных типов данных:

  • JSONB в PostgreSQL — GIN-индексы - основной инструмент для запросов по JSONB-документам
  • Полнотекстовый поиск — GIN индексирует tsvector - фундамент FTS в PostgreSQL
  • Расширения: PostGIS — PostGIS использует GiST для пространственных индексов

Ключевые идеи

  • **GIN - инвертированный индекс**: ключ → posting list строк. Оптимален для @>, &&, ?, когда в строке N значений (массивы, JSONB, tsvector).
  • **jsonb_ops vs jsonb_path_ops**: jsonb_ops универсален (?, ?|, ?&, @>), jsonb_path_ops в 2-3 раза компактнее для чистых @>-запросов.
  • **Fulltext: GIN + tsvector**: setweight для field-boosting, ts_rank для ранжирования, phraseto_tsquery для фразового поиска.
  • **GiST - расширяемое дерево**: предикаты вместо точных ключей. KNN через <->, ST_DWithin для радиуса, range && для пересечений.
  • **EXCLUDE USING gist**: exclusion constraint через GiST-индекс - атомарная защита от пересечений за O(log N) вместо триггерного O(N).

Вопросы для размышления

  • GIN-индекс на JSONB с jsonb_ops индексирует все ключи и значения. Что происходит с размером индекса при высокой кардинальности значений (UUID как значение в JSON)?
  • Exclusion constraint с GiST проверяется на вставке. Как это влияет на производительность массового импорта данных о бронированиях?
  • GIN поддерживает 'fast update' mode - буферизацию изменений. Когда это полезно и когда вредит?

Связанные уроки

  • pg-14-partial-expr — Частичные и expression-индексы - предшествующий уровень абстракции
  • pg-16-brin — BRIN - следующий специализированный индекс в серии
  • pg-32-jsonb — GIN-индексы - основной инструмент для JSONB-запросов
  • pg-33-fulltext — GIN индексирует tsvector для полнотекстового поиска
  • pg-36-extensions — PostGIS использует GiST для пространственных запросов
  • alg-05 — GiST - обобщённое дерево поиска: тот же принцип, другие предикаты
  • db-10-indexes-advanced
GIN и GiST: полнотекстовый поиск и гео

0

1

Войти