Kilka tygodni temu wyjaśniłem podstawy tuningu autovacuum. Na koniec tego posta obiecałem, że wkrótce zajmę się problemami z odkurzaniem. Cóż, zajęło to trochę dłużej niż planowałem, ale zaczynamy.
Aby szybko podsumować, autovacuum
jest procesem w tle, który oczyszcza martwe rzędy, np. stare usunięte wersje wierszy. Możesz również wykonać czyszczenie ręcznie, uruchamiając VACUUM
, ale autovacuum
robi to automatycznie w zależności od ilości martwych wierszy w tabeli, we właściwym momencie – nie za często, ale wystarczająco często, aby kontrolować ilość „śmieci”.
Ogólnie rzecz biorąc, autovacuum
nie może działać zbyt często – czyszczenie następuje dopiero po osiągnięciu pewnej liczby martwych wierszy zgromadzonych w tabeli. Jednak może to być opóźnione z różnych powodów, w wyniku czego tabele i indeksy stają się większe niż pożądane. I to jest dokładnie temat tego postu. Więc jacy są najczęstsi winowajcy i jak ich zidentyfikować?
Ograniczanie
Jak wyjaśniono w podstawach strojenia, autovacuum
pracownicy są ograniczani do wykonywania tylko określonej ilości pracy w przedziale czasu. Domyślne limity są dość niskie – około 4 MB/s zapisów, 8 MB/s odczytów. Jest to odpowiednie dla małych maszyn, takich jak Raspberry Pi lub małe serwery sprzed 10 lat, ale obecne maszyny są znacznie potężniejsze (zarówno pod względem procesora, jak i we/wy) i obsługują znacznie więcej danych.
Wyobraź sobie, że masz kilka dużych stołów i kilka małych. Jeśli wszystkie trzy autovacuum
pracownicy zaczynają sprzątać duże stoły, żaden z małych nie zostanie odkurzony, niezależnie od ilości martwych rzędów, które gromadzą. Zidentyfikowanie tego nie jest szczególnie trudne, zakładając, że masz wystarczający monitoring. Poszukaj okresów, w których wszystkie autovacuum
pracownicy są zajęci, podczas gdy stoły nie są odkurzane pomimo zgromadzenia wielu martwych rzędów.
Wszystkie niezbędne informacje znajdują się w pg_stat_activity
(liczba autovacuum
procesy robocze) i pg_stat_all_tables
(last_autovacuum
i n_dead_tup
).
Zwiększanie liczby autovacuum
pracowników nie jest rozwiązaniem, ponieważ całkowita ilość pracy pozostaje taka sama. Możesz określić limity ograniczania przepustowości na tabelę, wyłączając tego pracownika z całkowitego limitu, ale to nadal nie gwarantuje, że w razie potrzeby będą dostępni pracownicy.
Właściwym rozwiązaniem jest dostrojenie ograniczania przepustowości przy użyciu limitów rozsądnych w odniesieniu do konfiguracji sprzętowej i wzorców obciążenia. Niektóre podstawowe zalecenia dotyczące ograniczania przepustowości zostały wymienione w poprzednim poście. (Oczywiście, jeśli możesz zmniejszyć liczbę martwych wierszy generowanych w bazie danych, byłoby to idealne rozwiązanie.)
Od tego momentu będziemy zakładać, że dławienie nie jest problemem, tj. że autovacuum
pracownicy nie są nasyceni przez długi czas, a czyszczenie jest uruchamiane na wszystkich stołach bez nieuzasadnionych opóźnień.
Długie transakcje
Tak więc, jeśli stół jest regularnie odkurzany, z pewnością nie może gromadzić wielu martwych rzędów, prawda? Niestety nie. Wiersze nie są faktycznie „usuwalne” natychmiast po usunięciu, ale tylko wtedy, gdy nie ma żadnych transakcji, które mogłyby je zobaczyć. Dokładne zachowanie zależy od tego, co robią (były) inne transakcje i od poziomu serializacji, ale ogólnie:
PRZECZYTAJ ZAANGAŻOWANO
- uruchamianie zapytań blokuje czyszczenie
- bezczynne transakcje blokują czyszczenie tylko wtedy, gdy wykonały zapis
- bezczynne transakcje (bez żadnych zapisów) nie blokują czyszczenia (ale i tak nie jest dobrą praktyką trzymanie ich w pobliżu)
SERIALIZOWALNY
- uruchamianie zapytań blokuje czyszczenie
- bezczynne transakcje blokują czyszczenie (nawet jeśli tylko odczytywały)
W praktyce jest to oczywiście bardziej zniuansowane, ale wyjaśnienie wszystkich różnych bitów wymagałoby najpierw wyjaśnienia, jak działają XID i migawki, a to nie jest celem tego postu. To, co naprawdę powinieneś wziąć z tego, to to, że długie transakcje to zły pomysł, szczególnie jeśli te transakcje mogły spowodować zapisy.
Oczywiście istnieją całkowicie uzasadnione powody, dla których możesz potrzebować przechowywać transakcje przez długi czas (np. jeśli musisz zapewnić KWAS dla wszystkich zmian). Ale upewnij się, że nie dzieje się to niepotrzebnie, np. z powodu złego projektu aplikacji.
Nieco nieoczekiwaną konsekwencją tego jest wysokie zużycie procesora i we/wy, ze względu na autovacuum
biegać w kółko, bez czyszczenia martwych rzędów (lub tylko kilku z nich). Z tego powodu stoły nadal kwalifikują się do czyszczenia w następnej rundzie, powodując więcej szkody niż pożytku.
Jak to wykryć? Po pierwsze, musisz monitorować długoterminowe transakcje, szczególnie te bezczynne. Wszystko, co musisz zrobić, to odczytać dane z pg_stat_activity
. Definicja widoku zmienia się nieco w wersji PostgreSQL, więc być może trzeba będzie to trochę poprawić:
SELECT xact_start, state FROM pg_stat_activity; -- count 'idle' transactions longer than 15 minutes (since BEGIN) SELECT COUNT(*) FROM pg_stat_activity WHERE state = 'idle in transaction' AND (now() - xact_start) > interval '15 minutes' -- count transactions 'idle' for more than 5 minutes SELECT COUNT(*) FROM pg_stat_activity WHERE state = 'idle in transaction' AND (now() - state_change) > interval '5 minutes'
Możesz też po prostu użyć jakiejś istniejącej wtyczki monitorującej, np. check_postgres.pl. Obejmują one już ten rodzaj kontroli rozsądku. Musisz zdecydować, jaki jest rozsądny czas trwania transakcji/zapytania, który zależy od aplikacji.
Od PostgreSQL 9.6 możesz również użyć idle_in_transaction_session_timeout
aby transakcje bezczynne przez zbyt długi czas były automatycznie kończone. Podobnie w przypadku długich zapytań istnieje statement_timeout
.
Kolejną przydatną rzeczą jest VACUUM VERBOSE
co faktycznie powie ci, ile martwych wierszy nie można jeszcze usunąć:
db=# VACUUM verbose z; INFO: vacuuming "public.z" INFO: "z": found 0 removable, 66797 nonremovable row versions in 443 out of 443 pages DETAIL: 12308 dead row versions cannot be removed yet. ...
Nie powie ci, który backend uniemożliwia czyszczenie, ale jest to całkiem wyraźny znak tego, co się dzieje.
Uwaga: . Nie możesz łatwo uzyskać tych informacji z autovacuum
ponieważ jest rejestrowany tylko za pomocą DEBUG2
domyślnie (i na pewno nie chcesz działać z tym poziomem dziennika w środowisku produkcyjnym).
Długie zapytania dotyczące gorącego czuwania
Załóżmy, że tabele są odkurzane w odpowiednim czasie, ale nie usuwają martwych krotek, co powoduje rozdęcie tabeli i indeksu. Monitorujesz pg_stat_activity
i nie ma długotrwałych transakcji. Jaki może być problem?
Jeśli masz replikę strumieniową, prawdopodobnie występuje problem. Jeśli replika używa hot_standby_feedback=on
, zapytania dotyczące repliki działają prawie jak transakcje na podstawowym, w tym blokowanie czyszczenia. Oczywiście hot_standby_feedback=on
jest używany dokładnie podczas uruchamiania długich zapytań (np. obciążeń analitycznych i BI) na replikach, aby zapobiec anulowaniu z powodu konfliktów replikacji.
Niestety, będziesz musiał wybrać – albo pozostaw hot_standby_feedback=on
i akceptuj opóźnienia w sprzątaniu lub zajmuj się anulowanymi zapytaniami. Możesz także użyć max_standby_streaming_delay
aby ograniczyć wpływ, chociaż nie zapobiega to całkowicie anulowaniu (więc nadal musisz ponawiać zapytania).
Właściwie jest teraz trzecia opcja – replikacja logiczna. Zamiast korzystać z fizycznej replikacji strumieniowej dla repliki BI, możesz skopiować zmiany za pomocą nowej replikacji logicznej, dostępnej w PostgreSQL 10. Replikacja logiczna rozluźnia sprzężenie między pierwotną a repliką i sprawia, że klastry są w większości niezależne (są czyszczone niezależnie, itp.).
Rozwiązuje to dwa problemy związane z fizyczną replikacją strumieniową — opóźnione czyszczenie podstawowych lub anulowanych zapytań w replice BI. W przypadku replik służących do celów DR replikacja strumieniowa pozostaje jednak właściwym wyborem. Ale te repliki nie są (lub nie powinny) uruchamiać długich zapytań.
Uwaga: Chociaż wspomniałem, że replikacja logiczna będzie dostępna w PostgreSQL 10, znaczna część infrastruktury była dostępna w poprzednich wydaniach (szczególnie w PostgreSQL 9.6). Więc możesz to zrobić nawet w starszych wersjach (zrobiliśmy to dla niektórych naszych klientów), ale PostgreSQL 10 sprawi, że będzie to znacznie wygodniejsze i wygodniejsze.
Problem z autoanalyze
Szczegółem, który możesz przegapić, jest to, że autovacuum
pracownicy faktycznie wykonują dwa różne zadania. Po pierwsze czyszczenie (jak przy uruchamianiu VACUUM
), ale także zbieranie statystyk (jak przy uruchamianiu ANALYZE
). I oba części są dławione za pomocą autovacuum_cost_limit
.
Ale jest duża różnica w obsłudze transakcji. Ilekroć VACUUM
część osiąga autovacuum_cost_limit
, pracownik zwalnia migawkę i przez chwilę śpi. ANALYZE
jednak musi działać w jednej migawce/transakcji, co tak blokowanie czyszczenia.
To elegancki sposób na strzelenie sobie w stopę, szczególnie jeśli robisz też coś takiego:
- zwiększ
default_statistics_target
budować dokładniejsze statystyki z większych próbek - niższy
autovacuum_analyze_scale_factor
aby częściej zbierać statystyki
Niezamierzoną konsekwencją jest oczywiście to, że ANALYZE
będzie się zdarzać częściej, potrwa znacznie dłużej i będzie (w przeciwieństwie do VACUUM
część) zapobiec czyszczeniu. Rozwiązanie jest zazwyczaj dość proste – nie obniżaj autovacuum_analyze_scale_factor
zbyt wiele. Uruchamianie ANALYZE
za każdym razem 10% zmian w tabeli powinno w większości przypadków wystarczyć.
n_dead_tup
Ostatnią rzeczą, o której chciałbym wspomnieć, są zmiany w pg_stat_all_tables.n_dead_tup
wartości. Możesz pomyśleć, że wartość jest prostym licznikiem, zwiększanym za każdym razem, gdy tworzona jest nowa martwa krotka i zmniejszana za każdym razem, gdy jest czyszczona. Ale w rzeczywistości jest to tylko szacunkowa liczba martwych krotek, zaktualizowana przez ANALYZE
. W przypadku małych tabel (mniej niż 240 MB) nie jest to duża różnica, ponieważ ANALYZE
czyta całą tabelę, więc jest całkiem dokładny. W przypadku dużych tabel może się to jednak nieco zmienić w zależności od tego, jaki podzbiór tabeli jest próbkowany. I obniżenie autovacuum_vacuum_scale_factor
sprawia, że jest bardziej losowy.
Więc bądź ostrożny patrząc na n_dead_tup
w systemie monitoringu. Nagłe spadki lub wzrosty wartości mogą być po prostu spowodowane ANALYZE
ponowne obliczenie innego oszacowania, a nie z powodu rzeczywistego czyszczenia i/lub nowych martwych krotek pojawiających się w tabeli.
Podsumowanie
Podsumowując to w kilku prostych punktach:
autovacuum
może działać tylko wtedy, gdy nie ma transakcji, które mogłyby potrzebować martwych krotek.- Długo działające zapytania blokują czyszczenie. Rozważ użycie
statement_timeout
aby ograniczyć szkody. - Długotrwała transakcja może blokować czyszczenie. Dokładne zachowanie zależy od takich rzeczy, jak poziom izolacji lub to, co wydarzyło się w transakcji. Monitoruj je i usuwaj je, jeśli to możliwe.
- Długotrwałe zapytania dotyczące replik z
hot_standby_feedback=on
może również blokować czyszczenie. autoanalyze
jest również dławiony, ale w przeciwieństwie doVACUUM
część przechowuje pojedynczy zrzut (a tym samym blokuje czyszczenie).n_dead_tup
to tylko oszacowanie obsługiwane przezANALYZE
, więc spodziewaj się pewnych wahań (szczególnie przy dużych stołach).