Proponuję przeczytać transkrypcję raportu z początku 2016 r. autorstwa Andrieja Salnikowa „Typowe błędy w aplikacjach prowadzące do rozdęcia bazy danych Postgresql”
W tym raporcie przeanalizuję główne błędy w aplikacjach, które pojawiają się na etapie projektowania i pisania kodu aplikacji. I wezmę pod uwagę tylko te błędy, które prowadzą do rozdęcia w Postgresql. Z reguły jest to początek końca wydajności całego systemu, chociaż początkowo nie było ku temu żadnych przesłanek.

Cieszę się, że mogę powitać wszystkich! Ten raport nie jest tak techniczny jak poprzedni od mojego kolegi. Ten raport jest skierowany głównie do programistów systemów back-end, ponieważ mamy dość dużą liczbę klientów. I wszyscy popełniają te same błędy. Opowiem wam o nich. Wyjaśnię, do czego te błędy prowadzą.

Dlaczego błędy się zdarzają? Zdarzają się z dwóch powodów: z kaprysu, może tak się uda, i z niewiedzy o pewnych mechanizmach, które zachodzą na poziomie między bazą danych a aplikacją, a także w samej bazie danych.
Podam trzy przykłady z okropnymi obrazami tego, jak źle się sprawy potoczyły. Krótko opowiem o mechanizmie, który tam zachodzi. I jak sobie z nimi radzić, gdy się pojawią, i jakie metody zapobiegawcze stosować, aby uniknąć błędów. Opowiem o narzędziach pomocniczych i podam przydatne linki.

Użyłem testowej bazy danych, w której miałem dwie tabele. Jedna tabela z kontami klientów, druga z transakcjami na tych kontach. I z pewną częstotliwością aktualizujemy salda na tych kontach.

Dane początkowe tabeli: są dość małe, 2 MB. Czas odpowiedzi bazy danych, a konkretnie tabeli, jest również bardzo dobry. A obciążenie jest całkiem dobre - 2 operacji na sekundę dla tabeli.

A w tym raporcie pokażę wam wykresy, aby było jasne, co się dzieje. Zawsze będą 2 slajdy z wykresami. Pierwszy slajd pokazuje, co dzieje się na serwerze w ogóle.
I w tej sytuacji widzimy, że mamy naprawdę małą tabelę. Indeks jest mały i ma 2 MB. To jest pierwszy wykres po lewej.
Średni czas odpowiedzi serwera jest również stabilny i mały. To jest wykres w prawym górnym rogu.
Lewy dolny wykres przedstawia najdłuższe transakcje. Widzimy, że transakcje są wykonywane szybko. A autovacuum jeszcze tutaj nie działa, ponieważ był to test startowy. Będzie działać dalej i będzie dla nas przydatne.

Drugi slajd zawsze będzie poświęcony testowanej tabeli. W tej sytuacji stale aktualizujemy salda kont klienta. I widzimy, że średni czas reakcji na operację aktualizacji jest całkiem dobry, poniżej milisekundy. Widzimy, że zasoby procesora (to jest wykres w prawym górnym rogu) są również równomiernie zużywane i są dość małe.
Wykres po prawej stronie u dołu pokazuje, ile pamięci operacyjnej i dyskowej przechodzimy w poszukiwaniu pożądanej linii przed jej aktualizacją. A liczba operacji według tabeli wynosi 2 na sekundę, jak powiedziałem na początku.

A teraz mamy tragedię. Z jakiegoś powodu dochodzi do dawno zapomnianej transakcji. Powody są zazwyczaj błahe:
- Jednym z najczęstszych jest to, że zaczęliśmy uzyskiwać dostęp do zewnętrznej usługi w kodzie aplikacji. A ta usługa nie odpowiada nam. To znaczy, otworzyliśmy transakcję, dokonaliśmy zmiany w bazie danych i przeszliśmy z aplikacji do odczytu poczty lub do innej usługi w naszej infrastrukturze, a ona z jakiegoś powodu nie odpowiada nam. A nasza sesja utknęła w stanie - nie wiadomo, kiedy zostanie rozwiązana.
- Druga sytuacja to taka, gdy z jakiegoś powodu w naszym kodzie wystąpił wyjątek. I nie przetworzyliśmy zamknięcia transakcji w wyjątku. I otrzymaliśmy zawieszoną sesję z otwartą transakcją.
- A ostatni przypadek jest również dość powszechny. To kod słabej jakości. Niektóre frameworki otwierają transakcję. Zawiesza się, a Ty możesz nie wiedzieć w aplikacji, że się zawiesza.
Dokąd prowadzą takie rzeczy?
Do tego, że nasze tabele i indeksy zaczynają gwałtownie puchnąć. To jest dokładnie ten sam efekt rozdęcia. Dla bazy danych będzie to wyrażone w tym, że nasz czas odpowiedzi bazy danych wzrośnie bardzo gwałtownie, obciążenie serwera bazy danych wzrośnie. A w rezultacie nasza aplikacja ucierpi. Bo jeśli w kodzie poświęciłeś 10 milisekund na żądanie do bazy danych, 10 milisekund na swoją logikę, to twoja funkcja działała przez 20 milisekund. I teraz twoja sytuacja będzie zupełnie smutna.
I zobaczmy, co się dzieje. Lewy dolny wykres pokazuje, że mamy długą, długą transakcję. A jeśli spojrzymy na lewy górny wykres, zobaczymy, że rozmiar tabeli nagle skoczył z dwóch megabajtów do 300 megabajtów. Jednocześnie ilość danych w tabeli się nie zmieniła, tj. jest tam całkiem sporo śmieci.

