Postgres текстовый поиск с индексом GIN и отсортированным DES C в другом столбце - PullRequest
3 голосов
/ 19 февраля 2020

В настоящее время я работаю над функцией поиска, которая заканчивает тем, что нажимает на БД с запросом LIKE. Раньше был в форме WHERE some_id = blah AND some_timestamp > blah AND (field1 LIKE '%some_text%' OR field2 LIKE '%some_text%' OR ...) ORDER BY some_timestamp DESC. Сейчас это плохо масштабируется, поскольку размер таблицы составляет десятки миллионов строк, особенно если она отфильтрована по очень старой отметке времени.

После некоторого исследования показалось, что индекс триграмм может быть более производительным для поиска текста. Поэтому я добавил индекс триграммы для всех объединенных текстовых полей и изначально получил хорошие результаты. После изменения нового запроса я обнаружил регрессию. Старый индекс (btree для some_id и some_timestamp DES C) больше не попадал. Таким образом, новый текстовый поиск помогает с некоторыми текстовыми запросами, которые раньше были очень медленными, а другие текстовые запросы, которые раньше были очень быстрыми (несколько мс) из-за индекса btree, теперь стали очень медленными (см. Ниже).

Есть ли способ получить лучшее из обоих миров? Быстрый текстовый поиск триграмм и быстрая индексация btree для запросов, которые в этом нуждаются?

Примечания:

  • Postgres 11,6

  • Я попытался использовать индекс btree_gin для индексации столбца метки времени, но получил примерно такую ​​же производительность.

  • Я немного изменил свой запрос (каскадные пробелы), чтобы обойти индекс триграммы и проверил медленные запросы возвращаются к индексу btree и <10 мс времени выполнения. </p>

  • Я попытался изменить порядок запросов, чтобы попытаться получить оба индекса безрезультатно.

Таблица:

table1
---------------------------------
some_id        | bigint
field1         | text
field2         | text
field3         | text
field4         | text
field5         | text
field6         | bigint
some_timestamp | timestamp without time zone

Индекс триграмм:

CREATE INDEX CONCURRENTLY IF NOT EXISTS trgm_idx ON table1 USING gin ((COALESCE(field1, '') || ' ' || COALESCE(field2, '') || COALESCE(field3, '') || ' ' || COALESCE(field4, '') || ' ' || COALESCE(field5, '') || ' ' || field6::text) gin_trgm_ops);

Запрос:

SELECT *
FROM table1 i
WHERE i.some_id = 1
    AND (COALESCE(field1, '') || ' ' || COALESCE(field2, '') || COALESCE(field3, '') || ' ' || COALESCE(field4, '') || ' ' || COALESCE(field5, '') || ' ' || field6::text) ILIKE '%some_text%'
    AND i.some_timestamp > '2015-01-00 00:00:00.0'
ORDER BY some_timestamp DESC limit 20;

Объяснение:

 Limit  (cost=1043.06..1043.11 rows=20 width=446) (actual time=37240.094..37240.099 rows=20 loops=1)
   ->  Sort  (cost=1043.06..1043.15 rows=39 width=446) (actual time=37240.092..37240.095 rows=20 loops=1)
         Sort Key: some_timestamp
         Sort Method: top-N heapsort  Memory: 36kB
         ->  Bitmap Heap Scan on table1 i  (cost=345.01..1042.03 rows=39 width=446) (actual time=1413.415..37202.331 rows=83066 loops=1)
               Recheck Cond: ((((((((((COALESCE(field1, ''::text) || ' '::text) || COALESCE(field2, ''::text)) || COALESCE(field3, ''::text)) || ' '::text) || COALESCE(field4, ''::text)) || ' '::text) || COALESCE(field5, ''::text)) || ' '::text) || (field6)::text) ~~* '%some_text%'::text)
               Rows Removed by Index Recheck: 23
               Filter: ((some_timestamp > '2015-01-00 00:00:00'::timestamp without time zone) AND (some_id = 1))
               Rows Removed by Filter: 5746666
               Heap Blocks: exact=395922
               ->  Bitmap Index Scan on trgm_idx  (cost=0.00..345.00 rows=667 width=0) (actual time=1325.867..1325.867 rows=5833670 loops=1)
                     Index Cond: ((((((((((COALESCE(field1, ''::text) || ' '::text) || COALESCE(field2, ''::text)) || COALESCE(field3, ''::text)) || ' '::text) || COALESCE(field4, ''::text)) || ' '::text) || COALESCE(field5, ''::text)) || ' '::text) || (field6)::text) ~~* '%some_text%'::text)
 Planning Time: 0.252 ms
 Execution Time: 37243.205 ms
(14 rows)

Ответы [ 2 ]

0 голосов
/ 20 февраля 2020

Я немного изменил свой запрос (объединенный пробел), чтобы обойти индекс триграммы, и проверил, что медленные запросы возвращаются к индексу btree и время выполнения <10 мс. </p>

Это уродливо, но это почти наверняка решение. Может быть, мы сможем исправить ситуацию, чтобы в какой-то (далекой) будущей версии PostgreSQL такие исправления не потребовались, но сегодня это вам не поможет.

Bitmap Index Scan on trgm_idx  (cost=0.00..345.00 rows=667 width=0) (actual time=1325.867..1325.867 rows=5833670 loops=1)

Эта оценка явно ненормальный, и это, вероятно, источник проблемы. Но индексы триграмм и запросы ILIKE очень чувствительны к тексту запроса. Наличие только (очевидно) анонимизированного значения '%some_text%' недостаточно для более глубокого изучения.

И другой подход будет использовать использование GiST, а не GIN. Индексы GiST могут выгодно использовать несколько столбцов одновременно.

CREATE INDEX CONCURRENTLY IF NOT EXISTS trgm_idx ON table1 USING gist
   (some_id, some_timestamp, (COALESCE(field1, '') || ' ' || COALESCE(field2, '') || COALESCE(field3, '') || ' ' || COALESCE(field4, '') || ' ' || COALESCE(field5, '') || ' ' || field6::text) gist_trgm_ops);

Возможно, вы захотите включить только один или другой из первых двух столбцов в зависимости от степени избирательности каждого из них. Возможно, вам придется провести несколько экспериментов.

Мне не нравится использовать GiST с pg_trgm, я считаю, что производительность (как при использовании, так и при сборке) errati c. Но для такого полезного многостолбцового индекса у вас нет другого выбора.

В любом случае, у вас есть индекс, который уже работает хорошо, он просто не использует его. Создание индекса GiST может быть «достаточно хорошим», чтобы отвлечь запрос от GIN, но тогда он может просто заставить какой-то другой запрос выбрать неправильный план.

Другой подход заключается в использовании индексов RUM, это позволяет вам сортировать по данным, хранящимся в индексе, но я думаю, вам придется написать код , чтобы они поддерживали pg_trgm.

0 голосов
/ 19 февраля 2020

Создайте дополнительный индекс:

CREATE INDEX ON table1 (some_id, some_timestamp);

Тогда у вас есть хороший шанс получить растровое изображение или сканирование по обоим индексам, что должно быть намного быстрее, чем при наличии более 5 миллион строк удален фильтром.

...