Voici une comparaison rapide des performances pour les requêtes mentionnées dans cet article.
Configuration actuelle :
Le tableau core_message
a 10 904 283 lignes et il y a 60 740 lignes dans test_boats
(ou 60 740 mmsi distincts dans core_message
).
Et j'utilise PostgreSQL 11.5
Requête utilisant l'analyse d'index uniquement :
1) en utilisant DISTINCT ON
:
SELECT DISTINCT ON (mmsi) mmsi
FROM core_message;
2) en utilisant RECURSIVE
avec 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) Utiliser une table supplémentaire avec 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;
Requête n'utilisant pas l'analyse d'index uniquement :
4) en utilisant DISTINCT ON
avec mmsi,time DESC
INDEX
:
SELECT DISTINCT ON (mmsi) *
FROM core_message
ORDER BY mmsi, time desc;
5) en utilisant DISTINCT ON
avec mmsi,time
en arrière UNIQUE CONSTRAINT
:
SELECT DISTINCT ON (mmsi) *
FROM core_message
ORDER BY mmsi desc, time desc;
6) en utilisant RECURSIVE
avec LATERAL
et 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) en utilisant RECURSIVE
avec LATERAL
et retour 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) Utiliser une table supplémentaire avec 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;
Utiliser une table dédiée pour le dernier message :
9) Voici ma solution initiale, en utilisant une table distincte avec uniquement le dernier message. Ce tableau est rempli au fur et à mesure de l'arrivée de nouveaux messages mais pourrait aussi être créé comme ceci :
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);
Ensuite, la demande pour obtenir le dernier message est aussi simple que cela :
SELECT * FROM core_shipinfos;
Résultats :
Moyenne de plusieurs requêtes (environ 5 pour la plus rapide) :
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
Conclusion :
Je ne commenterai pas la solution de table dédiée, et la garderai pour la fin.
La table supplémentaire (test_boats
) la solution est définitivement la gagnante ici mais la RECURSIVE
solution est également assez efficace.
Il y a un énorme écart de performances pour le DISTINCT ON
en utilisant l'analyse d'index uniquement et celle qui ne l'utilise pas, le gain de performances est plutôt faible pour l'autre requête efficace.
Cela a du sens car l'amélioration majeure apportée par ces requêtes est le fait qu'elles n'ont pas besoin de boucler sur l'ensemble du core_message
table mais uniquement sur un sous-ensemble de l'unique mmsi
qui est nettement plus petit (60K+) par rapport au core_message
taille du tableau (10M+)
De plus, il ne semble pas y avoir d'amélioration significative des performances pour les requêtes utilisant la UNIQUE CONSTRAINT
si je supprime le mmsi,time DESC
INDEX
. Mais la suppression de cet index me permettra bien sûr d'économiser de l'espace (cet index prend actuellement 328 Mo)
À propos de la solution de table dédiée :
Chaque message stocké dans le core_message
table contient à la fois des informations de position (position, vitesse, cap, etc.) ET des informations sur le navire (nom, indicatif, dimensions, etc.), ainsi que l'identifiant du navire (mmsi).
Pour donner un peu plus de contexte sur ce que j'essaie de faire :j'implémente un backend pour stocker les messages émis par les vaisseaux via le Protocole AIS .
En tant que tel, chaque mmsi unique que j'ai obtenu, je l'ai obtenu via ce protocole. Ce n'est pas une liste prédéfinie. Il continue d'ajouter de nouveaux MMSI jusqu'à ce que tous les navires du monde utilisent l'AIS.
Dans ce contexte, une table dédiée avec les informations du navire comme dernier message reçu a du sens.
Je pourrais éviter d'utiliser une telle table comme nous l'avons vu avec le RECURSIVE
solution, mais... une table dédiée est toujours 50x plus rapide que ce RECURSIVE
solution.
Cette table dédiée est en fait similaire au test_boat
table, avec plus d'informations que juste le mmsi
champ. En l'état, avoir une table avec mmsi
seul champ ou un tableau avec toutes les dernières informations du core_message
table ajoute la même complexité à mon application.
Au final, je pense que je vais me diriger vers cette table dédiée. Cela me donnera une vitesse imbattable et j'aurai toujours la possibilité d'utiliser le LATERAL
astuce sur core_message
, ce qui me donnera plus de flexibilité.