Ecco un rapido confronto delle prestazioni per le query menzionate in questo post.
Configurazione attuale :
La tabella core_message
ha 10.904.283 righe e ci sono 60.740 righe in test_boats
(o 60.740 mmsi distinti in core_message
).
E sto usando PostgreSQL 11.5
Query utilizzando la scansione solo indice :
1) utilizzando DISTINCT ON
:
SELECT DISTINCT ON (mmsi) mmsi
FROM core_message;
2) utilizzando 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) Utilizzo di una tabella aggiuntiva 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;
Query che non utilizza la scansione solo indice :
4) utilizzando DISTINCT ON
con mmsi,time DESC
INDEX
:
SELECT DISTINCT ON (mmsi) *
FROM core_message
ORDER BY mmsi, time desc;
5) utilizzando DISTINCT ON
con mmsi,time
indietro UNIQUE CONSTRAINT
:
SELECT DISTINCT ON (mmsi) *
FROM core_message
ORDER BY mmsi desc, time desc;
6) utilizzando RECURSIVE
con LATERAL
e 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) utilizzando RECURSIVE
con LATERAL
e indietro 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) Utilizzo di una tabella aggiuntiva 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;
Utilizzo di una tabella dedicata per l'ultimo messaggio:
9) Ecco la mia soluzione iniziale, utilizzando una tabella distinta con solo l'ultimo messaggio. Questa tabella viene popolata all'arrivo di nuovi messaggi, ma potrebbe anche essere creata in questo modo:
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);
Quindi la richiesta per ricevere l'ultimo messaggio è così semplice:
SELECT * FROM core_shipinfos;
Risultati :
Media di query multiple (circa 5 per quella veloce):
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) 15 ms
Conclusione:
Non commenterò la soluzione della tabella dedicata e la terrò per la fine.
La tabella aggiuntiva (test_boats
) la soluzione è sicuramente la vincitrice qui, ma il RECURSIVE
la soluzione è anche abbastanza efficiente.
C'è un enorme divario nelle prestazioni per DISTINCT ON
utilizzando la scansione solo indice e quella che non la utilizza, ma il guadagno in termini di prestazioni è piuttosto piccolo per l'altra query efficiente.
Questo ha senso in quanto il principale miglioramento apportato da queste query è il fatto che non hanno bisogno di scorrere l'intero core_message
tabella ma solo su un sottoinsieme del mmsi
univoco che è significativamente più piccolo (60.000+) rispetto al core_message
dimensione del tavolo (10M+)
Come nota aggiuntiva, non sembra esserci un miglioramento significativo delle prestazioni per le query che utilizzano il UNIQUE CONSTRAINT
se elimino mmsi,time DESC
INDEX
. Ma ovviamente eliminare quell'indice mi farà risparmiare spazio (questo indice attualmente occupa 328 MB)
Informazioni sulla soluzione da tavolo dedicata:
Ogni messaggio memorizzato nel core_message
la tabella contiene sia le informazioni di posizione (posizione, velocità, direzione, ecc.) E le informazioni sulla nave (nome, nominativo, dimensioni, ecc.), sia l'identificativo della nave (mmsi).
Per fornire un po' più di informazioni su ciò che sto effettivamente cercando di fare:sto implementando un backend per archiviare i messaggi emessi dalle navi tramite Protocollo AIS .
In quanto tale, ogni mmsi univoco che ho ricevuto, l'ho ottenuto tramite questo protocollo. Non è un elenco predefinito. Continua ad aggiungere nuovi MMSI finché non ho ricevuto tutte le navi del mondo utilizzando AIS.
In tale contesto, ha senso una tabella dedicata con le informazioni sulla nave come ultimo messaggio ricevuto.
Potrei evitare di usare una tabella del genere come abbiamo visto con il RECURSIVE
soluzione, ma... una tabella dedicata è ancora 50 volte più veloce di questa RECURSIVE
soluzione.
Quella tabella dedicata è infatti simile a test_boat
tabella, con più informazioni oltre a mmsi
campo. Così com'è, avere una tabella con mmsi
solo campo o una tabella con le ultime informazioni del core_message
la tabella aggiunge la stessa complessità alla mia applicazione.
Alla fine, penso che sceglierò questo tavolo dedicato. Mi darà una velocità imbattibile e avrò ancora la possibilità di utilizzare il LATERAL
trucco su core_message
, che mi darà più flessibilità.