Ogólna sytuacja dotycząca średniego czasu odpowiedzi serwera również zmieniła się o kilka rzędów wielkości. Oznacza to, że wszystkie żądania do serwera zaczęły znacznie spadać. Jednocześnie uruchomiono wewnętrzne procesy Postgres, reprezentowane przez autovacuum, które próbują coś zrobić i zużywają zasoby.

Co dzieje się z naszą tabelą? To samo. Średni czas odpowiedzi dla tabeli wzrósł o kilka rzędów wielkości. Jeśli przyjrzymy się konkretnie zużytym zasobom, zobaczymy, że obciążenie procesora znacznie wzrosło. To jest wykres w prawym górnym rogu. I wzrosło, ponieważ procesor musi przejrzeć mnóstwo bezużytecznych linii w poszukiwaniu tej jednej potrzebnej. To jest wykres w prawym dolnym rogu. W rezultacie liczba wywołań na sekundę zaczęła bardzo spadać, ponieważ baza danych nie może przetworzyć takiej samej liczby żądań.

Musimy wrócić do życia. Wchodzimy do sieci i odkrywamy, że długie transakcje powodują problem. Znajdujemy i zabijamy tę transakcję. I wszystko staje się dla nas normalne. Wszystko działa tak, jak powinno.
Uspokoiliśmy się, ale po chwili zaczęliśmy zauważać, że aplikacja nie działa już tak, jak przed awarią. Żądania są nadal przetwarzane wolniej, znacznie wolniej. W moim konkretnym przykładzie półtora do dwóch razy wolniej. Obciążenie serwera jest również wyższe niż przed awarią.

A pytanie brzmi: „Co dzieje się z bazą danych w tej chwili?” A oto sytuacja, która ma miejsce w bazie danych. Na wykresie transakcji widać, że się zatrzymała i nie ma żadnych długich transakcji. Ale rozmiary tabel podczas awarii wzrosły niebezpiecznie. I nie zmniejszyły się od tego czasu. Średni czas dla bazy danych ustabilizował się. A odpowiedzi wydają się przychodzić odpowiednio szybko, z akceptowalną dla nas prędkością. Autovacuum stało się bardziej aktywne i zaczęło coś robić z tabelą, ponieważ musi przekopać więcej danych.

Konkretnie dla testowanej tabeli z kontami, gdzie zmieniamy salda: czas odpowiedzi na żądanie wydaje się wrócić do normy. Ale w rzeczywistości jest półtora raza wyższy.
I widzimy z obciążenia procesora, że obciążenie procesora nie powróciło do wymaganej wartości przed awarią. A powody tego są ukryte na dolnym prawym wykresie. Jest jasne, że pewna ilość pamięci jest tam sortowana. To znaczy, aby znaleźć wymagany wiersz, marnujemy zasoby serwera bazy danych podczas sortowania bezużytecznych danych. Liczba transakcji na sekundę się ustabilizowała.
Ogólnie jest dobrze, ale sytuacja jest gorsza niż była. Wyraźna degradacja bazy danych w wyniku naszej aplikacji, która współpracuje z tą bazą danych.

A żeby zrozumieć, co się tam dzieje, jeśli nie byłeś na poprzednim raporcie, teraz trochę teorii. Teoria o procesie wewnętrznym. Dlaczego autopróżnia i co robi?
Dosłownie krótko dla zrozumienia. W pewnym momencie mamy tabelę. W tabeli mamy linie. Te linie mogą być aktywne, żywe, potrzebne nam teraz. Na obrazku są oznaczone na zielono. I są martwe linie, które już działały, zostały zaktualizowane, pojawiły się dla nich nowe rekordy. I są oznaczone, że nie są już interesujące dla bazy danych. Ale są w tabeli ze względu na specyfikę Postgresa.
Po co nam autovacuum? W pewnym momencie autovacuum przychodzi, kontaktuje się z bazą danych i pyta: „Proszę podać mi id najstarszej transakcji, która jest obecnie otwarta w bazie danych”. Baza danych zwraca to id. A autovacuum, na jego podstawie, przechodzi przez wiersze w tabeli. I jeśli widzi, że niektóre wiersze zostały zmienione przez znacznie starsze transakcje, to ma prawo oznaczyć je jako wiersze, które możemy ponownie wykorzystać w przyszłości, zapisując tam nowe dane. Jest to proces w tle.
W tym czasie kontynuujemy pracę z bazą danych, kontynuujemy wprowadzanie zmian w tabeli. I zapisujemy nowe dane do tych wierszy, które możemy ponownie wykorzystać. I tak powstaje cykl, tzn. cały czas pojawiają się tam jakieś stare martwe wiersze, zamiast nich zapisujemy nowe wiersze, których potrzebujemy. I to jest normalny stan dla działania PostgreSQL.

