Я храню относительно разумное (~ 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 может занять десять секунд, но индекс хеша, несомненно, намного быстрее.