Полнотекстовый поиск в PostgreSQL за миллисекунды
Download
Report
Transcript Полнотекстовый поиск в PostgreSQL за миллисекунды
Полнотекстовый поиск в PostgreSQL
за миллисекунды
Коротков А.Е, Бартунов О.С.
Полнотекстовый поиск в базе
данных: задача
Найти документы, которые
удовлетворяют запросу
Вернуть результаты в порядке
релевантности
Полнотестовый поиск в базе
данных: требования
Интеграция с ядром СУБД
Поддержка транзакций
Конкуретность, recovery
Обновление индекса «online»
Поддержка языка
Расширяемость, масштабируемость
Что такое документ?
Произвольный текстовый атрибут
Комбинация текстовых атрибутов
Может быть полностью вирутальным.
Например, результатом SQL объединения
таблиц doc и autor
Title || Abstract || Keywords || Body || Author
Операторы полнотекстового поиска
Традиционные FTS операторы для
атрибутов LIKE, ILIKE, ~, ~*
Проблемы
– Отсутствие поддержки языка (стемминг, стоп
слова)
– Отсутствие ранжирования
– Последовательное сканирование документов
Решение
– Предварительная обработка документов
– Поддержка индексов
FTS в PostgreSQL
набор правил по преобразованию
документа в его FTS представление –
tsvector, tsquery
набор функций для получения tsvector,
tsquery из текста
FTS операторы и индексы
функции ранжирования, подсветки
результатов
FTS в PostgreSQL
=# select 'a fat cat sat on a mat and ate a fat rat'::tsvector
@@
'cat & rat':: tsquery;
– tsvector – представление документа,
оптимизированное для поиска
• отсортированный массив лексем
• позиции и вес лексем
– tsquery – тип данные для полнотекстового запроса
• булевы операторы - & | ! ()
– поисковый оператор
tsvector @@ tsquery
Возможности FTS
Полная интеграция PostgreSQL
27 встроенный конфигураций для 10 языков
Поддержка пользовательских конфигураций
Встраиваемые словари (ispell, snowball, thesaurus),
парсеры
Ранжирование по релевантности
GiST и GIN индексы с поддержкой concurrency и
recovery
Богатый язык запросов с поддержкой
перезаписывания запросов
FTS в PostgreSQL
OpenFTS — 2000, Pg как хранилище
GiST index — 2000, спасибо Rambler
Tsearch — 2001, contrib:без ранжирования
Tsearch2 — 2003, contrib:config
GIN —2006, спасибо JFG Networks
FTS — 2006, в ядре, спасибо EnterpriseDB
E-FTS — Enterprise FTS, спасибо???
Накладные расходы
на ACID велики
Внешние решения: Sphinx, Solr, Lucene....
Скачивание БД в «поисковый движок»
(задержка)
Затруднен доступ к атрибутам
Дополнительная сложность
НО: Очень быстро !
Можно ли ускорить встроенный FTS ?
Можно ли ускорить
встроенный FTS ?
1.Поиск релевантных документов: Index
scan — как правило, довольно быстро
2.Расчет релевантности: Heap scan —
как правило, медленно
3.Сортировка документов
Можно ли ускорить
встроенный FTS ?
156676 статей Wikipedia:
postgres=# explain analyze
SELECT docid, ts_rank(text_vector, to_tsquery('english', 'title')) AS rank
FROM ti2
WHERE text_vector @@ to_tsquery('english', 'title')
ORDER BY rank DESC
LIMIT 3;
Limit (cost=8087.40..8087.41 rows=3 width=282) (actual time=433.750..433.752 rows=
-> Sort (cost=8087.40..8206.63 rows=47692 width=282) (actual time=433.749..433.
Sort Key: (ts_rank(text_vector, '''titl'''::tsquery))
Sort Method: top-N heapsort Memory: 25kB
-> Bitmap Heap Scan on ti2 (cost=529.61..7470.99 rows=47692 width=282) (a
Recheck Cond: (text_vector @@ '''titl'''::tsquery)
-> Bitmap Index Scan on ti2_index (cost=0.00..517.69 rows=47692 wid
Index Cond: (text_vector @@ '''titl'''::tsquery)
Total runtime: 433.787 ms
Можно ли ускорить
встроенный FTS ?
156676 статей Wikipedia:
postgres=# explain analyze
SELECT docid, ts_rank(text_vector, to_tsquery('english', 'title')) AS rank
FROM ti2
WHERE text_vector @@ to_tsquery('english', 'title')
ORDER BY text_vector>< plainto_tsquery('english','title')
LIMIT 3;
Если бы был такой план
Limit
->
(cost=20.00..21.65 rows=3 width=282) (actual time=18.376..18.427 rows=3 loops
Index Scan using ti2_index on ti2 (cost=20.00..26256.30 rows=47692 width=282
Index Cond: (text_vector @@ '''titl'''::tsquery)
Order By: (text_vector >< '''titl'''::tsquery)
Total runtime: 18.511 ms
то было бы неплохо!
Было бы неплохо
Обучить индекс (GIN) считать
релевантность и возвращать документы
упорядоченно
Хранить позиции лексем в индесу —
больше не нужна колонка tsvecotr
Использовать компрессию
Изменить алгоритмы и интерфейсы
Оптимизировать случай
редкое_слово & частое_слово
Инвертированный индекс
Инвертированный индекс
QUERY: compensation accelerometers
INDEX: accelerometers
5,10,25,28,30,36,58,59,61,73,74
RESULT:
30
compensation
30,68
Инвертированный индекс в PostgreSQL
E
N
T
R
Y
Posting list
Posting tree
T
R
E
E
Нет позиционной информации в индексе !
Список изменений
• GIN
– способ хранения
– алгоритм поиска
– поддержка ORDER BY
– изменения интерфейса
• Планировщик
Изменение структуры GIN
Дополнительная информация
(позиции слов)
ItemPointer
typedef struct ItemPointerData
{
BlockIdData ip_blkid;
OffsetNumber ip_posid;
}
typedef struct BlockIdData
{
uint16
bi_hi;
uint16
bi_lo;
} BlockIdData;
6 bytes
WordEntryPos
/*
* Equivalent to
* typedef struct {
*
uint16
*
weight:2,
*
pos:14;
* }
*/
typedef uint16 WordEntryPos;
2 bytes
Varbyte сжатие BlockIdData
Varbyte сжатие OffsetNumber
O0-O15 – биты OffsetNumber
N – NULL бит дополнительной информаци
Varbyte сжатие WordEntryPos
P0-P13 – биты позиции
W0,W1 – биты веса
Пример
Top-N запросы
1. Сканирование + вычисление
релевантности
2. Сортировка
3. Возвращение результатов по
одному с помощью gingettuple
Быстрое сканирование
entry1 && entry2
Изменения интерфейса GIN
extractValue
Datum *extractValue
(
Datum itemValue,
int32 *nkeys,
bool **nullFlags,
Datum *addInfo,
bool *addInfoIsNull
)
extractQuery
Datum *extractValue
(
Datum query,
int32 *nkeys,
StrategyNumber n,
bool **pmatch,
Pointer **extra_data,
bool **nullFlags,
int32 *searchMode,
???bool **required???
)
consistent
bool consistent
(
bool check[],
StrategyNumber n,
Datum query,
int32 nkeys,
Pointer extra_data[],
bool *recheck,
Datum queryKeys[],
bool nullFlags[],
Datum addInfo[],
bool addInfoIsNull[]
)
calcRank
float8 calcRank
(
bool check[],
StrategyNumber n,
Datum query,
int32 nkeys,
Pointer extra_data[],
bool *recheck,
Datum queryKeys[],
bool nullFlags[],
Datum addInfo[],
bool addInfoIsNull[]
)
???joinAddInfo???
Datum joinAddInfo
(
Datum addInfos[]
)
Оптимзация для
планировщика
До
test=# EXPLAIN (ANALYZE, VERBOSE) SELECT * FROM test
ORDER BY slow_func(x,y) LIMIT 10;
---------------------------------------------------Limit (cost=0.00..3.09 rows=10 width=16) (actual t
Output: x, y, (slow_func(x, y))
-> Index Scan using test_idx on public.test (co
Output: x, y, slow_func(x, y)
Total runtime: 103.524 ms
(5 rows)
После
test=# EXPLAIN (ANALYZE, VERBOSE) SELECT * FROM test
ORDER BY slow_func(x,y) LIMIT 10;
---------------------------------------------------Limit (cost=0.00..3.09 rows=10 width=16) (actual t
Output: x, y
-> Index Scan using test_idx on public.test (co
Output: x, y
Total runtime: 0.164 ms
(5 rows)
Результаты тестирования
avito.ru: 6.7 млн. документов
С колонкой tsvector, без патча
SELECT
itemid, title
FROM
items
WHERE
fts @@ plainto_tsquery('russian',
'квартира')
ORDER BY
ts_rank(fts, plainto_tsquery('russian',
'квартира')) DESC
LIMIT
10;
С колонкой tsvector, без патча
Limit (cost=729272.24..729272.26 rows=10 width=398) (actu
Buffers: shared hit=696232
-> Sort (cost=729272.24..731294.81 rows=809028 width=3
Sort Key: (ts_rank(fts, '''квартир'''::tsquery))
Sort Method: top-N heapsort Memory: 26kB
Buffers: shared hit=696232
-> Bitmap Heap Scan on items (cost=8661.97..7117
Recheck Cond: (fts @@ '''квартир'''::tsquery
Buffers: shared hit=696232
-> Bitmap Index Scan on fts_idx (cost=0.00
Index Cond: (fts @@ '''квартир'''::tsq
Buffers: shared hit=612
Total runtime: 1871.349 ms
С колонкой tsvector, с патчем
SELECT
itemid, title
FROM
items
WHERE
fts @@ plainto_tsquery('russian',
'квартира')
ORDER BY
fts >< plainto_tsquery('russian',
'квартира')
LIMIT
10;
С колонкой tsvector, с патчем
Limit (cost=20.00..59.46 rows=10 width=4
-> Index Scan using fts_idx on items
Index Cond: (fts @@ '''квартир'''
Order By: (fts >< '''квартир'''::
Total runtime: 143.952 ms
Без колонки tsvector, без патча
SELECT itemid, title
FROM items2
WHERE (setweight(to_tsvector('russian'::regconfig
ORDER BY ts_rank((setweight(to_tsvector('russian'
LIMIT 10;
Без колонки tsvector, без патча
Limit (cost=749132.39..749132.41 rows=10 width=372) (actu
Buffers: shared hit=485458
-> Sort (cost=749132.39..751145.79 rows=805360 width=3
Sort Key: (ts_rank((setweight(to_tsvector('russian
Sort Method: top-N heapsort Memory: 26kB
Buffers: shared hit=485458
-> Bitmap Heap Scan on items2 (cost=8625.55..731
Recheck Cond: ((setweight(to_tsvector('russi
Buffers: shared hit=485458
-> Bitmap Index Scan on fts_idx2 (cost=0.0
Index Cond: ((setweight(to_tsvector('r
Buffers: shared hit=612
Total runtime: 52685.595 ms
Без колонки tsvector, с патчем
SELECT itemid, title
FROM items2
WHERE (setweight(to_tsvector('russian'::re
ORDER BY (setweight(to_tsvector('russian':
LIMIT 10;
Без колонки tsvector, с патчем
Limit (cost=20.02..59.61 rows=10 width=373) (ac
Buffers: shared hit=1556
-> Index Scan using fts_idx2 on items2 (cost
Index Cond: ((setweight(to_tsvector('rus
Order By: ((setweight(to_tsvector('russi
Buffers: shared hit=1556
Total runtime: 143.639 ms
C колонкой tsvector, без патча
SELECT
itemid, title
FROM
items
WHERE
fts @@ plainto_tsquery('russian',
'квартира арбат')
ORDER BY
ts_rank(fts, plainto_tsquery('russian',
'квартира арбат')) DESC
LIMIT
10;
C колонкой tsvector, без патча
Limit (cost=6908.03..6908.05 rows=10 width=398) (actual t
Buffers: shared hit=1314
-> Sort (cost=6908.03..6912.44 rows=1766 width=398) (a
Sort Key: (ts_rank(fts, '''квартир'' & ''арбат''':
Sort Method: top-N heapsort Memory: 26kB
Buffers: shared hit=1314
-> Bitmap Heap Scan on items (cost=61.69..6869.8
Recheck Cond: (fts @@ '''квартир'' & ''арбат
Buffers: shared hit=1314
-> Bitmap Index Scan on fts_idx (cost=0.00
Index Cond: (fts @@ '''квартир'' & ''а
Buffers: shared hit=616
Total runtime: 92.069 ms
C колонкой tsvector, с патчем
SELECT
itemid, title
FROM
items
WHERE
fts @@ plainto_tsquery('russian',
'квартира арбат')
ORDER BY
fts >< plainto_tsquery('russian',
'квартира арбат')
LIMIT
10;
C колонкой tsvector, с патчем
Limit (cost=40.00..80.22 rows=10 width=400) (ac
Buffers: shared hit=1236
-> Index Scan using fts_idx on items (cost=4
Index Cond: (fts @@ '''квартир'' & ''арб
Order By: (fts >< '''квартир'' & ''арбат
Buffers: shared hit=1236
Total runtime: 1.579 ms
avito.ru: тесты
Без
патча
С патчем
С пачем
без
tsvector
Sphinx
Размер
таблицы
6.0 GB
6.0 GB
2.87 GB
-
Размер
индекса
1.29 GB
1.27 GB
1.27 GB
1.12 GB
216 с
303 с
718 с
180 с*
3,0 млн.
42.7 млн.
42.7 млн.
32.0 мн.
Время
созадания
индекса
Запросов за
8 часов
Анонимный источник:
18 млн. документов
Анонимный источник: тесты
Без
патча
С патчем
С патчем,
без
tsvector
Sphinx
Размер
таблицы
18.2 GB
18.2 GB
11.9 GB
-
Размер
индекса
2.28 GB
2.30 GB
2.30 GB
3.09 GB
258 с
684 с
1712 с
481 с*
2.67 млн. 38.7 млн.
38.7 млн.
26.7 млн.
Время
создания
индекса
Запросов за
8 чаос
Положение дел
2 из 4 планируемых патчей на
текущем commitfest
Спасибо за внимание!