Co się wydarzyło podczas wypadku? Jak tam przebiegał ten proces?
Mieliśmy tabelę w pewnym stanie, niektóre aktywne, niektóre martwe linie. Autovacuum przyszło. Zapytało bazę danych, jaka jest nasza najstarsza transakcja, jaki jest jej identyfikator. Otrzymało ten identyfikator, który mógł mieć wiele godzin lub dziesięć minut. Zależy to od tego, jak duże jest obciążenie w bazie danych. I poszło poszukać linii, które mogłoby oznaczyć jako ponownie użyte. I nie znalazło takich linii w naszej tabeli.
Ale w tym momencie kontynuujemy pracę z tabelą. Robimy coś w niej, aktualizujemy, zmieniamy dane. A co w tym momencie powinna zrobić baza danych? Nie ma innego wyjścia, jak dodać nowe wiersze na końcu istniejącej tabeli. I tak rozmiar naszej tabeli zaczyna puchnąć.
W rzeczywistości potrzebujemy zielonych linii, aby działać. Ale podczas takiego problemu otrzymujemy, że procent zielonych linii jest niezwykle niski w całej objętości tabeli.
A kiedy wykonujemy zapytanie, baza danych musi przejść przez wszystkie linie: zarówno czerwoną, jak i zieloną, aby znaleźć potrzebną linię. A efekt nadmuchania tabeli bezużytecznymi danymi nazywa się „bloat”, który również pochłania naszą przestrzeń dyskową. Pamiętasz, było 2 MB, stało się 300 MB? Teraz zmień megabajty na gigabajty, a szybko stracisz wszystkie zasoby dyskowe.

Jakie mogą być tego dla nas konsekwencje?
- W moim przykładzie tabela i indeks wzrosły 150 razy. Niektórzy nasi klienci mieli bardziej śmiertelne przypadki, gdy po prostu zaczęło im brakować miejsca na dysku.
- Rozmiar samych tabel nigdy się nie zmniejszy. Autovacuum może w niektórych przypadkach odciąć ogon tabeli, jeśli są tylko martwe linie. Ale ponieważ istnieje stała rotacja, jedna zielona linia może wisieć na końcu i nie zostać zaktualizowana, a wszystkie inne gdzieś na początku tabeli zostaną zapisane. Ale jest to tak mało prawdopodobne zdarzenie, że sama tabela zmniejszy się, że nie powinieneś na to liczyć.
- Baza danych musi przejrzeć cały stos bezużytecznych linii. A my marnujemy zasoby dyskowe, marnujemy zasoby procesora i prąd.
- I to bezpośrednio wpływa na naszą aplikację, ponieważ jeśli na początku spędziliśmy 10 milisekund na żądaniu, 10 milisekund na naszym kodzie, to podczas awarii zaczęliśmy spędzać sekundę na żądaniu i 10 milisekund na kodzie, czyli wydajność aplikacji spadła o rząd wielkości. A gdy awaria została rozwiązana, zaczęliśmy spędzać 20 milisekund na żądaniu, 10 milisekund na kodzie. Oznacza to, że nadal spadliśmy o półtora raza w wydajności. A wszystko to z powodu jednej transakcji, która się zawiesiła, i być może z naszej winy.
- I pytanie brzmi: „Jak możemy wszystko odzyskać?”, żeby wszystko było dla nas dobre, a prośby zaczęły płynąć tak szybko, jak przed wypadkiem.

W tym celu przeprowadzany jest pewien cykl prac.
Najpierw musimy znaleźć problematyczne tabele, które spuchły. Rozumiemy, że do niektórych tabel zapisuje się bardziej aktywnie, podczas gdy do innych mniej aktywnie. I w tym celu stosuje się rozszerzenie Instalując to rozszerzenie, możesz pisać zapytania, które pomogą Ci znaleźć tabele, które stały się wystarczająco rozdęte.
Gdy już znajdziesz te tabele, musisz je skompresować. Istnieją już narzędzia do tego. W naszej firmie używamy trzech narzędzi. Pierwszym z nich jest wbudowany VACUUM FULL. Jest okrutny, surowy i bezlitosny, ale czasami bardzo przydatny. и - Są to narzędzia firm trzecich do kompresji tabel. I są bardziej ostrożne z bazą danych.
Stosuje się je w zależności od tego, co jest dla Ciebie wygodniejsze. Ale o tym opowiem Ci na samym końcu. Najważniejsze jest to, że są trzy narzędzia. Jest w czym wybierać.
Gdy już wszystko naprawimy i upewnimy się, że wszystko jest w porządku, musimy dowiedzieć się, jak zapobiec takiej sytuacji w przyszłości:
- Można temu zapobiec dość łatwo. Musisz monitorować czas trwania sesji na serwerze głównym. Szczególnie niebezpieczne sesje w stanie bezczynności w transakcji. To są te, które po prostu otworzyły transakcję, zrobiły coś i odeszły, albo po prostu się rozłączyły, zagubione w kodzie.
- I ważne jest, abyście jako deweloperzy testowali swój kod, gdy pojawią się takie sytuacje. Nie jest to trudne. Będzie to przydatne sprawdzenie. Unikniecie wielu „dziecinnych” problemów związanych z długimi transakcjami.

Na tych wykresach chciałem pokazać, jak tabela i zachowanie bazy danych zmieniły się po uruchomieniu VACUUM FULL na tabeli w tym przypadku. To nie jest produkcja.
Rozmiar tabeli natychmiast powrócił do normalnego stanu roboczego kilku megabajtów. Nie miało to znaczącego wpływu na średni czas odpowiedzi serwera.

Ale konkretnie dla naszej tabeli testowej, w której aktualizowaliśmy salda kont, widzimy, że średni czas odpowiedzi na żądanie aktualizacji danych w tabeli zmniejszył się do poziomu sprzed awarii. Zasoby zużywane przez procesor do wykonania tego żądania również spadły do poziomu sprzed awarii. A prawy dolny wykres pokazuje, że teraz znajdujemy dokładnie wiersz, którego potrzebujemy od razu, bez przechodzenia przez grupę martwych wierszy, które były przed skompresowaniem tabeli. A średni czas żądania pozostał mniej więcej na tym samym poziomie. Ale tutaj mam najprawdopodobniej błąd w moim sprzęcie.

