Я не знаю, если вы уже решили свою проблему, но, как я уже сталкивался с этим, вы можете сами просмотреть вывод объяснения: https://explain.depesz.com/s/4GYT
Я не знаю точно, почему (пока), но планировщик запросов должен был сгенерировать MergeAppend
вместо Append
исполнительного узла, чтобы включить нажатие LIMIT
в каждом сканировании раздела (индекса), как в мои настройки теста: https://explain.depesz.com/s/Fi9C
Для сравнения, тот же запрос без ограничения, чтобы показать количество тестовых данных, которые я использовал здесь: https://explain.depesz.com/s/AJ6 (да, только 37M промежуточных строк, но это показывает эффект)
Для нетерпеливых в цифрах:
Without LIMIT: 49,174.946 milliseconds
LIMIT 274 OFFSET 100000: 35,022.269 milliseconds
LIMIT 274: 2.710 milliseconds
(также различные значения для LIMIT
, а также параметры запроса вместо констант не приводят к каким-либо другим планам)
Эта оптимизация для MergeAppend
была в PostgreSQL с момента выпуска 9.1 (см. Commit 6fbc323 ) с или без Timescale с использованием разделов, но не существует для простого Append
, что говорит мне, что это ключевое узкое место здесь. На это, в свою очередь, может влиять не обновленная статистика (вы сделали analyze
для обеих таблиц или?).
Следующая версия PostgreSQL 12 должна также выдвинуть LIMIT ... OFFSET ...
в Append
исполнительные узлы с коммитом 959d00e .
OTOH, даже для оптимизированной по времени таблицы с 2+ миллиардами строк, где каждый раздел имеет более 10 миллионов строк, время также может быть потрачено на ввод-вывод большими частями. Чтобы проверить это, выполните следующее и заново создайте план EXPLAIN (ANALYZE, BUFFERS, COSTS OFF)
:
SET track_io_timing TO ON;
Что включает в себя время (сумму) для операций ввода-вывода на каждом соответствующем узле в плане выполнения, например ::1010 *
Limit (actual time=1546.517..1546.572 rows=274 loops=1)
Buffers: shared hit=73438 read=23452
I/O Timings: read=1316.373
-> Sort (actual time=1546.512..1546.536 rows=274 loops=1)
Sort Key: e.source_time
Sort Method: top-N heapsort Memory: 46kB
Buffers: shared hit=73438 read=23452
I/O Timings: read=1316.373
-> Nested Loop (actual time=7.147..1524.251 rows=69293 loops=1)
Buffers: shared hit=73438 read=23452
I/O Timings: read=1316.373
-> Index Only Scan using subscription_signal_subscription_id_signal_id_uindex on subscription_signal (actual time=0.038..0.236 rows=114 loops=1)
Index Cond: (subscription_id = $1)
Heap Fetches: 114
Buffers: shared hit=4
-> Append (actual time=0.334..13.184 rows=608 loops=114)
Buffers: shared hit=73434 read=23452
I/O Timings: read=1316.373
-> Seq Scan on events e (actual time=0.000..0.000 rows=0 loops=114)
Filter: ((source_time > $2) AND (subscription_signal.signal_id = signal_id))
-> Index Scan using _hyper_5_13094_chunk_events_signal_id_source_time_index on _hyper_5_13094_chunk e_1 (actual time=0.014..0.014 rows=0 loops=114)
Index Cond: ((signal_id = subscription_signal.signal_id) AND (source_time > $2))
Buffers: shared hit=340 read=3
I/O Timings: read=0.851
-> Index Scan using _hyper_5_13095_chunk_events_signal_id_source_time_index on _hyper_5_13095_chunk e_2 (actual time=0.009..0.009 rows=0 loops=114)
Index Cond: ((signal_id = subscription_signal.signal_id) AND (source_time > $2))
Buffers: shared hit=335 read=7
I/O Timings: read=0.398
...
Это может быть очень полезно для определения того, является ли запрос на самом деле привязанным к процессору или связанным с вводом / выводом (который является большим узким местом и, следовательно, лучшими инвестициями для улучшения ситуации).
Уменьшить промежуточные строки результатов :
В качестве альтернативы, наиболее важной частью было бы значительно сократить количество промежуточных рядов. Поскольку мы видим, что количество строк, которые вы фактически возвращаете, составляет ~ 0,000205% строк, соответствующих критериям, я настоятельно призываю вас переосмыслить свои критерии.
В запросе временных рядов вы почти никогда не фильтруете только с нижней границей, вместо этого вы получаете разумный интервал, который приводит к достаточному количеству строк для возврата к LIMIT
в 95% всех случаев и для другие 5%, где не хватает строк, просто запросите снова с последующим интервалом времени.
Итак, просто попробуйте следующий запрос:
SELECT events.*
FROM Events
JOIN subscription_signal
ON subscription_signal.subscription_id = $1 AND
events.signal_id = subscription_signal.signal_id
WHERE source_time > $2 AND source_time <= ($2 + '1 week'::interval)
ORDER BY source_time ASC
LIMIT $3
Одно это должно значительно сократить время запроса.