Jak wykorzystaliśmy opóźnioną replikację do odzyskiwania po awarii za pomocą PostgreSQL

Jak wykorzystaliśmy opóźnioną replikację do odzyskiwania po awarii za pomocą PostgreSQL
Replikacja nie jest kopią zapasową. Albo nie? Oto, jak wykorzystaliśmy replikację odroczoną, aby odzyskać siły po przypadkowym usunięciu skrótów.

Specjaliści od infrastruktury Za pracę odpowiada GitLab GitLab.com - największa w przyrodzie instancja GitLab. Z 3 milionami użytkowników i prawie 7 milionami projektów, jest to jedna z największych witryn SaaS typu open source z dedykowaną architekturą. Bez systemu baz danych PostgreSQL infrastruktura GitLab.com nie zajdzie daleko i co robimy, aby zapewnić odporność na awarie w przypadku jakichkolwiek awarii, gdy dane mogą zostać utracone. Jest mało prawdopodobne, aby taka katastrofa miała miejsce, ale jesteśmy dobrze przygotowani i zaopatrzeni w różne mechanizmy tworzenia kopii zapasowych i replikacji.

Replikacja nie jest sposobem tworzenia kopii zapasowych baz danych (patrz poniżej). Ale teraz zobaczymy, jak szybko odzyskać przypadkowo usunięte dane za pomocą leniwej replikacji: wł GitLab.com użytkownik usunąłem skrót dla projektu gitlab-ce i utracone połączenia z żądaniami scalania i zadaniami.

Dzięki replikie odroczonej odzyskaliśmy dane w zaledwie 1,5 godziny. Zobacz jak to się stało.

Odzyskiwanie danych w określonym momencie za pomocą PostgreSQL

PostgreSQL ma wbudowaną funkcję przywracającą stan bazy danych do określonego momentu. Nazywa się to Przywracanie do określonego momentu (PITR) i wykorzystuje te same mechanizmy, które dbają o aktualność repliki: zaczynając od niezawodnego snapshotu całego klastra bazy danych (bazowa kopia zapasowa), stosujemy serię zmian stanu aż do określonego momentu w czasie.

Aby skorzystać z tej funkcji do zimnego tworzenia kopii zapasowych, regularnie tworzymy podstawową kopię zapasową bazy danych i przechowujemy ją w archiwum (archiwa GitLab znajdują się w Pamięć w chmurze Google). Monitorujemy także zmiany stanu bazy danych poprzez archiwizację logu zapisu wyprzedzającego (dziennik zapisu z wyprzedzeniem, WAŁ). Mając to wszystko na miejscu, możemy wykonać PITR w celu odzyskiwania po awarii: zaczynając od migawki wykonanej przed awarią i stosując zmiany z archiwum WAL aż do awarii.

Co to jest odroczona replikacja?

Leniwa replikacja to zastosowanie zmian z WAL z opóźnieniem. Oznacza to, że transakcja nastąpiła w ciągu godziny X, ale pojawi się w replice z opóźnieniem d na godzinę X + d.

PostgreSQL ma 2 sposoby skonfigurowania fizycznej repliki bazy danych: odzyskiwanie kopii zapasowych i replikacja strumieniowa. Przywracanie z archiwum, zasadniczo działa jak PITR, ale w sposób ciągły: stale pobieramy zmiany z archiwum WAL i stosujemy je do repliki. A replikacja strumieniowa bezpośrednio pobiera strumień WAL z nadrzędnego hosta bazy danych. Preferujemy odzyskiwanie archiwów - jest łatwiejsze w zarządzaniu i ma normalną wydajność, która dotrzymuje kroku klastrowi produkcyjnemu.

Jak skonfigurować opóźnione odzyskiwanie z archiwum

Opcje odzyskiwania opisane w pliku recovery.conf. Przykład:

standby_mode = 'on'
restore_command = '/usr/bin/envdir /etc/wal-e.d/env /opt/wal-e/bin/wal-e wal-fetch -p 4 "%f" "%p"'
recovery_min_apply_delay = '8h'
recovery_target_timeline = 'latest'

Dzięki tym parametrom skonfigurowaliśmy replikę odroczoną z odzyskiwaniem kopii zapasowych. Tutaj jest używany krajka aby wyodrębnić segmenty WAL (restore_command) z archiwum, a zmiany zostaną zastosowane po ośmiu godzinach (recovery_min_apply_delay). Replika będzie obserwować zmiany na osi czasu w archiwum, na przykład z powodu przełączania awaryjnego klastra (recovery_target_timeline).