Tutaj kończy się pierwsza historia. To jest najczęstsza. I zdarza się każdemu, niezależnie od doświadczenia klienta, umiejętności programistów. Wcześniej czy później się zdarza.
Druga historia, w której rozkładamy obciążenie i optymalizujemy zasoby serwera

- Już dorośliśmy i staliśmy się poważnymi facetami. I rozumiemy, że mamy replikę i dobrze byłoby, żebyśmy zrównoważyli obciążenie: pisz do Mastera i czytaj z repliki. I zazwyczaj taka sytuacja pojawia się, gdy chcemy przygotować jakieś raporty lub ETL. A biznes jest z tego bardzo zadowolony. Naprawdę chce różnych raportów z mnóstwem złożonych analiz.
- Raporty zajmują wiele godzin, ponieważ złożonych analiz nie da się obliczyć w milisekundach. My, jak odważni faceci, piszemy kod. Wstawiamy do aplikacji, którą rejestrujemy na Masterze, i wykonujemy raporty na replice.
- Rozkładamy obciążenie.
- Wszystko działa świetnie. Jesteśmy wspaniali.

A jak wygląda ta sytuacja? Konkretnie na tych wykresach dodałem również czas trwania transakcji z repliki dla czasu trwania transakcji. Wszystkie inne wykresy odnoszą się tylko do serwera Master.
Tabela z raportami powiększyła się o ten punkt. Jest ich więcej. Widzimy, że średni czas odpowiedzi serwera jest stabilny. Widzimy, że mamy długą transakcję na replice, która działa od 2 godzin. Widzimy cichą pracę autoodkurzacza, który przetwarza martwe linie. I wszystko jest u nas w porządku.

Konkretnie dla testowanej tabeli kontynuujemy aktualizację sald na kontach tam. I mamy również stabilny czas odpowiedzi na żądania, stabilne zużycie zasobów. Wszystko jest u nas w porządku.

Wszystko jest w porządku, dopóki te raporty nie zaczną się odbijać z powodu konfliktu z replikacją. I odbijają się ze stałą częstotliwością.
Wchodzimy do sieci i zaczynamy czytać, dlaczego tak się dzieje. I znajdujemy rozwiązanie.
Pierwszym rozwiązaniem jest zwiększenie opóźnienia replikacji. Wiemy, że nasz raport działa przez 3 godziny. Ustawiamy opóźnienie replikacji na 3 godziny. Uruchamiamy wszystko, ale nadal mamy problemy z czasami uruchamianiem raportów.
Chcemy, żeby wszystko było idealne. Idziemy dalej. I znajdujemy fajne ustawienie w Internecie – hot_standby_feedback. Włączamy je. Hot_standby_feedback pozwala nam utrzymać autovacuum na Masterze. W ten sposób całkowicie pozbywamy się konfliktów replikacji. I wszystko działa dobrze z raportami.

A co dzieje się w tym czasie z serwerem Master? A z serwerem Master mamy totalną katastrofę. Teraz patrzymy na wykresy, gdy włączyłem oba te ustawienia. I widzimy, że sesja na replice w jakiś sposób zaczęła wpływać na sytuację na serwerze Master. Naprawdę wpływa, ponieważ zawiesiła automatyczne podciśnienie, które usuwa martwe wiersze. Rozmiar naszej tabeli znów gwałtownie wzrósł. Średni czas wykonywania zapytania dla całej bazy danych również gwałtownie wzrósł. Automatyczne podciśnienia trochę się nadwyrężyły.

Konkretnie w przypadku naszej tabeli widzimy, że aktualizacja danych dla niej również gwałtownie wzrosła. Podobnie znacznie wzrosło zużycie zasobów procesora. Ponownie sortujemy dużą liczbę martwych, bezużytecznych wierszy. A czas reakcji dla tej tabeli, liczba transakcji spadła.

Jak to będzie wyglądać, jeśli nie będziemy wiedzieć tego, o czym mówiłem wcześniej?
- Zaczynamy szukać problemów. Jeśli napotkaliśmy problemy w pierwszej części, wiemy, że może to być spowodowane długą transakcją i wspinamy się na Master. Problem jest w Masterze. Trzęsie się. Nagrzewa się, jego Load Average jest poniżej stu.
- Żądania są tam powolne, ale nie widzimy tam żadnych długich transakcji. I nie rozumiemy, co się dzieje. Nie wiemy, gdzie szukać.
- Sprawdzamy sprzęt serwera. Może nasz raid się zawalił. Może nasz pasek pamięci się przepalił. Wszystko może się zdarzyć. Ale nie, serwery są nowe, wszystko działa dobrze.
- Wszyscy biegają: administratorzy, deweloperzy i dyrektor. Nic nie pomaga.
- I w pewnym momencie wszystko nagle zaczyna się poprawiać.

W tym momencie żądanie repliki zostało przetworzone i wysłane. Otrzymaliśmy raport. Biznes jest nadal zadowolony. Jak widać, nasz stół znów się powiększył i nie zmniejszy się. Na wykresie sesji zostawiłem fragment tej długiej transakcji z repliki, abyś mógł ocenić, ile czasu zajmuje ustabilizowanie się sytuacji.
Sesja zniknęła. I dopiero po jakimś czasie serwer mniej więcej się uporządkuje. A średni czas odpowiedzi na żądania na serwerze Master wraca do normy. Bo w końcu autovacuum dostało możliwość posprzątania, zaznaczenia tych martwych linii. I zaczęło robić swoje. I tak szybko jak to zrobi, tak szybko my się uporządkujemy.

