Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds
Powrót do kolekcji
Przewodnik10 min czytania

PostgreSQL 18, asynchroniczne wejście i wyjście

PostgreSQL 18 wprowadził asynchroniczne wejście i wyjście, a 19 jest w becie. Co to zmienia, jak stroić konfigurację i gdzie naprawdę leżą wąskie gardła.

PostgreSQL 18, asynchroniczne wejście i wyjście

PostgreSQL to baza relacyjna rozwijana od trzech dekad przez społeczność, bez właściciela komercyjnego, na licencji pozwalającej na dowolne użycie. Bieżąca stabilna linia to 18, a dziewiętnastka jest w fazie beta i jej wydanie planowane jest na drugą połowę 2026 roku.

Warto tę różnicę odnotować, bo materiały opisujące nowości dziewiętnastki mówią o czymś, czego jeszcze nie postawisz na produkcji. To, co możesz uruchomić dzisiaj, to osiemnastka, a jej najważniejsza zmiana dotyczy sposobu czytania danych z dysku.

Asynchroniczne wejście i wyjście

To jest najpoważniejsza zmiana wewnętrzna od lat i warto rozumieć, dlaczego akurat teraz.

Przez większość swojej historii baza czytała dane synchronicznie: prosiła system o blok, czekała na odpowiedź, prosiła o następny. Na dysku talerzowym miało to sens, bo głowica i tak musiała się przesunąć. Na dysku półprzewodnikowym, a zwłaszcza na dysku sieciowym w chmurze, oznacza to marnowanie czasu na czekanie, gdy urządzenie mogłoby obsłużyć kilkadziesiąt żądań naraz.

Osiemnastka wprowadziła podsystem wysyłający wiele żądań odczytu jednocześnie. Skutek widać najwyraźniej przy skanowaniu dużych tabel i przy operacjach czytających sekwencyjnie, gdzie opóźnienie pojedynczego odczytu było dotąd czynnikiem ograniczającym.

Największy zysk dotyczy wdrożeń w chmurze, gdzie dysk jest zasobem sieciowym, a opóźnienie pojedynczego odczytu bywa dziesięciokrotnie wyższe niż przy dysku lokalnym. Tam różnica bywa odczuwalna bez żadnego strojenia zapytań.

Dziewiętnastka rozwija ten mechanizm o automatyczne skalowanie liczby procesów obsługujących odczyty oraz o pokazywanie statystyk tego podsystemu w planie wykonania. To drugie jest praktycznie ważniejsze, bo pozwala zobaczyć, czy zapytanie faktycznie korzysta z nowego mechanizmu, zamiast zakładać.

Konfiguracja domyślna i dlaczego jest zachowawcza

Zanim sięgniesz po nowe możliwości, warto sprawdzić rzecz, która odpowiada za większość skarg na wydajność.

Ustawienia domyślne są dobrane tak, żeby baza uruchomiła się na maszynie o skromnych zasobach. Znaczy to, że na serwerze z trzydziestoma dwoma gigabajtami pamięci baza domyślnie użyje ułamka tego, co mogłaby.

Trzy parametry robią największą różnicę. Rozmiar bufora współdzielonego, zwykle ustawiany na jedną czwartą pamięci maszyny. Szacunek pamięci dostępnej na buforowanie przez system, zwykle na trzy czwarte. Oraz pamięć na operacje sortowania i łączenia, dobierana z rozwagą, bo mnoży się przez liczbę równoległych operacji, a nie przez liczbę połączeń.

Czwarty parametr dotyczy kosztu odczytu losowego. Wartość domyślna pochodzi z czasów dysków talerzowych i mówi planiście, że odczyt losowy jest czterokrotnie droższy od sekwencyjnego. Na dysku półprzewodnikowym ta proporcja jest znacznie mniejsza, a zostawienie starej wartości sprawia, że planista unika indeksów tam, gdzie powinien po nie sięgnąć.

Zmiana tego jednego ustawienia bywa poprawką o większym skutku niż tydzień przepisywania zapytań, i jest jedną linią w konfiguracji.

Gdzie naprawdę leżą wąskie gardła

Diagnoza w tej bazie ma ustaloną kolejność i warto ją znać, bo intuicja prowadzi zwykle w złą stronę.

