PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Modo corretto per accedere all'ultima riga per ogni singolo identificatore?

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à.