У меня есть таблица, которая содержит данные, извлеченные из текстовых документов. Данные хранятся в столбце, "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"
ORDER BY "RANK" DESC
. Я хотел бы исследоватьpg_trgm
с индексом GiST и операторов сходства / расстояния в качестве альтернативы. Рассмотрим: dba.stackexchange.com/questions/56224/… . Может даже привести к «лучшим» результатам (помимо того, что быстрее).explain (analyze, buffers)
, предпочтительно с track_io_timing, установленным вON
? Нет никакого способа, которым может потребоваться 520 секунд для последующего сканирования этой таблицы, если только она не хранится на RAID-диске с гибкими дисками. Там что-то определенно патологическое. Кроме того, каковы ваши настройкиrandom_page_cost
и другие параметры стоимости?Ответы:
Сомнительный вариант использования
Текстовая строка, которая является одинаковой для всех строк, является просто мертвым грузом. Удалите это и объедините это в представлении, если вам нужно показать это.
Очевидно, вы знаете об этом:
Обновите свою версию Postgres
Находясь на Postgres 9.3, вы должны хотя бы обновить его до последней версии (в настоящее время 9.3.9). Официальная рекомендация проекта:
Более того, обновитесь до 9.4, который получил значительные улучшения для индексов GIN .
Основная проблема 1: оценка стоимости
Стоимость некоторых функций textsearch была серьезно недооценена до версии 9.4 включительно. Эта стоимость увеличена в 100 раз в следующей версии 9.5, как @jjanes описывает в своем недавнем ответе:
Вот соответствующая ветка, где это обсуждалось, и сообщение о коммите Тома Лейна.
Как вы можете видеть в сообщении фиксации,
to_tsvector()
это одна из этих функций. Вы можете применить изменения немедленно (как суперпользователь):что должно значительно повысить вероятность использования вашего функционального индекса.
Основная проблема 2: КНН
Основная проблема заключается в том, что Postgres должен вычислить ранг
ts_rank()
для 260k строк (rows=261011
), прежде чем он сможет упорядочить и выбрать топ 5. Это будет дорого , даже после того, как вы исправите другие проблемы, как обсуждалось. Это проблема K-ближайшего соседа (KNN) по своей природе, и есть решения для связанных случаев. Но я не могу придумать общего решения для вашего случая, так как само вычисление ранга зависит от ввода пользователя. Я бы постарался исключить большую часть матчей с низким рейтингом на ранней стадии, так что полный расчет нужно сделать только для нескольких хороших кандидатов.Один из способов, который я могу придумать, - это объединить ваш полнотекстовый поиск с поиском схожести по триграммам, который предлагает работающую реализацию проблемы KNN. Таким образом, вы можете предварительно выбрать «лучшие» совпадения с
LIKE
предикатом в качестве кандидатов (в подзапросе,LIMIT 50
например, с), а затем выбрать 5 строк с самым высоким рейтингом в соответствии с вашим вычислением ранга в основном запросе.Или примените оба предиката в одном и том же запросе и выберите наиболее близкие совпадения в соответствии со сходством триграмм (что приведет к разным результатам), как в следующем ответе:
Я провел еще несколько исследований, и вы не первый, кто столкнулся с этой проблемой. Похожие посты на pgsql-general:
Продолжается работа по внедрению
tsvector <-> tsquery
оператора.Олег Бартунов и Александр Коротков даже представили рабочий прототип (используя в
><
качестве оператора вместо того<->
), но его очень сложно интегрировать в Postgres, вся инфраструктура для индексов GIN должна быть переработана (большая часть которой уже сделана).Основная проблема 3: вес и индекс
И я определил еще один фактор, добавляющий медлительности запроса. По документации:
Жирный акцент мой. Как только вес учитывается, каждая строка должна быть извлечена из кучи (а не просто дешевой проверки видимости), а длинные значения должны быть обнулены, что увеличивает стоимость. Но, похоже, для этого есть решение:
Определение индекса
Глядя на ваш индекс еще раз, кажется, для начала нет смысла. Вы присваиваете вес одному столбцу, который не имеет смысла, если вы не объединяете другие столбцы с другим весом.
COALESCE()
также не имеет смысла, если вы на самом деле не объединяете больше столбцов.Упростите ваш индекс:
И ваш запрос:
Все еще дорого для поискового запроса, который соответствует каждой строке, но, вероятно, много меньше.
Asides
Все эти проблемы, вместе взятые, безумная стоимость 520 секунд для вашего второго запроса начинает обретать смысл. Но могут быть и другие проблемы. Вы настроили свой сервер?
Все обычные советы по оптимизации производительности применяются.
Это облегчит вашу жизнь, если вы не будете работать с двойными кавычками идентификаторов CaMeL-case:
источник
USING gin (to_tsvector('english', "CONTENT")
У меня была похожая проблема. Я позаботился об этом, предварительно вычислив ts_rank каждого популярного термина текстового запроса в поле: кортеж таблицы и сохранив его в таблице поиска. Это сэкономило мне много времени (в 40 раз) при поиске популярных слов в тексте.
Запрос: посмотрите эту таблицу и получите отсортированные идентификаторы документов по их рангу. если не там, сделай это по-старому.
источник