Generalized Search Tree для геоданных, диапазонов и нечёткого поиска.
GiST — это расширяемый индекс для произвольных типов данных. Он позволяет индексировать геоданные, диапазоны, нечёткий поиск и другие специализированные структуры.
GiST (Generalized Search Tree) — это сбалансированное дерево, которое позволяет индексировать произвольные типы данных через определение предикатов.
В отличие от B-дерева, которое требует тотального порядка (a < b < c), GiST работает с предикатами истинности.
B-дерево: требует <, >, =
GiST: требует только «пересекается/содержит/ближайший»
| Сценарий | Тип данных | Пример |
|---|---|---|
| Геоданные | PostGIS (geometry, geography) | WHERE geom && box |
| Диапазоны | tsrange, int4range, numrange | WHERE tsrange && '[2026-01-01, 2026-12-31]' |
| Нечёткий поиск | text с trigram | WHERE name % 'jon' |
| Полнотекстовый поиск | tsvector | WHERE text @@ query |
| Векторы | pgvector | WHERE embedding <-> query < 0.5 |
┌─────────────┐
│ ROOT │
│ bounding │
│ box │
└──────┬──────┘
│
┌─────────────────┼─────────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│INTERNAL │ │INTERNAL │ │INTERNAL │
│ box 1 │ │ box 2 │ │ box 3 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
▼ ▼ ▼ ▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ LEAF │ │ LEAF │ │ LEAF │ │ LEAF │ │ LEAF │ │ LEAF │
│point │ │point │ │point │ │point │ │point │ │point │
│(1,2) │ │(3,4) │ │(5,6) │ │(7,8) │ │(9,10)│ │... │
└──────┘ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘
Каждый узел содержит ограничивающий прямоугольник (bounding box), который охватывает все дочерние элементы.
SELECT * FROM places WHERE geom && ST_MakeEnvelope(0, 0, 10, 10);Для поддержки типа данных в GiST нужно определить 7 методов:
| Метод | Описание |
|---|---|
| Consistent | Проверяет, удовлетворяет ли элемент условию |
| Union | Вычисляет bounding box для множества элементов |
| Compress | Сжимает данные для хранения в индексе |
| Decompress | Восстанавливает данные из сжатого вида |
| Penalty | Оценка стоимости добавления элемента в узел |
| PickSplit | Алгоритм разделения переполненного узла |
| Same | Проверяет эквивалентность двух элементов |
CREATE EXTENSION postgis;CREATE TABLE places (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255),
geom GEOMETRY(POINT, 4326) -- WGS84 (lat/lon)
);
INSERT INTO places (name, geom) VALUES
('Moscow', ST_MakePoint(37.6176, 55.7558)),
('Saint Petersburg', ST_MakePoint(30.3141, 59.9386)),
('Kazan', ST_MakePoint(49.1221, 55.7961));CREATE INDEX idx_places_geom ON places USING GIST (geom);-- Поиск в прямоугольнике (&& — пересекается)
SELECT * FROM places
WHERE geom && ST_MakeEnvelope(37, 55, 38, 56, 4326);
-- Поиск в радиусе (DWithin — в пределах расстояния)
SELECT * FROM places
WHERE ST_DWithin(geom, ST_MakePoint(37.6, 55.7), 10000); -- 10 км
-- Ближайшие объекты (<-> — расстояние длясортировки)
SELECT name, geom
FROM places
ORDER BY geom <-> ST_MakePoint(37.6, 55.7)
LIMIT 5;
-- Расстояние между точками
SELECT
name,
ST_Distance(geom, ST_MakePoint(37.6, 55.7)) as distance
FROM places;| Оператор | Описание | Пример |
|---|---|---|
&& | Пересекается | geom && box |
@> | Содержит | polygon @> point |
<@ | Содержится в | point <@ polygon |
<-> | Расстояние (для ORDER BY) | ORDER BY geom <-> point |
ST_DWithin | В пределах расстояния | ST_DWithin(geom, point, 1000) |
| Тип | Описание |
|---|---|
tsrange | Диапазон timestamp |
tstzrange | Диапазон timestamp с timezone |
int4range | Диапазон integer |
int8range | Диапазон bigint |
numrange | Диапазон numeric |
CREATE TABLE bookings (
id BIGSERIAL PRIMARY KEY,
room_id INTEGER,
during tsrange
);
INSERT INTO bookings (room_id, during) VALUES
(1, tsrange('2026-01-01 10:00', '2026-01-01 12:00')),
(1, tsrange('2026-01-01 14:00', '2026-01-01 16:00')),
(2, tsrange('2026-01-01 10:00', '2026-01-01 11:00'));CREATE INDEX idx_bookings_during ON bookings USING GIST (during);-- Пересечение диапазонов (&&)
SELECT * FROM bookings
WHERE during && tsrange('2026-01-01 11:00', '2026-01-01 13:00');
-- Содержит ли диапазон значение (@>)
SELECT * FROM bookings
WHERE during @> '2026-01-01 11:30'::timestamp;
-- Содержится ли диапазон в другом (<@)
SELECT * FROM bookings
WHERE tsrange('2026-01-01 10:30', '2026-01-01 11:30') <@ during;
-- Смежные диапазоны (-|-)
SELECT * FROM bookings
WHERE during -|- tsrange('2026-01-01 12:00', '2026-01-01 14:00');CREATE TABLE room_bookings (
room_id INTEGER,
during tsrange,
EXCLUDE USING GIST (
room_id WITH =,
during WITH &&
)
);
-- ✅ Первая бронь
INSERT INTO room_bookings (room_id, during)
VALUES (1, tsrange('2026-01-01 10:00', '2026-01-01 12:00'));
-- ❌ Пересечение — ошибка
INSERT INTO room_bookings (room_id, during)
VALUES (1, tsrange('2026-01-01 11:00', '2026-01-01 13:00'));
-- ERROR: conflicting key value violates exclusion constraintCREATE EXTENSION pg_trgm;CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255)
);
INSERT INTO users (name) VALUES
('John Smith'),
('Jonathan Doe'),
('Jane Wilson'),
('Bob Johnson');CREATE INDEX idx_users_name_trgm ON users USING GIST (name gist_trgm_ops);-- Нечёткое совпадение (% — similarity)
SELECT * FROM users WHERE name % 'jon';
-- Найдёт: John, Jonathan
-- Расстояние Левенштейна (<->)
SELECT name, name <-> 'jon' as distance
FROM users
ORDER BY name <-> 'jon'
LIMIT 5;
-- Порог схожести
SET pg_trgm.similarity_threshold = 0.3;
SELECT * FROM users WHERE name % 'jon';CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
title VARCHAR(255),
content TEXT,
search_vector tsvector
);
-- Заполнение search_vector
UPDATE documents
SET search_vector = to_tsvector('russian', title || ' ' || content);CREATE INDEX idx_docs_search ON documents USING GIST (search_vector);SELECT * FROM documents
WHERE search_vector @@ to_tsquery('russian', 'база & данных');| Характеристика | GIN | GiST |
|---|---|---|
| Скорость поиска | Быстрее | Медленнее |
| Скорость вставки | Медленнее | Быстрее |
| Размер индекса | Больше | Меньше |
| Поддержка ранжирования | ✅ Да | ✅ Да |
| Обновление в реальном времени | ❌ Хуже | ✅ Лучше |
Рекомендация: GIN для статичных данных, GiST для часто обновляемых.
CREATE EXTENSION vector;CREATE TABLE embeddings (
id BIGSERIAL PRIMARY KEY,
embedding vector(384), -- 384-мерный вектор
metadata JSONB
);-- Для косинусного расстояния
CREATE INDEX idx_embeddings_embedding ON embeddings
USING GIST (embedding vector_cosine_ops);
-- Для L2-расстояния
CREATE INDEX idx_embeddings_l2 ON embeddings
USING GIST (embedding vector_l2_ops);-- Поиск ближайших соседей (KNN)
SELECT id, metadata, embedding <-> '[0.1, 0.2, ...]'::vector as distance
FROM embeddings
ORDER BY embedding <-> '[0.1, 0.2, ...]'::vector
LIMIT 10;
-- Поиск в пределах расстояния
SELECT * FROM embeddings
WHERE embedding <-> '[0.1, 0.2, ...]'::vector < 0.5;EXPLAIN ANALYZE
SELECT * FROM places
WHERE geom && ST_MakeEnvelope(37, 55, 38, 56, 4326);Без индекса:
Seq Scan on places
Filter: (geom && ...)
Execution Time: 15.2 ms
С GiST:
Bitmap Heap Scan on places
Recheck Cond: (geom && ...)
-> Bitmap Index Scan on idx_places_geom
Index Cond: (geom && ...)
Execution Time: 0.8 ms
-- GiST поддерживает индексно-упорядоченное сканирование
EXPLAIN ANALYZE
SELECT * FROM places
ORDER BY geom <-> ST_MakePoint(37.6, 55.7)
LIMIT 10;Index Scan using idx_places_geom on places
Order By: (geom <-> ...)
Execution Time: 0.5 ms
Без KNN: потребовалось бы вычислить расстояние до всех строк и отсортировать.
| Тип данных | GiST размер |
|---|---|
| geometry(POINT) | ~2x от данных |
| tsrange | ~1.5x от данных |
| text (trigram) | ~2.5x от данных |
| vector(384) | ~3x от данных |
-- Меньший fillfactor для частых обновлений
CREATE INDEX idx ON table USING GIST (column) WITH (fillfactor = 70);-- Размер буфера для вставки (GiST)
SET gist_buffers = '16MB'; -- По умолчанию зависит от shared_buffersSELECT
indexname,
pg_size_pretty(pg_relation_size(indexname::regclass)) as size
FROM pg_indexes
WHERE tablename = 'places';SELECT
indexrelname,
idx_scan,
idx_tup_read
FROM pg_stat_user_indexes
WHERE relname = 'places';-- Проверка целостности
SELECT pg_indexam_has_property('gist'::regtype, 'can_order');CREATE TABLE stores (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255),
location GEOGRAPHY(POINT, 4326)
);
CREATE INDEX idx_stores_location ON stores USING GIST (location);
-- Магазины в радиусе 5 км
SELECT name, ST_Distance(location, ST_MakePoint(37.6, 55.7)::geography) as distance
FROM stores
WHERE ST_DWithin(location, ST_MakePoint(37.6, 55.7)::geography, 5000)
ORDER BY distance;CREATE TABLE room_reservations (
room_id INTEGER,
during tsrange,
EXCLUDE USING GIST (
room_id WITH =,
during WITH &&
)
);
-- Проверка доступности
SELECT * FROM room_reservations
WHERE room_id = 1
AND during && tsrange('2026-01-01 14:00', '2026-01-01 16:00');CREATE TABLE products (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255),
embedding vector(512)
);
CREATE INDEX idx_products_embedding ON products
USING GIST (embedding vector_cosine_ops);
-- Похожие товары
SELECT name, 1 - (embedding <=> '[...]'::vector) as similarity
FROM products
ORDER BY embedding <=> '[...]'::vector
LIMIT 10;Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.