С recovery_min_apply_delay Możesz skonfigurować replikację strumieniową z opóźnieniem, ale istnieje kilka pułapek związanych z miejscami replikacji, sprzężeniem zwrotnym w trybie gotowości i tak dalej. Archiwum WAL pozwala ich uniknąć.

Parametr recovery_min_apply_delay pojawił się tylko w PostgreSQL 9.3. W poprzednich wersjach w przypadku replikacji odroczonej konieczne było skonfigurowanie kombinacji funkcje zarządzania odzyskiwaniem (pg_xlog_replay_pause(), pg_xlog_replay_resume()) lub wstrzymaj segmenty WAL w archiwum na czas opóźnienia.

Jak PostgreSQL to robi?

Ciekawie jest zobaczyć, jak PostgreSQL implementuje leniwe odzyskiwanie. Spójrzmy na recoveryApplyDelay(XlogReaderState). Nazywa się to od główna pętla powtórzeń za każdy wpis z WAL.

static bool
recoveryApplyDelay(XLogReaderState *record)
{
    uint8       xact_info;
    TimestampTz xtime;
    long        secs;
    int         microsecs;

    /* nothing to do if no delay configured */
    if (recovery_min_apply_delay <= 0)
        return false;

    /* no delay is applied on a database not yet consistent */
    if (!reachedConsistency)
        return false;

    /*
     * Is it a COMMIT record?
     *
     * We deliberately choose not to delay aborts since they have no effect on
     * MVCC. We already allow replay of records that don't have a timestamp,
     * so there is already opportunity for issues caused by early conflicts on
     * standbys.
     */
    if (XLogRecGetRmid(record) != RM_XACT_ID)
        return false;

    xact_info = XLogRecGetInfo(record) & XLOG_XACT_OPMASK;

    if (xact_info != XLOG_XACT_COMMIT &&
        xact_info != XLOG_XACT_COMMIT_PREPARED)
        return false;

    if (!getRecordTimestamp(record, &xtime))
        return false;

    recoveryDelayUntilTime =
        TimestampTzPlusMilliseconds(xtime, recovery_min_apply_delay);

    /*
     * Exit without arming the latch if it's already past time to apply this
     * record
     */
    TimestampDifference(GetCurrentTimestamp(), recoveryDelayUntilTime,
                        &secs, &microsecs);
    if (secs <= 0 && microsecs <= 0)
        return false;

    while (true)
    {
        // Shortened:
        // Use WaitLatch until we reached recoveryDelayUntilTime
        // and then
        break;
    }
    return true;
}

Najważniejsze jest to, że opóźnienie opiera się na czasie fizycznym zarejestrowanym w znaczniku czasu zatwierdzenia transakcji (xtime). Jak widać opóźnienie dotyczy tylko zatwierdzeń i nie ma wpływu na inne wpisy - wszystkie zmiany są stosowane bezpośrednio, a zatwierdzenie jest opóźnione, więc zmiany zobaczymy dopiero po skonfigurowanym opóźnieniu.

Jak używać opóźnionej repliki do przywracania danych

Załóżmy, że mamy klaster baz danych i replikę z ośmiogodzinnym opóźnieniem w produkcji. Zobaczmy, jak odzyskać dane na przykładzie przypadkowe usunięcie skrótów.

Kiedy dowiedzieliśmy się o problemie, zgłosiliśmy się przywracanie archiwum zostało wstrzymane dla odroczonej repliki:

SELECT pg_xlog_replay_pause();

Przy przerwie nie mieliśmy ryzyka, że ​​replika powtórzy żądanie DELETE. Przydatna rzecz, jeśli potrzebujesz czasu, aby wszystko przemyśleć.

Chodzi o to, że odroczona replika musi dotrzeć do momentu poprzedzającego żądanie DELETE. Znaliśmy mniej więcej fizyczny czas usunięcia. Usunęliśmy recovery_min_apply_delay i dodał recovery_target_time в recovery.conf. W ten sposób replika bezzwłocznie dociera do właściwego momentu:

recovery_target_time = '2018-10-12 09:25:00+00'

W przypadku znaczników czasu lepiej zmniejszyć nadmiar, aby nie przegapić. To prawda, że ​​im większy spadek, tym więcej danych tracimy. Ponownie, jeśli przegapimy prośbę DELETE, wszystko zostanie ponownie usunięte i będziesz musiał zacząć od nowa (lub nawet wykonać zimną kopię zapasową PITR).

Zrestartowaliśmy odroczoną instancję Postgres, a segmenty WAL były powtarzane do określonego czasu. Na tym etapie możesz śledzić postęp, zadając pytanie:

SELECT
  -- current location in WAL
  pg_last_xlog_replay_location(),
  -- current transaction timestamp (state of the replica)
  pg_last_xact_replay_timestamp(),
  -- current physical time
  now(),
  -- the amount of time still to be applied until recovery_target_time has been reached
  '2018-10-12 09:25:00+00'::timestamptz - pg_last_xact_replay_timestamp() as delay;

Jeśli sygnatura czasowa już się nie zmienia, odzyskiwanie jest zakończone. Działanie można dostosować recovery_target_actionaby zamknąć, awansować lub wstrzymać instancję po ponownej próbie (domyślnie jest zawieszona).

Baza danych powróciła do stanu sprzed tego niefortunnego żądania. Teraz możesz na przykład eksportować dane. Wyeksportowaliśmy usunięte dane etykiet oraz wszystkie linki do problemów i żądań scalania i przenieśliśmy je do produkcyjnej bazy danych. Jeżeli straty są duże, można po prostu wypromować replikę i używać jej jako głównej. Ale wtedy wszystkie zmiany po punkcie, do którego odzyskaliśmy, zostaną utracone.

Zamiast znaczników czasu lepiej jest używać identyfikatorów transakcji. Przydatne jest zapisanie tych identyfikatorów na przykład dla instrukcji DDL (takich jak DROP TABLE), używając log_statements = 'ddl'. Gdybyśmy mieli identyfikator transakcji, wzięlibyśmy go recovery_target_xid i przeprowadził wszystko aż do transakcji przed żądaniem DELETE.

Powrót do pracy jest bardzo prosty: usuń wszystkie zmiany z recovery.conf i uruchom ponownie Postgres. Replika wkrótce znów będzie miała ośmiogodzinne opóźnienie, a my jesteśmy przygotowani na przyszłe kłopoty.

Korzyści z regeneracji

Dzięki replikie odroczonej zamiast zimnej kopii zapasowej nie musisz tracić godzin na przywracanie całego obrazu z archiwum. Przykładowo wykonanie całej podstawowej kopii zapasowej o pojemności 2 TB zajmuje nam pięć godzin. A potem nadal trzeba zastosować cały dzienny WAL, aby powrócić do pożądanego stanu (w najgorszym przypadku).

Odroczona replika jest lepsza niż zimna kopia zapasowa pod dwoma względami:

  1. Nie ma potrzeby usuwania całej podstawowej kopii zapasowej z archiwum.
  2. Istnieje stałe ośmiogodzinne okno segmentów WAL, które należy powtórzyć.

Stale sprawdzamy również, czy możliwe jest wykonanie PITR z WAL, a monitorując opóźnienie odroczonej repliki, szybko zauważylibyśmy uszkodzenie lub inne problemy z archiwum WAL.

W tym przykładzie przywrócenie danych zajęło nam 50 minut, co oznacza, że ​​prędkość wynosiła 110 GB danych WAL na godzinę (archiwum było nadal włączone AWS-a3). W sumie rozwiązaliśmy problem i odzyskaliśmy dane w 1,5 godziny.

Wyniki: gdzie odroczona replika jest przydatna (a gdzie nie)

Użyj opóźnionej replikacji jako pierwszej pomocy, jeśli przypadkowo utraciłeś dane i zauważyłeś ten problem w ramach skonfigurowanego opóźnienia.

Pamiętaj jednak: replikacja nie jest kopią zapasową.

Tworzenie kopii zapasowych i replikacja mają różne cele. Zimna kopia zapasowa przyda się, jeśli przypadkowo ją wykonasz DELETE lub DROP TABLE. Wykonujemy kopię zapasową z chłodni i przywracamy poprzedni stan tabeli lub całej bazy danych. Ale jednocześnie prośba DROP TABLE jest niemal natychmiast odtwarzany we wszystkich replikach działającego klastra, więc zwykła replikacja tutaj nie pomoże. Sama replikacja utrzymuje dostępność bazy danych w przypadku wynajmu poszczególnych serwerów i rozkłada obciążenie.

Nawet w przypadku repliki odroczonej czasami naprawdę potrzebujemy zimnej kopii zapasowej w bezpiecznym miejscu, jeśli wystąpi awaria centrum danych, ukryte uszkodzenia lub inne zdarzenia, które nie są natychmiast zauważalne. Sama replikacja nie ma tutaj żadnego zastosowania.

Operacja. Na GitLab.com Obecnie zabezpieczamy przed utratą danych jedynie na poziomie systemu i nie odzyskujemy danych na poziomie użytkownika.

Źródło: www.habr.com

Dodaj komentarz