Pierwsze miejsce to brakujące indeksy. Zapytanie przeglądające całą tabelę, gdy szuka jednego wiersza, to najczęstsza przyczyna i najłatwiejsza do naprawienia. Plan wykonania pokazuje to wprost.

Drugie to indeksy istniejące i nieużywane, bo zapytanie napisano w sposób, który je omija. Funkcja nałożona na kolumnę, niezgodność typów albo wyszukiwanie tekstowe z gwiazdką na początku wzorca sprawiają, że planista nie może z indeksu skorzystać.

Trzecie to problem z liczbą zapytań, a nie ich treścią. Pętla wykonująca zapytanie dla każdego elementu listy generuje setki przelotów sieciowych, z których każdy trwa milisekundy. Suma bywa dłuższa niż jedno zapytanie zwracające wszystko naraz.

Czwarte to rozdęte tabele. Baza nie usuwa fizycznie starych wersji wierszy od razu, a przy intensywnych zmianach tabela zajmuje wielokrotnie więcej miejsca niż wynikałoby z liczby wierszy. Proces sprzątający działa w tle i przy dużym ruchu bywa, że nie nadąża.

Warto tę kolejność zachować, bo strojenie konfiguracji przy brakującym indeksie jest jak dolewanie oleju do silnika bez świec.

Typy danych, które warto znać

Ta baza ma zestaw wykraczający poza to, co spotyka się w innych, i część z tego oszczędza sporo kodu.

Typ przechowujący dane w postaci dokumentu pozwala trzymać strukturę zmienną w kolumnie, z indeksowaniem i zapytaniami po zawartości. To jest rozsądna droga przy danych, których kształt zależy od typu rekordu, i zła przy danych, które mają stały kształt i tylko wygodniej było je wrzucić w jedno pole.

Typy przedziałowe opisują zakres wartości, na przykład czas rezerwacji, i pozwalają wymusić, że dwa przedziały nie zachodzą na siebie. Bez tego to samo wymaga wyzwalacza albo blokady i wychodzi gorzej.

Tablice pozwalają trzymać listę wartości w jednej kolumnie i mają sens przy krótkich, stałych listach, na przykład etykietach. Przy relacji, która ma rosnąć, właściwa jest osobna tabela.

Wyszukiwanie pełnotekstowe jest wbudowane i przy niewielkich zbiorach zastępuje osobny silnik wyszukiwania. Obsługa odmiany dla języka polskiego wymaga słownika, którego trzeba dodać, i to jest krok pomijany przez osoby zaskoczone, że wyszukiwanie nie znajduje form odmienionych.

Granica przebiega przy analizatorach językowych, synonimach, podświetlaniu znalezionych fragmentów i mieszaniu dopasowania po słowach z podobieństwem znaczeniowym. Tego wbudowany mechanizm nie daje, a Elasticsearch tak, płacąc za to osobnym klastrem, który wymaga wiedzy o sterowaniu pamięcią maszyny wirtualnej Javy oraz o odłamkach i replikach.

Rozszerzenie do wyszukiwania po podobieństwie wektorowym pozwala trzymać osadzenia obok danych i pytać o najbliższe sąsiedztwo. Przy zbiorach do kilkuset tysięcy fragmentów jest to zwykle wystarczające i prostsze operacyjnie niż osobna baza wektorowa.

Indeksy poza podstawowym rodzajem

Domyślny rodzaj indeksu obsługuje większość przypadków i nie wszystkie, a wybór właściwego bywa różnicą między sekundą a milisekundą.

Indeks odwrócony służy do kolumn zawierających wiele wartości: dokumentów, tablic, wyszukiwania pełnotekstowego. Bez niego zapytanie o obecność klucza w kolumnie dokumentowej przegląda całą tabelę, bo domyślny rodzaj nie potrafi zajrzeć do środka wartości.

Indeks blokowy zajmuje ułamek miejsca i sprawdza się przy danych naturalnie uporządkowanych, przede wszystkim przy czasie. Tabela zdarzeń z kolumną znacznika czasu, do której dopisuje się chronologicznie, dostaje przy nim wyszukiwanie zakresowe niemal za darmo, kosztem dokładności przy danych nieuporządkowanych.