W tabeli testowej, w której aktualizujemy salda kont, widzimy dokładnie ten sam obraz. Średni czas aktualizacji kont również stopniowo się normalizuje. Zasoby zużywane przez procesor również maleją. A liczba transakcji na sekundę wraca do normy. Ale znowu, nie do normy, którą mieliśmy przed awarią.

W każdym razie mamy spadek wydajności, jak w pierwszym przypadku, półtora do dwóch razy, a czasami nawet większy.
Wydaje się, że zrobiliśmy wszystko poprawnie. Rozłóż obciążenie. Sprzęt nie jest bezczynny. Rozdzieliliśmy żądania mądrze, ale wszystko i tak wyszło źle.
- Nie włączać hot_standby_feedback? Tak, nie zaleca się włączania go bez bardzo ważnego powodu. Ponieważ to pokrętło bezpośrednio wpływa na serwer Master i zawiesza tam autovacuum. Włączając je w jakiejś replice i zapominając o nim, można zabić Master i mieć duże problemy z aplikacją.
- Zwiększyć max_standby_streaming_delay? Tak, w przypadku raportów — to prawda. Jeśli masz trzygodzinny raport i nie chcesz, aby się zawiesił z powodu konfliktów replikacji, po prostu zwiększ opóźnienie. Długi raport nigdy nie wymaga danych, które właśnie dotarły do bazy danych. Jeśli masz trzygodzinny raport, uruchamiasz go dla jakiegoś starego okresu danych. A dla Ciebie, czy opóźnienie trzygodzinne czy sześciogodzinne nie będzie miało znaczenia, ale będziesz otrzymywać raporty stabilnie i nie będziesz mieć problemów z ich zawieszaniem.
- Naturalnie, musisz kontrolować długie sesje na replikach, szczególnie jeśli zdecydowałeś się włączyć hot_standby_feedback na replice. Ponieważ wszystko może się zdarzyć. Dałeś tę replikę deweloperowi, aby mógł przetestować zapytania. Napisał szalone zapytanie. Uruchomił je i poszedł napić się herbaty, a my otrzymaliśmy działającego Mastera. Albo pozwoliliśmy tam na niewłaściwą aplikację. Sytuacje są różne. Sesje na replikach muszą być kontrolowane tak ostrożnie, jak na Masterze.
- A jeśli masz szybkie i długie zapytania do replik, to w takim przypadku lepiej je rozdzielić w celu rozłożenia obciążenia. To jest link do streaming_delay. W przypadku szybkich zapytań, miej jedną replikę z małym opóźnieniem replikacji. W przypadku długich zapytań raportowania, miej replikę, która może opóźnić się o 6 godzin, o dzień. To jest całkowicie normalna sytuacja.
Skutki eliminujemy w ten sam sposób:
- Znajdujemy napompowane tabele.
- A my kompresujemy je przy użyciu najwygodniejszego dla nas narzędzia.
Druga historia kończy się tutaj. Przejdźmy do trzeciej historii.

Również dość powszechne jest u nas, gdzie dokonujemy migracji.

- Każdy produkt programowy rośnie. Wymagania wobec niego się zmieniają. Chcemy się rozwijać w każdym przypadku. I zdarza się, że musimy zaktualizować dane w tabeli, a mianowicie uruchomić aktualizację pod kątem naszej migracji do nowej funkcjonalności, którą wdrażamy jako część naszego rozwoju.
- Stary format danych nie jest zadowalający. Powiedzmy, że teraz przejdziemy do drugiej tabeli, gdzie mam transakcje na tych kontach. I powiedzmy, że były w rublach, ale postanowiliśmy zwiększyć dokładność i zrobić to w kopiejkach. I w tym celu musimy zrobić aktualizację: pomnożyć pole z kwotą transakcji przez sto.
- W dzisiejszym świecie używamy zautomatyzowanych środków kontroli wersji baz danych. Powiedzmy, . Rejestrujemy tam naszą migrację. Testujemy ją na naszej bazie testowej. Wszystko jest w porządku. Aktualizacja przechodzi. Blokuje pracę na jakiś czas, ale otrzymujemy zaktualizowane dane. I możemy uruchomić nową funkcjonalność na tym. Testowaliśmy i sprawdzaliśmy wszystko. Wszystko zostało potwierdzone.
- Wykonaliśmy zaplanowane prace i przeprowadziliśmy migrację.

Oto migracja z aktualizacją, którą Ci zaprezentowano. Ponieważ są to transakcje mojego konta, tabela miała 15 GB. A ponieważ aktualizujemy każdy wiersz, rozdmuchaliśmy tabelę dwa razy aktualizacją, ponieważ przepisaliśmy każdy wiersz.

Podczas migracji nie mogliśmy nic zrobić z tą tabelą, ponieważ wszystkie żądania do niej były w kolejce i czekały na zakończenie tej aktualizacji. Ale tutaj chcę zwrócić uwagę na liczby na osi pionowej. To znaczy, że mamy średni czas żądania przed migracją około 5 milisekund i obciążenie procesora, liczba operacji blokowych do odczytu pamięci dyskowej jest mniejsza niż 7,5.

Przeprowadziliśmy migrację i znów pojawiły się problemy.
Migracja zakończyła się sukcesem, ale:
- Wykonywanie starych funkcji zajmuje teraz więcej czasu.
- Tabela znów się powiększyła.
- Obciążenie serwera znów stało się większe niż poprzednio.
- I oczywiście wciąż pracujemy nad funkcjonalnością, która działała dobrze, trochę ją udoskonaliliśmy.
I to jest znowu rozdęcie, które znów rujnuje nam życie.

