Aqui está uma rápida comparação de desempenho para as consultas mencionadas neste post.
Configuração atual:
A tabela
core_message
tem 10.904.283 linhas e há 60.740 linhas em test_boats
(ou 60.740 mmsi distintos em core_message
). E estou usando o PostgreSQL 11.5
Consulta usando verificação somente de índice:
1) usando
DISTINCT ON
:SELECT DISTINCT ON (mmsi) mmsi
FROM core_message;
2) usando
RECURSIVE
com 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 uma tabela extra com
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 não usa verificação somente de índice:
4) usando
DISTINCT ON
com mmsi,time DESC
INDEX
:SELECT DISTINCT ON (mmsi) *
FROM core_message
ORDER BY mmsi, time desc;
5) usando
DISTINCT ON
com retrocesso mmsi,time
UNIQUE CONSTRAINT
:SELECT DISTINCT ON (mmsi) *
FROM core_message
ORDER BY mmsi desc, time desc;
6) usando
RECURSIVE
com 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) usando
RECURSIVE
com LATERAL
e retroceder 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 uma tabela extra com
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 uma tabela dedicada para a última mensagem:
9) Aqui está minha solução inicial, usando uma tabela distinta com apenas a última mensagem. Esta tabela é preenchida à medida que novas mensagens chegam, mas também pode ser criada assim:
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);
Então, a solicitação para obter a mensagem mais recente é tão simples quanto isso:
SELECT * FROM core_shipinfos;
Resultados:
Média de consultas múltiplas (cerca de 5 para a rápida):
1) 9146 ms
2) 728 ms
3) 498 ms
4) 51.488 ms
5) 54.764 ms
6) 729 ms
7) 778 ms
8) 516 ms
9) 15 ms
Conclusão:
Não comentarei sobre a solução de tabela dedicada e a manterei para o final.
A tabela adicional (
test_boats
) é definitivamente a vencedora aqui, mas a solução RECURSIVE
solução também é bastante eficiente. Há uma grande lacuna no desempenho do
DISTINCT ON
usando a varredura somente de índice e a que não a usa, mas o ganho de desempenho é bastante pequeno para a outra consulta eficiente. Isso faz sentido, pois a principal melhoria que essas consultas trazem é o fato de que elas não precisam percorrer todo o
core_message
tabela, mas apenas em um subconjunto do exclusivo mmsi
que é significativamente menor (60K+) em comparação com o core_message
tamanho da mesa (10M+) Como uma observação adicional, não parece haver uma melhoria significativa no desempenho das consultas usando a
UNIQUE CONSTRAINT
se eu soltar o mmsi,time DESC
INDEX
. Mas é claro que descartar esse índice me economizará algum espaço (esse índice atualmente ocupa 328 MB) Sobre a solução de tabela dedicada:
Cada mensagem armazenada no
core_message
A tabela contém informações posicionais (posição, velocidade, rumo, etc.) E informações do navio (nome, indicativo, dimensões, etc.), bem como o identificador do navio (mmsi). Para dar um pouco mais de informações sobre o que estou realmente tentando fazer:estou implementando um back-end para armazenar mensagens emitidas por navios por meio do protocolo AIS .
Como tal, todos os mmsi únicos que obtive, obtive-os através deste protocolo. Não é uma lista pré-definida. Ele continua adicionando novos MMSI até que eu tenha todos os navios do mundo usando AIS.
Nesse contexto, faz sentido uma tabela dedicada com informações de envio como última mensagem recebida.
Eu poderia evitar usar uma tabela como vimos com o
RECURSIVE
solução, mas... uma tabela dedicada ainda é 50x mais rápida que esta RECURSIVE
solução. Essa tabela dedicada é de fato semelhante ao
test_boat
tabela, com mais informações do que apenas o mmsi
campo. Como está, ter uma tabela com mmsi
único campo ou uma tabela com todas as últimas informações da core_message
table adicionam a mesma complexidade ao meu aplicativo. No final, acho que vou optar por esta mesa dedicada. Vai me dar uma velocidade imbatível e ainda terei a possibilidade de usar o
LATERAL
truque em core_message
, o que me dará mais flexibilidade.