Мне нужно создать подсказку (автозаполнение) для нескольких моделей.У меня есть специальная таблица, которая содержит записи поиска ts_vector всех моделей, которые должны быть предложены.
Хотите показать максимум N результатов для каждого значения searchable_type
столбца.Так, если у нас есть, например, 4 значения типа: ['Artist', 'Artwork', 'Category', 'Nation']
и есть записи с типами, которые соответствуют поисковому запросу, результат должен быть таким, как в примере.
Пример для N = 3, запрос= 'titl':
id title search searchable_type searchable_id
1 Title 1 'title':1A 'Artist' 121
1 Titlimbo 'titlimbo':1A 'Artist' 122
1 Titlover 'titlover':1A 'Artist' 123
1 Titleart 'titleart':1A 'Artwork' 124
1 Titless 'titless':1A 'Artwork' 125
1 Titlecat 'titlecat':1A 'Category' 126
1 Titledog 'titledog':1A 'Category' 127
1 TitleNation 1 'titlenation':1A 'Nation' 128
У меня есть запрос, который выполняет эту задачу, и делает это хорошо в таблице с около 2000 записей.Но когда я решил проверить этот запрос в таблице с 150 тыс. Записей, я был неприятно удивлен.Время выполнения запроса возрастает до 24 минут!Это довольно большое время для подсказок в реальном времени.
Так что это факт, что я делаю это неправильно.Я прошу помощи в реализации таких запросов и объяснений, почему это происходит.
Создать запрос к таблице
CREATE TABLE pg_search_documents (
id bigint NOT NULL CONSTRAINT pg_search_documents_pkey PRIMARY KEY,
title character varying,
search tsvector,
searchable_type character varying,
searchable_id bigint,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
);
CREATE INDEX index_pg_search_documents_on_search ON pg_search_documents USING gin (search);
CREATE INDEX index_pg_search_documents_on_searchable_type_and_searchable_id ON pg_search_documents USING btree (searchable_type, searchable_id);
Запрос (limit = 5, search = 'fir')
SELECT DISTINCT t_outer.searchable_type, t_top.id, t_top.title, t_top.searchable_id, t_top.updated_at FROM pg_search_documents t_outer
JOIN LATERAL (
SELECT * FROM pg_search_documents t_inner
WHERE t_inner.searchable_type = t_outer.searchable_type AND ((t_inner.search) @@ (to_tsquery('simple', ''' ' || 'fir' || ' ''' || ':*')))
ORDER BY t_inner.updated_at DESC
LIMIT 5
) t_top ON true
ORDER BY t_top.updated_at DESC
Время выполнения для записей 150 тыс.до 25 минут.
EXPLAIN (ANALYZE, BUFFERS) SELECT DISTINCT t_top.id, t_top.title, t_top.searchable_id, t_top.updated_at FROM pg_search_documents AS t_outer
JOIN LATERAL (
SELECT * FROM pg_search_documents AS t_inner
WHERE t_inner.searchable_type = t_outer.searchable_type AND ((t_inner.search) @@ (to_tsquery('simple', ''' ' || 'fir' || ' ''' || ':*')))
ORDER BY t_inner.updated_at DESC
LIMIT 5
) AS t_top ON true
ORDER BY t_top.updated_at DESC;
Unique (cost=301102555.51..301111909.01 rows=5 width=60) (actual time=161305.761..161796.379 rows=10 loops=1)
Buffers: shared hit=74382891, temp read=19008 written=19046
-> Sort (cost=301102555.51..301104426.21 rows=748280 width=60) (actual time=161305.759..161616.199 rows=748065 loops=1)
Sort Key: t_inner.updated_at DESC, t_inner.id, t_inner.title, t_inner.searchable_id
Sort Method: external merge Disk: 90312kB
Buffers: shared hit=74382891, temp read=19008 written=19046
-> Nested Loop (cost=2010.95..300973275.75 rows=748280 width=60) (actual time=0.904..160242.631 rows=748065 loops=1)
Buffers: shared hit=74382891
-> Seq Scan on pg_search_documents t_outer (cost=0.00..5355.56 rows=149656 width=7) (actual time=0.008..49.066 rows=149656 loops=1)
Buffers: shared hit=3859
-> Limit (cost=2010.95..2010.96 rows=5 width=132) (actual time=1.067..1.068 rows=5 loops=149656)
Buffers: shared hit=74379032
-> Sort (cost=2010.95..2011.45 rows=201 width=132) (actual time=1.065..1.066 rows=5 loops=149656)
Sort Key: t_inner.updated_at DESC
Sort Method: top-N heapsort Memory: 26kB
Buffers: shared hit=74379032
-> Bitmap Heap Scan on pg_search_documents t_inner (cost=30.09..2007.61 rows=201 width=132) (actual time=0.338..0.803 rows=795 loops=149656)
Recheck Cond: (search @@ '''fir'':*'::tsquery)
Filter: ((searchable_type)::text = (t_outer.searchable_type)::text)
Rows Removed by Filter: 98
Heap Blocks: exact=73780408
Buffers: shared hit=74379032
-> Bitmap Index Scan on index_pg_search_documents_on_search (cost=0.00..30.04 rows=805 width=0) (actual time=0.277..0.277 rows=893 loops=149656)
Index Cond: (search @@ '''fir'':*'::tsquery)
Buffers: shared hit=598624
Planning time: 0.220 ms
Execution time: 161893.484 ms
Если изменить значение WHERE в JOIN LATERAL с t_inner.searchable_type = t_outer.searchable_type
на t_inner.searchable_type = 'Artist'
, время выполнения составит 464 мс (это нормально), но результат вывода будет неправильным (не ОК).
Объясните
Sort (cost=26369.27..26369.32 rows=20 width=67)
Sort Key: t_top.updated_at DESC
-> HashAggregate (cost=26368.64..26368.84 rows=20 width=67)
Group Key: t_top.updated_at, t_outer.searchable_type, t_top.id, t_top.title, t_top.searchable_id
-> Nested Loop (cost=2273.74..17000.20 rows=749475 width=67)
-> Seq Scan on pg_search_documents t_outer (cost=0.00..5357.95 rows=149895 width=7)
-> Materialize (cost=2273.74..2273.82 rows=5 width=60)
-> Subquery Scan on t_top (cost=2273.74..2273.80 rows=5 width=60)
-> Limit (cost=2273.74..2273.75 rows=5 width=132)
-> Sort (cost=2273.74..2275.53 rows=718 width=132)
Sort Key: t_inner.updated_at DESC
-> Bitmap Heap Scan on pg_search_documents t_inner (cost=282.23..2261.81 rows=718 width=132)
Recheck Cond: (search @@ '''fir'':*'::tsquery)
Filter: ((searchable_type)::text = 'Artwork'::text)
-> Bitmap Index Scan on index_pg_search_documents_on_search (cost=0.00..282.05 rows=806 width=0)
Index Cond: (search @@ '''fir'':*'::tsquery)
Итак, насколько я понимаю, проблема в проверке равных этого типа.
ОБНОВЛЕНИЕ
Одна из причин такого медленного запроса - возможный некорректный индекс GIN поискового столбца в таблице.Postgres рекомендует отключить его перед переносом больших данных (я этого не делал).Поэтому после удаления и создания индекса мой поисковый запрос начинает работать быстрее - 2,5 минуты на запрос - но это также огромное время.