Tutaj pokazuję, że tabela, podobnie jak w dwóch poprzednich przypadkach, nie powróci do swojego poprzedniego rozmiaru. Średnie obciążenie serwera wydaje się być odpowiednie.

A jeśli spojrzymy na tabelę z kontami, zobaczymy, że średni czas zapytania wzrósł dwukrotnie w porównaniu do tej tabeli. Obciążenie procesora i liczba wierszy w pamięci, które są sortowane, skoczyły powyżej 7,5, i były niższe. I skoczyły w przypadku procesorów 2-krotnie, w przypadku operacji blokowych 1,5-krotnie, czyli dostaliśmy degradację wydajności serwera. A w konsekwencji - degradację wydajności naszej aplikacji. Jednocześnie liczba wywołań pozostała mniej więcej na tym samym poziomie.

I tutaj najważniejsze jest zrozumienie, jak prawidłowo wykonywać takie migracje. I trzeba je wykonywać. Robimy te migracje dość stale.
- Tak duże migracje nie są wykonywane automatycznie. Zawsze muszą być kontrolowane.
- Musi być nadzorowane przez osobę posiadającą wiedzę. Jeśli masz w zespole DBA, pozwól mu to zrobić. To jego zadanie. Jeśli nie, pozwól zrobić to osobie najbardziej doświadczonej, która wie, jak pracować z bazami danych.
- Nowy schemat bazy danych zawsze przygotowujemy etapami, nawet jeśli aktualizujemy jedną kolumnę, czyli z wyprzedzeniem, przed wdrożeniem nowej wersji aplikacji:
- Dodano nowe pola, do których będziemy wpisywać zaktualizowane dane.
- Przenosimy dane ze starego pola do nowego pola w małych częściach. Dlaczego to robimy? Po pierwsze, zawsze kontrolujemy proces tego procesu. Wiemy, że przenieśliśmy już tak wiele partii i tak wiele nam zostało.
- A drugi pozytywny efekt jest taki, że pomiędzy każdą taką partią zamykamy transakcję, otwieramy nową i to daje odkurzaczowi możliwość pracy według tabeli, oznaczając terminy ponownego użycia.
- Dla linii, które pojawią się podczas działania aplikacji (wciąż mamy uruchomioną starą aplikację), dodajemy wyzwalacz, który zapisuje nowe wartości do nowych pól. W naszym przypadku jest to pomnożenie starej wartości przez sto.
- Jeśli jesteśmy naprawdę uparci i chcemy tego samego pola, to po zakończeniu wszystkich migracji i przed wdrożeniem nowej wersji aplikacji po prostu zmieniamy nazwy pól. Starych na jakieś wymyślone nazwy, a nowych na stare.
- Dopiero potem uruchamiamy nową wersję aplikacji.
Jednocześnie nie doświadczymy rozdęcia systemu i spadku wydajności.
W tym miejscu kończy się trzecia historia.

A teraz trochę więcej szczegółów na temat narzędzi, o których wspomniałem w pierwszej historii.
Przed rozpoczęciem wyszukiwania rozszerzeń należy zainstalować rozszerzenie .
Abyś nie musiał wymyślać zapytań, już napisaliśmy te zapytania w naszej pracy. Możesz ich użyć. Dwa zapytania są tutaj przedstawione.
- Pierwszy działa dość długo, ale pokaże Ci dokładne wartości wzdęcia zgodnie z tabelą.
- Drugi działa szybciej i jest bardzo skuteczny, gdy trzeba szybko ocenić, czy w tabeli występuje rozdęcie, czy nie. I należy również zrozumieć, że w tabeli Postgres zawsze występuje rozdęcie. Jest to cecha modelu MVCC.
- A 20% rozdęcie jest normalne dla tabel w większości przypadków. Więc nie musisz się martwić o kompresję tej tabeli.
Dowiedzieliśmy się, jak identyfikować tabele, które stały się rozdęte, a także te, w których znajdują się bezużyteczne dane.
A teraz kilka słów o tym, jak poradzić sobie z wzdęciami:
- Jeśli mamy małą tabelę i dobre dyski, tj. na tabeli do gigabajta, całkiem możliwe jest użycie VACUUM FULL. Założy wyłączną blokadę tabeli na kilka sekund i to jest w porządku, ale zrobi wszystko szybko i sztywno. Co robi VACUUM FULL? Założy wyłączną blokadę tabeli i przepisuje wiersze na żywo ze starych tabel do nowej tabeli. A na końcu je zamienia. Usuwa stare pliki, zastępuje je nowymi. Ale na czas swojej pracy zakłada wyłączną blokadę tabeli. Oznacza to, że nie będziesz mógł nic zrobić z tą tabelą: ani do niej zapisywać, ani z niej czytać, ani jej modyfikować. A VACUUM FULL wymaga dodatkowej przestrzeni dyskowej do zapisywania danych.
- Następne narzędzie . W swojej zasadzie jest bardzo podobny do VACUUM FULL, ponieważ również przepisuje dane ze starych plików do nowych i zastępuje je w tabeli. Ale nie przyjmuje wyłącznej blokady na tabeli na samym początku swojej pracy, ale przyjmuje ją dopiero w momencie, gdy ma już gotowe dane do zastąpienia plików. Jego wymagania dotyczące zasobów dyskowych są podobne do tych z VACUUM FULL. Potrzebujesz dodatkowej przestrzeni dyskowej, a to może być czasami krytyczne, jeśli masz tabele terabajtowe. I jest dość procesorożerny, ponieważ aktywnie pracuje z wejściem-wyjściem.
- Trzecią użytecznością jest . Jest bardziej ostrożny z zasobami, ponieważ działa na nieco innych zasadach. Głównym punktem pgcompacttable jest to, że przenosi wszystkie aktywne wiersze na początek tabeli z aktualizacjami. A następnie rozpoczyna próżnię w tej tabeli, ponieważ wiemy, że mamy aktywne wiersze na początku, a martwe wiersze na końcu. A sama próżnia odcina ten ogon, tzn. nie wymaga dużo dodatkowej przestrzeni dyskowej. A jednocześnie nadal można ją skompresować pod względem zasobów.
To tyle na temat narzędzi.

