Два столбца, объем, произвольный доступ из разреженной таблицы с использованием PostgreSQL - PullRequest
3 голосов
/ 06 июля 2011

Я храню относительно разумное (~ 3 миллиона) количество очень маленьких строк (вся БД ~ 300 МБ) в PostgreSQL. Данные организованы таким образом:

                                      Table "public.tr_rating"
  Column   |           Type           |                           Modifiers                           
-----------+--------------------------+---------------------------------------------------------------
 user_id   | bigint                   | not null
 place_id  | bigint                   | not null
 rating    | smallint                 | not null
 rated_at  | timestamp with time zone | not null default now()
 rating_id | bigint                   | not null default nextval('tr_rating_rating_id_seq'::regclass)
Indexes:
    "tr_rating_rating_id_key" UNIQUE, btree (rating_id)
    "tr_rating_user_idx" btree (user_id, place_id)

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

Естественный запрос, который я написал:

SELECT * FROM tr_rating WHERE user_id=ANY(?) AND place_id=ANY(?)

Размер массива user_id ~ 500, а массива place_id ~ 10000

Это превращается в:

 Bitmap Heap Scan on tr_rating  (cost=2453743.43..2492013.53 rows=3627 width=34) (actual time=10174.044..10174.234 rows=1111 loops=1)
 Buffers: shared hit=27922214
     ->  Bitmap Index Scan on tr_rating_user_idx  (cost=0.00..2453742.53 rows=3627 width=0) (actual time=10174.031..10174.031 rows=1111 loops=1)
         Index Cond: ((user_id = ANY (...) ))
         Buffers: shared hit=27922214
 Total runtime: 10279.290 ms

Первая подозрительная вещь, которую я здесь вижу, состоит в том, что, по ее оценкам, сканирование индекса на 500 пользователей займет 2,5 млн. Запросов на диск

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

 public | tr_rating_user_idx | index | tr_rating | 67 MB | 

при 67 МБ, я ожидал бы, что он может прорваться через индекс за тривиальное время, даже если он должен делать это последовательно. Как показывает учет буферов из EXPLAIN ANALYZE, все уже находится в памяти (поскольку все значения, кроме shared_hit, равны нулю и, следовательно, подавлены).

Я пробовал различные комбинации REINDEX, VACUUM, ANALYZE и CLUSTER без каких-либо ощутимых улучшений.

Есть какие-нибудь мысли о том, что я здесь делаю неправильно, или как я мог бы отлаживать дальше? Я озадачен; 67 МБ данных - это ничтожная сумма, чтобы тратить так много времени на поиск ...

Для справки: аппаратное обеспечение представляет собой новейший Xeon с 8 путями с 8 дисками по 15K 300 ГБ в RAID-10. Должно быть достаточно: -)

EDIT

По предложению btilly я опробовал временные таблицы:

 => explain analyze select * from tr_rating NATURAL JOIN user_ids NATURAL JOIN place_ids;
                                                                      QUERY PLAN                                                                      
------------------------------------------------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=49133.46..49299.51 rows=3524 width=34) (actual time=13.801..15.676 rows=1111 loops=1)
   Hash Cond: (place_ids.place_id = tr_rating.place_id)
   ->  Seq Scan on place_ids  (cost=0.00..59.66 rows=4066 width=8) (actual time=0.009..0.619 rows=4251 loops=1)
   ->  Hash  (cost=48208.02..48208.02 rows=74035 width=34) (actual time=13.767..13.767 rows=7486 loops=1)
         Buckets: 8192  Batches: 1  Memory Usage: 527kB
         ->  Nested Loop  (cost=0.00..48208.02 rows=74035 width=34) (actual time=0.047..11.055 rows=7486 loops=1)
               ->  Seq Scan on user_ids  (cost=0.00..31.40 rows=2140 width=8) (actual time=0.006..0.399 rows=2189 loops=1)
               ->  Index Scan using tr_rating_user_idx on tr_rating  (cost=0.00..22.07 rows=35 width=34) (actual time=0.002..0.003 rows=3 loops=2189)
                     Index Cond: (tr_rating.user_id = user_ids.user_id) JOIN place_ids;
 Total runtime: 15.931 ms

Почему план запроса намного лучше, когда он сталкивается с временными таблицами, а не с массивами? Данные точно такие же, просто представлены по-другому. Кроме того, я измерил время создания временной таблицы при работе от десятков до сотен миллисекунд, а это довольно большие затраты. Могу ли я продолжать использовать подход с использованием массива, но позволить Postgres использовать вместо этого хеш-соединение, которое намного быстрее?

РЕДАКТИРОВАТЬ 2

Путем создания хеш-индекса для user_id время выполнения сокращается до 250 мс. Добавление еще одного хеш-индекса в place_id сокращает время выполнения до 50 мс. Это все еще в два раза медленнее, чем при использовании временных таблиц, но накладные расходы на создание таблицы сводят на нет все выгоды, которые я вижу. Я до сих пор не понимаю, как поиск O (500) в индексе btree может занять десять секунд, но индекс хеша, несомненно, намного быстрее.

1 Ответ

1 голос
/ 06 июля 2011

Похоже, что он берет каждую строку в индексе, а затем сканирует ваш массив user_id, а затем, если обнаруживает, что он сканирует ваш массив place_id. Это означает, что для 3 миллионов строк он должен сканировать через 100 user_id с, а для каждого совпадения он сканирует через 10000 place_id с. Эти совпадения выполняются индивидуально быстро, но это плохой алгоритм, который может привести к выполнению до 30 миллиардов операций.

Было бы лучше создать две временные таблицы, дать им индексы и выполнить объединение. Если он выполнит хеш-соединение, то у вас будет 6 миллионов поисков хешей. (3 миллиона за user_id и 3 миллиона за place_id.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...