Показать максимум N результатов для каждого времени выполнения значения типа - PullRequest
0 голосов
/ 29 сентября 2018

Мне нужно создать подсказку (автозаполнение) для нескольких моделей.У меня есть специальная таблица, которая содержит записи поиска 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 минуты на запрос - но это также огромное время.

1 Ответ

0 голосов
/ 01 октября 2018

Решение с оконными функциями, рекомендованным a_horse_with_no_name .

У этого решения нет проблем со временем выполнения в таблице с 150k строками, и я заменю ЛАТЕРАЛЬНЫЙ запрос этим решением.

SELECT rank_filter.* FROM (
                          SELECT pg_search_documents.*,
                                 rank() OVER (
                                   PARTITION BY searchable_type
                                   ORDER BY created_at DESC
                                   )
                          FROM pg_search_documents
                          WHERE ((search) @@ (to_tsquery('simple', ''' ' || '#{query}' || ' ''' || ':*')))
                          ) rank_filter WHERE RANK <= 5

EXPLAIN (ANALYZE, BUFFERS) SELECT rank_filter.* FROM (
                          SELECT pg_search_documents.*,
                                 rank() OVER (
                                   PARTITION BY searchable_type
                                   ORDER BY created_at DESC
                                   )
                          FROM pg_search_documents
                          WHERE ((search) @@ (to_tsquery('simple', ''' ' || 'fir' || ' ''' || ':*')))
                          ) rank_filter WHERE RANK <= 5;

Subquery Scan on rank_filter  (cost=2044.61..2070.77 rows=268 width=184) (actual time=1.628..2.275 rows=10 loops=1)
  Filter: (rank_filter.rank <= 5)
  Rows Removed by Filter: 883
  Buffers: shared hit=497
  ->  WindowAgg  (cost=2044.61..2060.71 rows=805 width=184) (actual time=1.627..2.206 rows=893 loops=1)
        Buffers: shared hit=497
        ->  Sort  (cost=2044.61..2046.62 rows=805 width=176) (actual time=1.622..1.684 rows=893 loops=1)
              Sort Key: pg_search_documents.searchable_type, pg_search_documents.created_at DESC
              Sort Method: quicksort  Memory: 417kB
              Buffers: shared hit=497
              ->  Bitmap Heap Scan on pg_search_documents  (cost=30.24..2005.75 rows=805 width=176) (actual time=0.300..1.007 rows=893 loops=1)
                    Recheck Cond: (search @@ '''fir'':*'::tsquery)
                    Heap Blocks: exact=493
                    Buffers: shared hit=497
                    ->  Bitmap Index Scan on index_pg_search_documents_on_search  (cost=0.00..30.04 rows=805 width=0) (actual time=0.251..0.251 rows=893 loops=1)
                          Index Cond: (search @@ '''fir'':*'::tsquery)
                          Buffers: shared hit=4
Planning time: 0.180 ms
Execution time: 2.317 ms
...