Медленный полнотекстовый поиск по терминам с высокой частотой встречаемости

8

У меня есть таблица, которая содержит данные, извлеченные из текстовых документов. Данные хранятся в столбце, "CONTENT"для которого я создал этот индекс, используя GIN:

CREATE INDEX "File_contentIndex"
  ON "File"
  USING gin
  (setweight(to_tsvector('english'::regconfig
           , COALESCE("CONTENT", ''::character varying)::text), 'C'::"char"));

Я использую следующий запрос для выполнения полнотекстового поиска в таблице:

SELECT "ITEMID",
  ts_rank(setweight(to_tsvector('english', coalesce("CONTENT",'')), 'C') , 
  plainto_tsquery('english', 'searchTerm')) AS "RANK"
FROM "File"
WHERE setweight(to_tsvector('english', coalesce("CONTENT",'')), 'C') 
  @@ plainto_tsquery('english', 'searchTerm')
ORDER BY "RANK" DESC
LIMIT 5;

Таблица File содержит 250 000 строк, и каждая "CONTENT"запись состоит из одного случайного слова и текстовой строки, которая одинакова для всех строк.

Теперь, когда я ищу случайное слово (1 попадание во всей таблице), запрос выполняется очень быстро (<100 мс). Однако, когда я ищу слово, которое присутствует во всех строках, запрос выполняется очень медленно (10 минут и более).

EXPLAIN ANALYZEпоказывает, что для поиска с 1 попаданием выполняется сканирование индекса растрового изображения, а затем сканирование кучи растрового изображения . Для медленного поиска вместо этого выполняется Seq Scan , что занимает так много времени.

Конечно, нереально иметь одинаковые данные во всех строках. Но так как я не могу контролировать текстовые документы, загружаемые пользователями, и поиск, который они выполняют, возможно, возникает подобный сценарий (поиск по терминам с очень высокой частотой в БД). Как я могу увеличить производительность моего поискового запроса для такого сценария?

Запуск PostgreSQL 9.3.4

Планы запроса от EXPLAIN ANALYZE:

Быстрый поиск (1 попадание в БД)

"Limit  (cost=2802.89..2802.90 rows=5 width=26) (actual time=0.037..0.037 rows=1 loops=1)"
"  ->  Sort  (cost=2802.89..2806.15 rows=1305 width=26) (actual time=0.037..0.037 rows=1 loops=1)"
"        Sort Key: (ts_rank(setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char"), '''wfecg'''::tsquery))"
"        Sort Method: quicksort  Memory: 25kB"
"        ->  Bitmap Heap Scan on "File"  (cost=38.12..2781.21 rows=1305 width=26) (actual time=0.030..0.031 rows=1 loops=1)"
"              Recheck Cond: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''wfecg'''::tsquery)"
"              ->  Bitmap Index Scan on "File_contentIndex"  (cost=0.00..37.79 rows=1305 width=0) (actual time=0.012..0.012 rows=1 loops=1)"
"                    Index Cond: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''wfecg'''::tsquery)"
"Total runtime: 0.069 ms"

Медленный поиск (250 тыс. Просмотров в БД)

"Limit  (cost=14876.82..14876.84 rows=5 width=26) (actual time=519667.404..519667.405 rows=5 loops=1)"
"  ->  Sort  (cost=14876.82..15529.37 rows=261017 width=26) (actual time=519667.402..519667.402 rows=5 loops=1)"
"        Sort Key: (ts_rank(setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char"), '''cyberspace'''::tsquery))"
"        Sort Method: top-N heapsort  Memory: 25kB"
"        ->  Seq Scan on "File"  (cost=0.00..10541.43 rows=261017 width=26) (actual time=2.097..519465.953 rows=261011 loops=1)"
"              Filter: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''cyberspace'''::tsquery)"
"              Rows Removed by Filter: 6"
"Total runtime: 519667.429 ms"
Danjo
источник
1
От всей души: индексы GIN получили значительные улучшения в Postgres 9.4 (и некоторые другие в предстоящем 9.5). Это, безусловно, заплатит за обновление до текущего 9.4. И я бы также исследовал производительность GiST вместо индекса GIN. Виновник в вашем запросе ORDER BY "RANK" DESC. Я хотел бы исследовать pg_trgmс индексом GiST и операторов сходства / расстояния в качестве альтернативы. Рассмотрим: dba.stackexchange.com/questions/56224/… . Может даже привести к «лучшим» результатам (помимо того, что быстрее).
Эрвин Брандштеттер,
На какой ОС вы используете свой экземпляр PostgreSQL?
Кассандри
Можете ли вы повторить это explain (analyze, buffers), предпочтительно с track_io_timing, установленным в ON? Нет никакого способа, которым может потребоваться 520 секунд для последующего сканирования этой таблицы, если только она не хранится на RAID-диске с гибкими дисками. Там что-то определенно патологическое. Кроме того, каковы ваши настройки random_page_costи другие параметры стоимости?
Джанес
@danjo Я сталкиваюсь с такими же проблемами, даже когда я не использую порядок. Можете ли вы сказать мне, как вы это исправили?
Сахил

Ответы:

11

Сомнительный вариант использования

... каждая запись CONTENT состоит из одного случайного слова и текстовой строки, которая одинакова для всех строк.

Текстовая строка, которая является одинаковой для всех строк, является просто мертвым грузом. Удалите это и объедините это в представлении, если вам нужно показать это.

Очевидно, вы знаете об этом:

Конечно, это нереально ... Но так как я не могу контролировать текст ...

