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