Indeks częściowy obejmuje wyłącznie wiersze spełniające warunek. Przy tabeli, w której dziewięćdziesiąt dziewięć procent rekordów ma status zakończony, a zapytania dotyczą wyłącznie aktywnych, indeks nałożony tylko na aktywne jest sto razy mniejszy i szybszy.

Indeks na wyrażeniu rozwiązuje problem opisany wcześniej: funkcja nałożona na kolumnę wyklucza zwykły indeks, natomiast indeks zbudowany na dokładnie tym samym wyrażeniu działa. To jest właściwa odpowiedź na wyszukiwanie bez rozróżniania wielkości liter.

Warto też pamiętać o koszcie. Każdy indeks spowalnia zapisy i zajmuje miejsce, więc tabela z ośmioma indeksami płaci za nie przy każdej zmianie wiersza. Indeksy nieużywane przez planistę da się wykryć w statystykach systemowych i warto to zrobić raz na jakiś czas, bo zostają po zapytaniach, których już nie ma.

Transakcje i poziomy izolacji

Domyślny poziom izolacji jest tu inny, niż zakłada część osób przychodzących z innych baz, i to rodzi błędy trudne do odtworzenia.

Domyślnie każde zapytanie w transakcji widzi migawkę danych z chwili swojego rozpoczęcia. Znaczy to, że dwa odczyty tej samej tabeli w jednej transakcji mogą zwrócić różne wyniki, jeśli między nimi ktoś zatwierdził zmianę.

Przy operacjach polegających na odczycie, decyzji i zapisie ta właściwość prowadzi do klasycznego problemu: dwa procesy czytają ten sam stan, oba uznają, że mogą działać, i oba zapisują. Objawem jest podwójna rezerwacja albo saldo, które zeszło poniżej zera mimo sprawdzenia.

Rozwiązania są trzy. Wyższy poziom izolacji, gdzie baza wykrywa konflikt i odrzuca jedną z transakcji, co wymaga obsługi ponowienia w kodzie. Blokada wiersza przy odczycie, prostsza i ograniczająca współbieżność. Albo warunek w samym zapytaniu zapisującym, tak żeby zapis nie doszedł do skutku, gdy stan się zmienił.

Ostatnia droga jest zwykle najlepsza i najrzadziej wybierana, bo wymaga przemyślenia zapytania zamiast dołożenia blokady. Aktualizacja z warunkiem sprawdzającym poprzednią wartość załatwia większość takich przypadków bez żadnej dodatkowej infrastruktury.

PostgreSQL kontra alternatywy

RozwiązanieModelUtrzymanieKiedy wybrać
PostgreSQL własnyPełna kontrolaPo Twojej stronieDuży ruch, wymogi co do lokalizacji danych
NeonZarządzany, rozdzielone warstwyU dostawcyGałęzie bazy przy podglądach wdrożeń
SupabaseZarządzany plus warstwy dodatkoweU dostawcyChcesz uwierzytelniania i magazynu w komplecie
RedisDane w pamięciZależnieBufor obok bazy, nie zamiast niej

Pierwszy wiersz opłaca się przy stałym, dużym obciążeniu i przy wymogach dotyczących miejsca przechowywania danych. Cena to praca operacyjna: kopie zapasowe, aktualizacje wersji głównych, monitorowanie i procedura odtworzenia, którą ktoś musi przećwiczyć zanim będzie potrzebna.

Warianty zarządzane przejmują tę pracę i różnią się głównie tym, co dokładają wokół. Możliwość utworzenia gałęzi bazy do podglądu wdrożenia jest przykładem rzeczy, której samodzielne odtworzenie kosztuje sporo, a przy pracy z żądaniami scalenia zmienia sposób pracy zespołu.

Ostatni wiersz przypomina o częstym nieporozumieniu. Baza w pamięci nie zastępuje relacyjnej, tylko stoi obok niej jako warstwa przyspieszająca odczyt. Traktowanie jej jako źródła prawdy jest błędem architektonicznym, do którego łatwo dojść, bo jest szybka i wygodna.

Replikacja i utrzymanie

Dwa rodzaje replikacji rozwiązują różne problemy i mylenie ich prowadzi do złych decyzji.

