Aquí hay una comparación rápida de rendimiento para las consultas mencionadas en esta publicación.
Configuración actual:
La tabla core_message
tiene 10,904,283 filas y hay 60,740 filas en test_boats
(o 60.740 mmsi distintos en core_message
).
Y estoy usando PostgreSQL 11.5
Consulta mediante análisis de solo índice:
1) usando DISTINCT ON
:
SELECT DISTINCT ON (mmsi) mmsi
FROM core_message;
2) usando RECURSIVE
con LATERAL
:
WITH RECURSIVE cte AS (
(
SELECT mmsi
FROM core_message
ORDER BY mmsi
LIMIT 1
)
UNION ALL
SELECT m.*
FROM cte c
CROSS JOIN LATERAL (
SELECT mmsi
FROM core_message
WHERE mmsi > c.mmsi
ORDER BY mmsi
LIMIT 1
) m
)
TABLE cte;
3) Usando una tabla extra con LATERAL
:
SELECT a.mmsi
FROM test_boats a
CROSS JOIN LATERAL(
SELECT b.time
FROM core_message b
WHERE a.mmsi = b.mmsi
ORDER BY b.time DESC
LIMIT 1
) b;
Consulta que no usa escaneo de solo índice:
4) usando DISTINCT ON
con mmsi,time DESC
INDEX
:
SELECT DISTINCT ON (mmsi) *
FROM core_message
ORDER BY mmsi, time desc;
5) usando DISTINCT ON
con hacia atrás mmsi,time
UNIQUE CONSTRAINT
:
SELECT DISTINCT ON (mmsi) *
FROM core_message
ORDER BY mmsi desc, time desc;
6) usando RECURSIVE
con LATERAL
y mmsi,time DESC
INDEX
:
WITH RECURSIVE cte AS (
(
SELECT *
FROM core_message
ORDER BY mmsi , time DESC
LIMIT 1
)
UNION ALL
SELECT m.*
FROM cte c
CROSS JOIN LATERAL (
SELECT *
FROM core_message
WHERE mmsi > c.mmsi
ORDER BY mmsi , time DESC
LIMIT 1
) m
)
TABLE cte;
7) usando RECURSIVE
con LATERAL
y hacia atrás mmsi,time
UNIQUE CONSTRAINT
:
WITH RECURSIVE cte AS (
(
SELECT *
FROM core_message
ORDER BY mmsi DESC , time DESC
LIMIT 1
)
UNION ALL
SELECT m.*
FROM cte c
CROSS JOIN LATERAL (
SELECT *
FROM core_message
WHERE mmsi < c.mmsi
ORDER BY mmsi DESC , time DESC
LIMIT 1
) m
)
TABLE cte;
8) Usando una tabla extra con LATERAL
:
SELECT b.*
FROM test_boats a
CROSS JOIN LATERAL(
SELECT b.*
FROM core_message b
WHERE a.mmsi = b.mmsi
ORDER BY b.time DESC
LIMIT 1
) b;
Usando una tabla dedicada para el último mensaje:
9) Aquí está mi solución inicial, usando una tabla distinta con solo el último mensaje. Esta tabla se completa a medida que llegan nuevos mensajes, pero también podría crearse así:
CREATE TABLE core_shipinfos AS (
WITH RECURSIVE cte AS (
(
SELECT *
FROM core_message
ORDER BY mmsi DESC , time DESC
LIMIT 1
)
UNION ALL
SELECT m.*
FROM cte c
CROSS JOIN LATERAL (
SELECT *
FROM core_message
WHERE mmsi < c.mmsi
ORDER BY mmsi DESC , time DESC
LIMIT 1
) m
)
TABLE cte);
Entonces la solicitud para recibir el último mensaje es tan simple como eso:
SELECT * FROM core_shipinfos;
Resultados:
Promedio de consulta múltiple (alrededor de 5 para la rápida):
1) 9146 ms
2) 728 ms
3) 498 ms
4) 51488 ms
5) 54764 ms
6) 729 ms
7) 778 ms
8) 516 ms
9) 15ms
Conclusión:
No comentaré sobre la solución de mesa dedicada y la dejaré para el final.
La tabla adicional (test_boats
) la solución es definitivamente la ganadora aquí, pero el RECURSIVE
La solución también es bastante eficiente.
Hay una gran diferencia en el rendimiento de DISTINCT ON
usando escaneo de solo índice y el que no lo usa, pero la ganancia de rendimiento es bastante pequeña para la otra consulta eficiente.
Esto tiene sentido ya que la principal mejora que aportan esas consultas es el hecho de que no necesitan recorrer todo el core_message
tabla, pero solo en un subconjunto de los únicos mmsi
eso es significativamente más pequeño (60K+) en comparación con el core_message
tamaño de la mesa (10M+)
Como nota adicional, no parece haber una mejora significativa en el rendimiento de las consultas que utilizan la UNIQUE CONSTRAINT
si dejo caer el mmsi,time DESC
INDEX
. Pero dejar ese índice, por supuesto, me ahorrará algo de espacio (este índice actualmente ocupa 328 MB)
Acerca de la solución de mesa dedicada:
Cada mensaje almacenado en el core_message
La tabla contiene información de posición (posición, velocidad, rumbo, etc.) E información del barco (nombre, indicativo, dimensiones, etc.), así como el identificador del barco (mmsi).
Para brindar un poco más de información sobre lo que realmente estoy tratando de hacer:estoy implementando un backend para almacenar mensajes emitidos por barcos a través de Protocolo AIS .
Como tal, cada mmsi único que obtuve, lo obtuve a través de este protocolo. No es una lista predefinida. Sigue agregando nuevos MMSI hasta que tengo todos los barcos del mundo que usan AIS.
En ese contexto, tiene sentido una tabla dedicada con información del barco como el último mensaje recibido.
Podría evitar usar una tabla como la que hemos visto con RECURSIVE
solución, pero... una tabla dedicada sigue siendo 50 veces más rápida que esta RECURSIVE
solución.
Esa tabla dedicada es, de hecho, similar al test_boat
tabla, con más información que solo el mmsi
campo. Tal como está, tener una tabla con mmsi
único campo o una tabla con toda la información del core_message
table agrega la misma complejidad a mi aplicación.
Al final, creo que optaré por esta mesa dedicada. Me dará una velocidad imbatible y aún tendré la posibilidad de usar el LATERAL
truco en core_message
, lo que me dará más flexibilidad.