Jeśli temat wzdęć wydaje Ci się interesujący i chcesz się zgłębić, oto kilka przydatnych linków:
- - to jest raport mojego kolegi. Jest ogólny o tym, gdzie idzie przestrzeń Postgresa w trakcie jego pracy i życia. I jest bardzo duży i szczegółowy techniczny artykuł dla administratorów baz danych o rozdęciu.
- – to jest link do naszego repozytorium, w którym przechowujemy wiele przydatnych skryptów do sprawdzania stanu bazy danych. Można tam znaleźć skrypty do wyszukiwania bloat.
- и Linki do narzędzi, które pomogą Ci skompresować tabele.
- - to jest post mojego kolegi. Tam on całkiem poważnie i szczegółowo analizuje technicznie bloat dokładnie na poziomie bliskim administratorom.
Próbowałem pokazać tutaj historię grozy dla deweloperów, ponieważ są oni naszymi bezpośrednimi klientami baz danych i powinni zrozumieć, do czego i do czego prowadzą działania. Mam nadzieję, że mi się udało. Dziękuję za uwagę!
pytania
Dziękuję za raport! Mówiłeś o tym, jak identyfikować problemy. Ale jak można im zapobiec? Mam na myśli sytuację, w której żądania zawieszały się nie tylko dlatego, że uzyskiwały dostęp do zewnętrznych usług. To były po prostu jakieś dzikie połączenia. Były jakieś maleńkie, nieszkodliwe żądania, które zawieszały się przez dzień, a potem zaczęły robić jakieś bzdury. Mam na myśli, że jest to bardzo podobne do tego, co opisujesz. Jak można to śledzić? Siedzieć i ciągle patrzeć, które żądanie zawiesza się? Jak można temu zapobiec?
W tym przypadku jest to zadanie dla administratorów Twojej firmy, a niekoniecznie dla administratora baz danych.
Jestem administratorem.
PostgreSQL ma widok o nazwie pg_stat_activity, który pokazuje wiszące zapytania. I możesz zobaczyć, jak długo tam wisi.
Czy mam wchodzić i sprawdzać co 5 minut?
Skonfiguruj cron i sprawdź. Jeśli masz długoterminową prośbę, napisz list i to wszystko. To znaczy, nie musisz patrzeć oczami, można to zautomatyzować. Otrzymasz list, zareagujesz na niego. Albo możesz strzelać automatycznie.
Czy istnieją jakieś oczywiste powody, dla których tak się dzieje?
Wymieniłem kilka. Inne są bardziej złożonymi przykładami. I tam rozmowa może trwać długo.
Dzięki za raport! Chciałem wyjaśnić kwestię narzędzia pg_repack. Jeśli nie tworzy ono blokady wyłącznej, to…
Ona tworzy ekskluzywny zamek.
... wtedy potencjalnie mogę stracić dane. Moja aplikacja nie powinna nic zapisywać w tym czasie?
Nie, działa z tabelą cicho, tzn. pg_repack najpierw przesyła wszystkie aktywne linie, które tam są. Naturalnie, jakiś wpis do tabeli tam występuje. Dodaje tylko ten ogon.
Więc w końcu to robi?
Na koniec następuje wyłączna blokada możliwości podmiany tych plików.
Czy będzie to szybsze niż VACUUM FULL?
VACUUM FULL, jak tylko się uruchomiło, od razu wzięło blokadę wyłączności. I dopóki nie zrobi wszystkiego, nie zwolni jej. A pg_repack bierze blokadę wyłączności tylko w momencie podmiany plików. W tym momencie nie zapiszesz tam, ale dane nie zostaną utracone, wszystko będzie w porządku.
Cześć! Mówiłeś o autovacuum. Był wykres z czerwonymi, żółtymi i zielonymi komórkami rejestrującymi. To znaczy, żółte były oznaczone jako usunięte. I w rezultacie można w nich coś nowego zapisać?
Tak. Postgres nie usuwa wierszy. Ma taką specyfikę. Jeśli aktualizujemy wiersz, oznaczamy stary jako usunięty. Identyfikator transakcji, który zmienił ten wiersz, pojawia się tam i piszemy nowy wiersz. I mamy sesje, które potencjalnie mogą je odczytać. W pewnym momencie stają się bardzo stare. I istotą autovacuum jest to, że przechodzi przez te wiersze i oznacza je jako niepotrzebne. I można tam przepisać dane.
Rozumiem. Ale pytanie jest trochę nie na temat. Nie dokończyłem. Załóżmy, że mamy tabelę. Ma pola o zmiennej wielkości. A jeśli spróbuję wstawić coś nowego, może się to po prostu nie zmieścić w starej komórce.
Nie, cały wiersz jest i tak aktualizowany. Postgres ma dwa modele przechowywania danych. Wybiera na podstawie typu danych. Są dane, które są przechowywane bezpośrednio w tabeli, a także są dane tos. Są to duże ilości danych: tekst, json. Są przechowywane w oddzielnych tabelach. I ta sama historia z rozdęciem ma miejsce w przypadku tych tabel, tzn. wszystko jest takie samo. Są po prostu usuwane osobno.
Dzięki za raport! Jak akceptowalne jest używanie statement timeout do ograniczania czasu trwania żądań?
Bardzo akceptowalne. Używamy go wszędzie. A ponieważ nie mamy własnych usług, zapewniamy zdalne wsparcie, mamy dość różnorodnych klientów. I wszyscy są z niego zadowoleni. To znaczy, mamy zadania cron, które sprawdzają. Po prostu uzgadniamy z klientem czas trwania sesji, przed którymi ich nie zabijamy. Może to być minuta, może to być 10 minut. Zależy to od obciążenia bazy danych i jej celu. Ale wszyscy używamy pg_stat_activity.
Dziękuję za raport! Próbuję zastosować Twój raport do moich aplikacji. I wygląda na to, że zaczynamy transakcję wszędzie i wyraźnie ją kończymy wszędzie. Jeśli jest jakiś wyjątek, to wycofanie i tak się dzieje. I wtedy o tym pomyślałem. W końcu transakcja może rozpocząć się niejawnie. To jest prawdopodobnie wskazówka dla dziewczyny. Jeśli po prostu zaktualizuję rekord, czy transakcja rozpocznie się w PostgreSQL i zakończy się dopiero po rozłączeniu połączenia?
Jeśli teraz mówisz o poziomie aplikacji, to zależy to od używanego sterownika, używanego ORM. Jest tam wiele ustawień. Jeśli masz włączone automatyczne zatwierdzanie, transakcja rozpoczyna się tam i jest natychmiast zamykana.
Czyli zamyka się od razu po aktualizacji?
Zależy to od ustawień. Wspomniałem o jednym ustawieniu. Jest to auto commit on. Jest to dość powszechne. Jeśli jest włączone, transakcja jest otwierana i zamykana. Jeśli nie powiedziałeś wprost „rozpocznij transakcję” i „zakończ transakcję”, ale po prostu uruchomiłeś żądanie do sesji.
Cześć! Dziękuję za raport! Wyobraźmy sobie, że mamy bazę danych, która ciągle rośnie i rośnie, a potem na serwerze kończy się miejsce. Czy są jakieś narzędzia, aby naprawić tę sytuację?
Dobrym pomysłem byłoby monitorowanie przestrzeni serwerowej.
Na przykład DBA poszedł na herbatę, był w ośrodku wypoczynkowym, itp.
Kiedy tworzony jest system plików, w jego obrębie tworzona jest przynajmniej pewna ilość przestrzeni rezerwowej, w której nie są zapisywane żadne dane.
A co jeśli zniknie całkowicie?
Tam nazywa się to przestrzenią zarezerwowaną, tzn. można ją zwolnić i w zależności od tego, jak duża została utworzona, otrzymasz wolną przestrzeń. Domyślnie nie wiem, ile jej jest. A w innym przypadku - dostarcz dyski, aby mieć miejsce na wykonanie operacji odzyskiwania. Możesz usunąć jakąś tabelę, co do której masz pewność, że jej nie potrzebujesz.
Nie masz innych narzędzi?
To zawsze jest praca ręczna. I na miejscu ustala się, co najlepiej tam zrobić, ponieważ są dane krytyczne, są dane niekrytyczne. A dla każdej bazy danych i aplikacji, która z nią pracuje, zależy to od biznesu. Zawsze decyduje się na miejscu.
Dziękuję za raport! Mam dwa pytania. Po pierwsze, pokazałeś slajdy, na których pokazano, że w przypadku zawieszonych transakcji zarówno przestrzeń tabeli, jak i rozmiar indeksu rosną. A potem w raporcie było mnóstwo narzędzi, które pakują tabelę. A co z indeksem?
Oni również je pakują.
Ale próżnia nie wpływa na indeks?
Niektóre działają z indeksem. Na przykład pg_rapack, pgcompacttable. Vacuum odtwarza indeksy, wpływa na nie. Celem VACUUM FULL jest przepisanie wszystkiego, tzn. działa ze wszystkimi.
I drugie pytanie. Nie rozumiałem, dlaczego raporty o replikach tak bardzo zależą od samej replikacji. Wydawało mi się, że raporty to czytanie, a replikacja to pisanie.
Co powoduje konflikt replikacji? Mamy Mastera, w którym zachodzą procesy. Mamy autovacuum. Co tak naprawdę robi autovacuum? Wycina niektóre stare linie. Jeśli w tym momencie mamy zapytanie do repliki, które odczytuje te stare linie, a w Masterze była sytuacja, w której autovacuum oznaczyło te linie jako możliwe do przepisania, to przepisaliśmy je. I otrzymaliśmy pakiet danych, kiedy powinniśmy przepisać te linie, które są potrzebne do zapytania do repliki, to proces replikacji będzie czekał na skonfigurowany limit czasu. A następnie PostgreSQL zdecyduje, co jest dla niego ważniejsze. A replikacja jest dla niego ważniejsza niż zapytanie i uruchomi zapytanie, aby wykonać te zmiany w replice.
Andrey, mam pytanie. Czy te wspaniałe wykresy, które pokazałeś podczas prezentacji, są wynikiem jakiejś twojej użyteczności? Czego użyłeś do zbudowania wykresów?
To jest usługa .
Czy to jest produkt komercyjny?
Tak, jest to produkt komercyjny.
Źródło: www.habr.com
