Моя таблица выглядит следующим образом:
Column | Type |
-----------------------+-------------------+
id | integer |
source_id | integer |
timestamp | integer |
observation_timestamp | integer |
value | double precision |
индексы существуют для source_id, timestamp и для комбинации timestamp и id ( CREATE INDEX timeseries_id_timestamp_combo_idx ON timeseries (id, timeseries DESC NULLS LAST)
)
В нем 20 миллионов строк (ОК, 120 миллионов, но 20 миллионов с source_id = 1). Он имеет много записей для одного и того же timestamp
с различными observation_timestamp
, которые описывают value
произошедшее в timestamp
сообщении или наблюдалось в observation_timestamp
. Например, прогнозируемая температура на завтра в 2 часа дня, как сегодня прогнозируется в 12 часов утра.
В идеале эта таблица делает несколько вещей хорошо:
- Пакетная вставка новых записей, иногда 100K за раз
- выбор данных, наблюдаемых для временных диапазонов («каковы прогнозы температуры на январь до марта»)
- выбор данных, наблюдаемых для временных диапазонов, наблюдаемых с определенной точки («каковы прогнозы температуры на январь до марта, как мы думали 1 ноября»)
Второй - тот, который занимает центральное место в этом вопросе.
Данные в таблице будут выглядеть следующим образом
id source_id timestamp observation_timestamp value
1 1 1531084900 1531083900 9999
2 1 1531084900 1531082900 1111
3 1 1531085900 1531083900 8888
4 1 1531085900 1531082900 7777
5 1 1531086900 1531082900 5555
и результат запроса будет выглядеть следующим образом (представлена только строка самой последней строки direction_timestamp)
id source_id timestamp observation_timestamp value
1 1 1531084900 1531083900 9999
3 1 1531085900 1531083900 8888
5 1 1531086900 1531082900 5555
Я уже консультировался с некоторыми материалами для оптимизации этих запросов, а именно
- /programming/25536422/optimize-group-by-query-to-retrieve-latest-record-per-user/25536748#25536748
- Как сделать DISTINCT ON быстрее в PostgreSQL?
- /programming/3800551/select-first-row-in-each-group-by-group
... с ограниченным успехом.
Я подумал о создании отдельной таблицы, timestamp
в которой было бы легче ссылаться в боковом направлении, но из-за относительно высокой мощности тех, кого я сомневаюсь, они помогут мне - кроме того, я обеспокоен тем, что это будет препятствовать выполнению batch inserting new entries
.
Я смотрю на три запроса, и все они дают мне плохую производительность
- Рекурсивный CTE с Боковым соединением
- Оконная функция
- ОТЛИЧАЕТСЯ НА
(Я знаю, что они не совсем делают то же самое в данный момент, но они служат хорошей иллюстрацией типа запросов, насколько я вижу.)
Рекурсивный CTE с Боковым соединением
WITH RECURSIVE cte AS (
(
SELECT ts
FROM timeseries ts
WHERE source_id = 1
ORDER BY id, "timestamp" DESC NULLS LAST
LIMIT 1
)
UNION ALL
SELECT (
SELECT ts1
FROM timeseries ts1
WHERE id > (c.ts).id
AND source_id = 1
ORDER BY id, "timestamp" DESC NULLS LAST
LIMIT 1
)
FROM cte c
WHERE (c.ts).id IS NOT NULL
)
SELECT (ts).*
FROM cte
WHERE (ts).id IS NOT NULL
ORDER BY (ts).id;
Спектакль:
Sort (cost=164999681.98..164999682.23 rows=100 width=28)
Sort Key: ((cte.ts).id)
CTE cte
-> Recursive Union (cost=1653078.24..164999676.64 rows=101 width=52)
-> Subquery Scan on *SELECT* 1 (cost=1653078.24..1653078.26 rows=1 width=52)
-> Limit (cost=1653078.24..1653078.25 rows=1 width=60)
-> Sort (cost=1653078.24..1702109.00 rows=19612304 width=60)
Sort Key: ts.id, ts.timestamp DESC NULLS LAST
-> Bitmap Heap Scan on timeseries ts (cost=372587.92..1555016.72 rows=19612304 width=60)
Recheck Cond: (source_id = 1)
-> Bitmap Index Scan on ix_timeseries_source_id (cost=0.00..367684.85 rows=19612304 width=0)
Index Cond: (source_id = 1)
-> WorkTable Scan on cte c (cost=0.00..16334659.64 rows=10 width=32)
Filter: ((ts).id IS NOT NULL)
SubPlan 1
-> Limit (cost=1633465.94..1633465.94 rows=1 width=60)
-> Sort (cost=1633465.94..1649809.53 rows=6537435 width=60)
Sort Key: ts1.id, ts1.timestamp DESC NULLS LAST
-> Bitmap Heap Scan on timeseries ts1 (cost=369319.21..1600778.77 rows=6537435 width=60)
Recheck Cond: (source_id = 1)
Filter: (id > (c.ts).id)
-> Bitmap Index Scan on ix_timeseries_source_id (cost=0.00..367684.85 rows=19612304 width=0)
Index Cond: (source_id = 1)
-> CTE Scan on cte (cost=0.00..2.02 rows=100 width=28)
Filter: ((ts).id IS NOT NULL)
(только EXPLAIN
, EXPLAIN ANALYZE
не смог завершить, потребовалось> 24 часа для завершения запроса)
Оконная функция
WITH summary AS (
SELECT ts.id, ts.source_id, ts.value,
ROW_NUMBER() OVER(PARTITION BY ts.timestamp ORDER BY ts.observation_timestamp DESC) AS rn
FROM timeseries ts
WHERE source_id = 1
)
SELECT s.*
FROM summary s
WHERE s.rn = 1;
Спектакль:
CTE Scan on summary s (cost=5530627.97..5971995.66 rows=98082 width=24) (actual time=150368.441..226331.286 rows=88404 loops=1)
Filter: (rn = 1)
Rows Removed by Filter: 20673704
CTE summary
-> WindowAgg (cost=5138301.13..5530627.97 rows=19616342 width=32) (actual time=150368.429..171189.504 rows=20762108 loops=1)
-> Sort (cost=5138301.13..5187341.98 rows=19616342 width=24) (actual time=150368.405..165390.033 rows=20762108 loops=1)
Sort Key: ts.timestamp, ts.observation_timestamp DESC
Sort Method: external merge Disk: 689752kB
-> Bitmap Heap Scan on timeseries ts (cost=372675.22..1555347.49 rows=19616342 width=24) (actual time=2767.542..50399.741 rows=20762108 loops=1)
Recheck Cond: (source_id = 1)
Rows Removed by Index Recheck: 217784
Heap Blocks: exact=48415 lossy=106652
-> Bitmap Index Scan on ix_timeseries_source_id (cost=0.00..367771.13 rows=19616342 width=0) (actual time=2757.245..2757.245 rows=20762630 loops=1)
Index Cond: (source_id = 1)
Planning time: 0.186 ms
Execution time: 234883.090 ms
ОТЛИЧАЕТСЯ НА
SELECT DISTINCT ON (timestamp) *
FROM timeseries
WHERE source_id = 1
ORDER BY timestamp, observation_timestamp DESC;
Спектакль:
Unique (cost=5339449.63..5437531.34 rows=15991 width=28) (actual time=112653.438..121397.944 rows=88404 loops=1)
-> Sort (cost=5339449.63..5388490.48 rows=19616342 width=28) (actual time=112653.437..120175.512 rows=20762108 loops=1)
Sort Key: timestamp, observation_timestamp DESC
Sort Method: external merge Disk: 770888kB
-> Bitmap Heap Scan on timeseries (cost=372675.22..1555347.49 rows=19616342 width=28) (actual time=2091.585..56109.942 rows=20762108 loops=1)
Recheck Cond: (source_id = 1)
Rows Removed by Index Recheck: 217784
Heap Blocks: exact=48415 lossy=106652
-> Bitmap Index Scan on ix_timeseries_source_id (cost=0.00..367771.13 rows=19616342 width=0) (actual time=2080.054..2080.054 rows=20762630 loops=1)
Index Cond: (source_id = 1)
Planning time: 0.132 ms
Execution time: 161651.006 ms
Как мне структурировать мои данные, есть ли сканы, которых не должно быть, возможно ли вообще получить эти запросы до ~ 1 с (вместо ~ 120 с)?
Есть ли другой способ запроса данных для получения желаемых результатов?
Если нет, на какую другую инфраструктуру / архитектуру я должен смотреть?
источник
LIMIT
вопрос и добавил вывод с помощьюEXPLAIN ANALYZE
(толькоEXPLAIN
соrecursive
стороны)Ответы:
С вашим рекурсивным запросом CTE финал
ORDER BY (ts).id
не нужен, поскольку CTE автоматически создает их в этом порядке. Удаление этого должно сделать запрос намного быстрее, он может остановиться раньше, чем генерировать 20 180 572 строки, чтобы отбросить все, кроме 500. Кроме того, построение индекса(source_id, id, timestamp desc nulls last)
должно улучшить его.Для остальных двух запросов некоторым поможет увеличение work_mem, чтобы растровые изображения помещались в память (чтобы избавиться от блоков кучи с потерями). Но не так много, как пользовательские индексы, такие как
(source_id, "timestamp", observation_timestamp DESC)
или еще лучше для сканирования только по индексам(source_id, "timestamp", observation_timestamp DESC, value, id)
.источник
LIMIT 500
Предназначались для меня , чтобы ограничить выход, но в производственном коде этого не происходит. Я отредактирую свой пост, чтобы отразить это.LIMIT
вашими и вашими предложениями, в настоящий момент выполняется356.482 ms
(Index Scan using ix_timeseries_source_id_timestamp_observation_timestamp on timeseries (cost=0.57..62573201.42 rows=18333374 width=28) (actual time=174.098..356.097 rows=2995 loops=1)
) Но безLIMIT
этого, как прежде. Как бы я также использовалIndex Scan
в этом случае, а неBitmap Index/Heap Scan
?