Replikacja fizyczna kopiuje całą bazę bajt po bajcie i służy do zapewnienia dostępności oraz rozłożenia odczytów. Replika jest wierną kopią, więc nie da się na niej zmienić schematu ani wybrać części danych.

Replikacja logiczna przesyła zmiany na poziomie wierszy i pozwala wybrać, co się replikuje. Służy do przenoszenia danych między wersjami głównymi, do integracji z innymi systemami i do aktualizacji bez długiego przestoju.

Dziewiętnastka dokłada tu kilka rzeczy, które warto znać przy planowaniu: replikowanie wartości sekwencji, włączanie replikacji logicznej bez restartu serwera oraz możliwość publikowania wszystkich tabel poza wskazanymi. Pierwsza i druga razem znacząco upraszczają aktualizację wersji głównej, która dotąd była najbardziej stresującą operacją w cyklu życia tej bazy.

Osobna sprawa to sprzątanie po zmianach. Proces działający w tle usuwa nieaktualne wersje wierszy i przy dużym ruchu bywa, że nie nadąża, co objawia się rosnącym rozmiarem tabel i wolniejszymi zapytaniami. Warto obserwować to zawczasu, bo naprawa przy rozdętej tabeli wymaga przepisania jej, a to blokuje.

Typowe błędy

Pierwszy to pozostawienie konfiguracji domyślnej na maszynie produkcyjnej. Baza użyje wtedy ułamka dostępnej pamięci, a skarga brzmi „jest wolna".

Drugi to stary koszt odczytu losowego na dysku półprzewodnikowym. Planista unika wtedy indeksów, choć są odpowiednie, i wybiera przeglądanie całej tabeli.

Trzeci to zapytanie w pętli zamiast jednego zapytania zwracającego całość. Setki przelotów sieciowych sumują się do czasu dłuższego niż jedna operacja.

Czwarty to funkcja nałożona na kolumnę w warunku. Indeks przestaje być użyteczny, a plan wykonania pokazuje przeglądanie całej tabeli mimo istniejącego indeksu.

Piąty to trzymanie danych o stałym kształcie w kolumnie dokumentowej. Traci się kontrolę typów i możliwość prostych indeksów, a zyskuje wyłącznie wygodę przy pierwszym zapisie.

Szósty to brak przećwiczonej procedury odtworzenia z kopii zapasowej. Kopia, której nikt nigdy nie odtworzył, jest założeniem, a nie zabezpieczeniem.

FAQ

Która wersja jest teraz stabilna?

Osiemnasta linia, w wydaniach poprawkowych. Dziewiętnastka jest w fazie beta, a jej wydanie stabilne planowane jest na drugą połowę 2026 roku, więc materiały opisujące jej nowości dotyczą czegoś, czego jeszcze nie postawisz na produkcji.

Co dało asynchroniczne wejście i wyjście?

Możliwość wysyłania wielu żądań odczytu naraz zamiast czekania na każde po kolei. Największy zysk widać przy skanowaniu dużych tabel i we wdrożeniach chmurowych, gdzie dysk jest zasobem sieciowym o wysokim opóźnieniu pojedynczego odczytu.

Od czego zacząć strojenie wydajności?

Od planów wykonania i indeksów, nie od konfiguracji. Brakujący indeks kosztuje więcej niż jakiekolwiek ustawienie, a po jego dodaniu warto sprawdzić rozmiar buforów i koszt odczytu losowego, bo domyślne wartości pochodzą z czasów innych dysków.

Czy PostgreSQL nadaje się do danych bez stałego schematu?

Tak, dzięki kolumnie dokumentowej z indeksowaniem i zapytaniami po zawartości. Warto natomiast używać tego do danych faktycznie zmiennych, a nie do stałych, bo przy tych drugich traci się kontrolę typów bez żadnego zysku.

Czy potrzebuję osobnej bazy wektorowej?

Przy zbiorach do kilkuset tysięcy fragmentów zwykle nie, bo rozszerzenie do wyszukiwania po podobieństwie trzyma osadzenia obok danych i upraszcza całą infrastrukturę. Przy milionach dokumentów baza przeznaczona do tego zaczyna mieć przewagę.

Dokumentacja stoi na stronie projektu, a opis nadchodzącego wydania w notatkach do wersji 19.