Oto szybkie porównanie wydajności dla zapytań wspomnianych w tym poście.
Obecna konfiguracja:
Tabela core_message
ma 10 904 283 wierszy i jest 60 740 wierszy w test_boats
(lub 60 740 odrębnych mmsi w core_message
).
A ja używam PostgreSQL 11.5
Zapytanie za pomocą skanowania tylko indeksu:
1) za pomocą DISTINCT ON
:
SELECT DISTINCT ON (mmsi) mmsi
FROM core_message;
2) za pomocą RECURSIVE
z 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) Korzystanie z dodatkowej tabeli z 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;
Zapytanie nie używające skanowania tylko do indeksu:
4) za pomocą DISTINCT ON
z mmsi,time DESC
INDEX
:
SELECT DISTINCT ON (mmsi) *
FROM core_message
ORDER BY mmsi, time desc;
5) za pomocą DISTINCT ON
z do tyłu mmsi,time
UNIQUE CONSTRAINT
:
SELECT DISTINCT ON (mmsi) *
FROM core_message
ORDER BY mmsi desc, time desc;
6) za pomocą RECURSIVE
z LATERAL
i 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) za pomocą RECURSIVE
z LATERAL
i wstecz 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) Korzystanie z dodatkowej tabeli z 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;
Korzystanie z dedykowanej tabeli dla ostatniej wiadomości:
9) Oto moje początkowe rozwiązanie, wykorzystujące odrębną tabelę z tylko ostatnią wiadomością. Ta tabela jest wypełniana w miarę napływania nowych wiadomości, ale można ją również utworzyć w następujący sposób:
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);
Wtedy prośba o otrzymanie najnowszej wiadomości jest tak prosta:
SELECT * FROM core_shipinfos;
Wyniki:
Średnia z wielu zapytań (około 5 w przypadku szybkiego):
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
Wniosek:
Nie będę komentował rozwiązania dotyczącego dedykowanego stołu i zachowam to na koniec.
Dodatkowa tabela (test_boats
) rozwiązanie jest tutaj zdecydowanie zwycięzcą, ale RECURSIVE
rozwiązanie jest również dość wydajne.
Istnieje ogromna luka w wydajności dla DISTINCT ON
przy użyciu skanowania tylko indeksu i tym, które go nie używa, ale wzrost wydajności jest raczej niewielki w przypadku drugiego wydajnego zapytania.
Ma to sens, ponieważ głównym ulepszeniem, jakie przynoszą te zapytania, jest fakt, że nie muszą one zapętlać całego core_message
tabeli, ale tylko na podzbiorze unikalnych mmsi
to jest znacznie mniejsze (60K+) w porównaniu do core_message
rozmiar stołu (10M+)
Jako dodatkowa uwaga, wydaje się, że nie ma znaczącej poprawy wydajności dla zapytań używających UNIQUE CONSTRAINT
jeśli upuszczę mmsi,time DESC
INDEX
. Ale upuszczenie tego indeksu oczywiście zaoszczędzi mi trochę miejsca (ten indeks zajmuje obecnie 328 MB)
O dedykowanym rozwiązaniu stołu:
Każda wiadomość przechowywana w core_message
tabela zawiera zarówno informacje o pozycji (pozycja, prędkość, kurs itp.) ORAZ informacje o statku (nazwa, znak wywoławczy, wymiary itp.), a także identyfikator statku (mmsi).
Aby dać nieco więcej informacji na temat tego, co faktycznie próbuję zrobić:implementuję backend do przechowywania wiadomości emitowanych przez statki za pośrednictwem Protokół AIS .
W związku z tym każdy unikalny mmsi, który dostałem, otrzymywałem za pomocą tego protokołu. Nie jest to z góry zdefiniowana lista. Dodaje nowe MMSI, dopóki nie otrzymam wszystkich statków na świecie za pomocą AIS.
W tym kontekście dedykowana tabela z informacjami o statku jako ostatnią otrzymaną wiadomością ma sens.
Mógłbym uniknąć używania takiej tabeli, jak widzieliśmy z RECURSIVE
rozwiązanie, ale... dedykowana tabela jest wciąż 50x szybsza niż ta RECURSIVE
rozwiązanie.
Ta dedykowana tabela jest w rzeczywistości podobna do test_boat
tabelę zawierającą więcej informacji niż tylko mmsi
pole. Jak to jest, posiadanie tabeli z mmsi
tylko pole lub tabela z wszystkimi ostatnimi informacjami core_message
tabela dodaje tę samą złożoność do mojej aplikacji.
W końcu myślę, że pójdę na ten dedykowany stolik. Zapewni mi to bezkonkurencyjną prędkość i nadal będę miał możliwość korzystania z LATERAL
sztuczka na core_message
, co zapewni mi większą elastyczność.