PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Maneira correta de acessar a linha mais recente para cada identificador individual?


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.