Обновите свою версию Postgres

Запуск PostgreSQL 9.3.4

Находясь на Postgres 9.3, вы должны хотя бы обновить его до последней версии (в настоящее время 9.3.9). Официальная рекомендация проекта:

Мы всегда рекомендуем всем пользователям запускать последний доступный вспомогательный выпуск для любой используемой основной версии.

Более того, обновитесь до 9.4, который получил значительные улучшения для индексов GIN .

Основная проблема 1: оценка стоимости

Стоимость некоторых функций textsearch была серьезно недооценена до версии 9.4 включительно. Эта стоимость увеличена в 100 раз в следующей версии 9.5, как @jjanes описывает в своем недавнем ответе:

Вот соответствующая ветка, где это обсуждалось, и сообщение о коммите Тома Лейна.

Как вы можете видеть в сообщении фиксации, to_tsvector()это одна из этих функций. Вы можете применить изменения немедленно (как суперпользователь):

ALTER FUNCTION to_tsvector (regconfig, text) COST 100;

что должно значительно повысить вероятность использования вашего функционального индекса.

Основная проблема 2: КНН

Основная проблема заключается в том, что Postgres должен вычислить ранг ts_rank()для 260k строк ( rows=261011), прежде чем он сможет упорядочить и выбрать топ 5. Это будет дорого , даже после того, как вы исправите другие проблемы, как обсуждалось. Это проблема K-ближайшего соседа (KNN) по своей природе, и есть решения для связанных случаев. Но я не могу придумать общего решения для вашего случая, так как само вычисление ранга зависит от ввода пользователя. Я бы постарался исключить большую часть матчей с низким рейтингом на ранней стадии, так что полный расчет нужно сделать только для нескольких хороших кандидатов.

Один из способов, который я могу придумать, - это объединить ваш полнотекстовый поиск с поиском схожести по триграммам, который предлагает работающую реализацию проблемы KNN. Таким образом, вы можете предварительно выбрать «лучшие» совпадения с LIKEпредикатом в качестве кандидатов (в подзапросе, LIMIT 50например, с), а затем выбрать 5 строк с самым высоким рейтингом в соответствии с вашим вычислением ранга в основном запросе.

Или примените оба предиката в одном и том же запросе и выберите наиболее близкие совпадения в соответствии со сходством триграмм (что приведет к разным результатам), как в следующем ответе:

Я провел еще несколько исследований, и вы не первый, кто столкнулся с этой проблемой. Похожие посты на pgsql-general:

Продолжается работа по внедрению tsvector <-> tsqueryоператора.

Олег Бартунов и Александр Коротков даже представили рабочий прототип (используя в ><качестве оператора вместо того <->), но его очень сложно интегрировать в Postgres, вся инфраструктура для индексов GIN должна быть переработана (большая часть которой уже сделана).

Основная проблема 3: вес и индекс

И я определил еще один фактор, добавляющий медлительности запроса. По документации:

Индексы GIN не являются потерями для стандартных запросов, но их производительность логарифмически зависит от количества уникальных слов. ( Однако индексы GIN хранят только слова (лексемы) tsvectorзначений, а не их весовые метки. Таким образом, повторная проверка строки таблицы необходима при использовании запроса с весами.)

Жирный акцент мой. Как только вес учитывается, каждая строка должна быть извлечена из кучи (а не просто дешевой проверки видимости), а длинные значения должны быть обнулены, что увеличивает стоимость. Но, похоже, для этого есть решение:

Определение индекса

Глядя на ваш индекс еще раз, кажется, для начала нет смысла. Вы присваиваете вес одному столбцу, который не имеет смысла, если вы не объединяете другие столбцы с другим весом.

COALESCE() также не имеет смысла, если вы на самом деле не объединяете больше столбцов.

Упростите ваш индекс:

CREATE INDEX "File_contentIndex" ON "File" USING gin
(to_tsvector('english', "CONTENT");

И ваш запрос:

SELECT "ITEMID", ts_rank(to_tsvector('english', "CONTENT")
                       , plainto_tsquery('english', 'searchTerm')) AS rank
FROM   "File"
WHERE  to_tsvector('english', "CONTENT")
       @@ plainto_tsquery('english', 'searchTerm')
ORDER  BY rank DESC
LIMIT  5;

Все еще дорого для поискового запроса, который соответствует каждой строке, но, вероятно, много меньше.

Asides

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

Это облегчит вашу жизнь, если вы не будете работать с двойными кавычками идентификаторов CaMeL-case:

Эрвин Брандштеттер
источник
Я тоже сталкиваюсь с этим. В Postgresql 9.6 мы используем переписывание запросов для синонимов, поэтому я не думаю, что поиск по сходству триграмм для ограничения количества документов будет работать хорошо.
Фолли
Удивительно! USING gin (to_tsvector('english', "CONTENT")
K-Gun
1

У меня была похожая проблема. Я позаботился об этом, предварительно вычислив ts_rank каждого популярного термина текстового запроса в поле: кортеж таблицы и сохранив его в таблице поиска. Это сэкономило мне много времени (в 40 раз) при поиске популярных слов в тексте.

  1. Получить популярные слова в корпусе, сканируя документ и подсчитывая его появления.
  2. Сортировать по самому популярному слову.
  3. предварительно вычислите ts_rank популярных слов и сохраните его в таблице.

Запрос: посмотрите эту таблицу и получите отсортированные идентификаторы документов по их рангу. если не там, сделай это по-старому.

Пари Раджарам
источник