Mi mesa tiene el siguiente aspecto:
Column | Type |
-----------------------+-------------------+
id | integer |
source_id | integer |
timestamp | integer |
observation_timestamp | integer |
value | double precision |
existen índices en source_id, timestamp y en una combinación de timestamp e id ( CREATE INDEX timeseries_id_timestamp_combo_idx ON timeseries (id, timeseries DESC NULLS LAST)
)
Hay 20 millones de filas (OK, hay 120 millones, pero 20 millones con source_id = 1). Tiene muchas entradas para lo mismo timestamp
con diferentes observation_timestamp
, que describen un value
ocurrido en timestamp
reportado u observado en observation_timestamp
. Por ejemplo, la temperatura pronosticada para mañana a las 2 p.m., como se predijo hoy a las 12 a.m.
Idealmente, esta tabla hace algunas cosas bien:
- lote que inserta nuevas entradas, a veces 100K a la vez
- selección de datos observados para intervalos de tiempo ("¿cuáles son las predicciones de temperatura para enero hasta marzo")
- seleccionar los datos observados para los intervalos de tiempo observados desde cierto punto ("cuál es la vista de las predicciones de temperatura para enero hasta marzo como pensamos el 1 de noviembre")
El segundo es el que es central para esta pregunta.
Los datos en la tabla se verían de la siguiente manera
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
y una salida de la consulta se vería de la siguiente manera (solo la fila de la última marca de tiempo de observación representada)
id source_id timestamp observation_timestamp value
1 1 1531084900 1531083900 9999
3 1 1531085900 1531083900 8888
5 1 1531086900 1531082900 5555
Ya he consultado algunos materiales antes para optimizar estas consultas, a saber
- /programming/25536422/optimize-group-by-query-to-retrieve-latest-record-per-user/25536748#25536748
- ¿Cómo hacer que DISTINCT ON sea más rápido en PostgreSQL?
- /programming/3800551/select-first-row-in-each-group-by-group
... con poco éxito.
He considerado crear una tabla separada timestamp
para que sea más fácil hacer referencia lateralmente, pero debido a la relativamente alta cardinalidad de aquellos, dudo si me ayudarán; además, me preocupa que dificulte el logro batch inserting new entries
.
Estoy viendo tres consultas, y todas me dan un mal desempeño
- CTE recursivo con unión LATERAL
- Función de ventana
- DISTINTO EN
(Soy consciente de que no hacen lo mismo en este momento, pero por lo que veo, sirven como buenas ilustraciones del tipo de consulta).
CTE recursivo con unión LATERAL
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;
Actuación:
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)
(solo EXPLAIN
, EXPLAIN ANALYZE
no se pudo completar, tardó> 24 horas en completar la consulta)
Función de ventana
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;
Actuación:
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
DISTINTO EN
SELECT DISTINCT ON (timestamp) *
FROM timeseries
WHERE source_id = 1
ORDER BY timestamp, observation_timestamp DESC;
Actuación:
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
¿Cómo debo estructurar mis datos? ¿Hay escaneos que no deberían estar allí? ¿Es generalmente posible llevar estas consultas a ~ 1s (en lugar de ~ 120s)?
¿Hay alguna forma diferente de consultar los datos para obtener los resultados que quería?
Si no es así, ¿qué infraestructura / arquitectura diferente debería considerar?
fuente
LIMIT
de la pregunta ahora, y agregué resultados conEXPLAIN ANALYZE
(soloEXPLAIN
en larecursive
parte)Respuestas:
Con su consulta recursiva de CTE, el final
ORDER BY (ts).id
es innecesario ya que el CTE los crea automáticamente en ese orden. Al eliminar eso, la consulta debería ser mucho más rápida, puede detenerse temprano en lugar de generar 20,180,572 filas solo para tirar todas menos 500. Además, construir el índice(source_id, id, timestamp desc nulls last)
debería mejorarlo aún más.Para las otras dos consultas, aumentar work_mem lo suficiente como para que los mapas de bits quepan en la memoria (para deshacerse de los bloques de almacenamiento dinámico con pérdida) ayudaría a algunos. Pero no tanto como los índices personalizados, como
(source_id, "timestamp", observation_timestamp DESC)
o mejor aún para los escaneos de índice solamente(source_id, "timestamp", observation_timestamp DESC, value, id)
.fuente
LIMIT 500
era para mí para limitar la salida, pero en el código de producción que esto no suceda. Editaré mi publicación para reflejar eso.LIMIT
y tus sugerencias, la ejecución actual es356.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)
) Pero sinLIMIT
que sea como antes. ¿Cómo aprovecharía tambiénIndex Scan
en ese caso y no en elBitmap Index/Heap Scan
?