9 Obsługa błędów i sytuacji awaryjnych


9.1 Wprowadzenie
9.2 Najważniejsze definicje i postulaty projektowe
9.3 Zapewnianie odporności na awarie
9.4 Konsekwencje niektórych rodzajów awarii
9.5 Modele obsługi błędów
9.6 Podsumowanie


9.1 Wprowadzenie

Niniejszy rozdział ma dosyć wyjątkowy charakter, ponieważ rozważania w nim zawarte są jakby równoległe do wszystkich pozostałych. Rozdziały 2 – 8 napisano przy założeniu, że kwestię awarii chwilowo pomijamy, dla prostoty opisu i klarowności rozważań. Obecnie skupimy się właśnie na awariach, odnosząc się w tym kontekście do wszystkich dotychczas omawianych kwestii: warstw administracyjnej i operacyjnej, ich współdziałania, architektury systemu oraz sposobu udostępniania zasobów klientom. 

Na początku tego rozdziału wprowadzamy kilka najważniejszych pojęć i formułujemy główne postulaty, którym podporządkowana będzie dalsza analiza. W kolejnym punkcie (9.3) wyliczamy niektóre sposoby zapewniania odporności na awarie, ogólnie i w kontekście systemu V3. Niejako komplementarne do zawartych w p. 9.3 są natomiast rozważania z punktu 9.4, gdzie wymieniamy potencjalne konsekwencje, jakie poszczególne rodzaje awarii mogą mieć dla opisywanego systemu. W obu tych punktach nie znajdziemy pełnego wykazu środków zapobiegania awariom zastosowanych w systemie V3. Część z nich została już omówiona w poprzednich rozdziałach, w innym kontekście. Nie dokonujemy też przeglądu wszelkich dostępnych metod, gdyż zagadnienie to jest zbyt obszerne, aby umieścić je w tej pracy. W ostatnim punkcie (9.5) dokonujemy przeglądu metod obsługi błędów użytych w systemie V3. Metody te łączą obsługę błędów z raportowaniem i obsługą awarii155.

9.2 Najważniejsze definicje i postulaty projektowe

9.2.1 Najczęstsze przyczyny niedostępności systemów

Niedostępność systemu (ang. outage lub downtime) może albo być wynikiem awarii, albo być zaplanowana (konieczność wymiany, unowocześnienia lub rozbudowania sprzętu, oprogramowania, archiwizacji danych itp.). Względna częstość różnych przyczyn niedostępności zależy od typu systemu, różne źródła podają różne dane. 

Na diagramie156 na rys. 9.1 przedstawione są ogólne statystyki przyczyn niedostępności systemów. 

Rys. 9.1 Przyczyny niedostępności systemów.

Zauważmy, że awarie sprzętowe stanowią jedynie około 10% przyczyn, podczas gdy najczęstszą przyczyną, odpowiedzialną za aż 40% awarii są różnego rodzaju błędy programowe (łącznie awarie: serwera, klienta i komunikacyjne157). Nie wystarczy więc zastosowanie drogich komponentów sprzętowych i macierzy RAID, sprawny system musi posiadać mechanizmy radzenia sobie z awariami programowymi. Zazwyczaj, w różnego rodzaju analizach i przy projektowaniu systemów odpornych na awarie, obserwuje się tendencję do koncentrowania się na awariach dysków i połączeń w sieci – temu wszystkiemu służą organizacje RAID, odtwarzanie połączeń, anulowanie i powtarzanie żądań itp. Tymczasem znacznie istotniejsze z punktu widzenia odporności systemu są takie usprawnienia jak izolacja błędów w poszczególnych modułach, zapisywanie stanów do dziennika i możliwość restartu poszczególnych modułów. 

Szacuje się158, że spośród wszystkich błędów sprzętowych około 90% stanowią błędy, które można naprawić (odtwarzając połączenia, ponawiając żądania itp.) w czasie rzeczywistym w sposób przezroczysty dla systemu, tzw. błędy przejściowe (ang. transient failures). W rzeczywistości zatem, jeśli zastosować odpowiednie algorytmy i (programowe) metody radzenia sobie z takimi błędami, wówczas okazuje się, że awarie sprzętowe wymagające poważniejszej ingerencji (wymiana podzespołów, rozpoczęcie migracji danych na nowy nośnik itp.) stanowią zaledwie 1/100 przypadków. 

Drugą pod względem częstości przyczyną niedostępności systemów są planowane wyłączenia. Wiele systemów nie umożliwia np. archiwizacji danych na bieżąco (ang. online backup), w trakcie działania systemu – np. w serwisach sieciowych lub systemach bankowych często można spotkać się z informacją, że „trwa przeładowywanie danych”. Z podanej statystyki jasno wynika, że dobrze zaprojektowany system powinien umożliwiać wykonywanie wszelkich czynności administracyjnych w trakcie normalnego działania, bez przerw w udostępnianiu usług. 

9.2.2 Cechy systemów odpornych na awarie

Z punktu widzenia projektowania systemów pod kątem odporności na awarie największe znaczenie mają następujące cechy:

Oczywiście liczą się również takie aspekty jak niezawodność (ang. reliability). Szczególnie z uwagi na to, że systemy omawiane w tej pracy, realizując wiele funkcji programowo, są bardziej narażone na błędy programisty. Aspekt niezawodności uwzględniamy tutaj wskazując na prostotę niektórych rozwiązań (np. w rozdziale 8).

9.2.2.1 Sprzętowa odporność na awarie

Odporność na awarie odnosi się przede wszystkim do sprzętu. Teoretycznie wykorzystanie sprzętu odpornego na awarie powinno zapewnić stuprocentową dostępność systemu. W praktyce okazuje się jednak, że najczęstsze są awarie programowe i nie da się zapewnić wysokiej dostępności systemu jedynie poprzez zastosowanie sprzętu odpornego na awarie. Z tego powodu, a także z uwagi na bardzo wysoki koszt tego rodzaju sprzętu, znacznie częściej spotyka się rozwiązania programowe. Wykorzystywanie sprzętu odpornego na awarie w pewnym stopniu stoi w sprzeczności z podstawową koncepcją tworzenia systemów klastrowych, zgodnie z którą należy wykorzystywać tani, powszechnie dostępny sprzęt (ang. commodity hardware), dzięki czemu za porównywalną ceną można wykorzystać go więcej (więcej węzłów w systemie, więcej dysków), a wydajność i odporność na awarie zapewnić za pomocą odpowiednich środków programowych. 

System V3 nie zawierał żadnych komponentów sprzętowych odpornych na awarie, temat ten nie mieści się również w problematyce niniejszej pracy, dlatego w dalszej jej części będziemy zajmować się wyłącznie programowymi metodami zapewniania wysokiej dostępności.

9.2.2.2 Wysoka dostępność

Wysoka dostępność, nazywana też czasem programową odpornością na awarie (ang. software fault-tolerance), obejmuje cały szereg różnego rodzaju środków, w tym między innymi następujące161:

Oczywiście wyżej wymienione środki nie wyczerpują całej gamy dostępnych rozwiązań. Autor zaprezentował tutaj jedynie najważniejsze i najczęściej stosowane. 

Warto tutaj nadmienić, że najlepsze systemy stosują wiele z wymienionych rozwiązań jednocześnie i posiadają jakiś algorytm165 zarządzający wykorzystaniem dostępnych środków, np. stosowaniem redundantnych organizacji danych (RAID1, RAID5), procesem replikacji danych itp.

9.3 Zapewnianie odporności na awarie

9.3.1 Sposoby zapewniania odporności na awarie

Zapewnianie odporności na awarie166 obejmuje zapobieganie awariom (minimalizacja prawdopodobieństwa ich wystąpienia lub szkodliwości ich skutków), wykrywanie faktu wystąpienia awarii oraz usuwanie skutków awarii

Sposoby zapobiegania awariom i minimalizacji ich skutków są oczywiście podzbiorem środków służących do zapewniania wysokiej dostępności (patrz p. 9.2.2.2), więc nie będziemy ich tutaj powtórnie wymieniać. Ograniczymy się tylko do omówienia izolacji modułów zapobiegającej rozprzestrzenianiu się awarii. Pozostałe metody spośród wcześniej wymienionych, zastosowane w systemie V3 zostały omówione w innych rozdziałach. 

Kwestii wykrywania awarii również nie będziemy tu bardziej szczegółowo omawiać, ponieważ w obecnej wersji systemu V3 zastosowane środki są nader skąpe: autor ograniczył się do komunikatów typu bicie serca wysyłanych między modułami w warstwie administracyjnej oraz okresowej kontroli spójności konfiguracji. 

W chwili obecnej w systemie V3 dostępne jest bardzo wąskie spektrum środków służących do usuwania skutków awarii: jedynie odtwarzanie połączeń w razie ich zerwania i możliwość restartu całych modułów. Druga z tych metod posłużyła jako postulat projektowy (autor projektował komponenty w taki sposób, aby możliwe było ich restartowanie w dowolnym momencie) a zarazem domyślna polityka obsługi błędów (patrz 9.5) w niektórych przypadkach, natomiast tym pierwszym autor niniejszej pracy nie zajmował się osobiście, dlatego nie będziemy tego opisywać, tym bardziej, że w chwili obecnej nie jest to wyrafinowany mechanizm.

9.3.2 Odizolowanie od siebie modułów systemu

Odizolowanie od siebie modułów systemu obejmuje następujące elementy:

Autor zastosował powyższe metody, aby wbudować izolację w system V3 za pomocą następujących kroków:

Oprócz tego w systemie zastosowano następujące środki:

Kwestia izolacji modułów miała także wpływ na architekturę systemu (rozdział 4) i decyzję o wyborze spośród dostępnych metod realizacji niektórych zadań (patrz np. p. 8.2.2).

9.4 Konsekwencje niektórych rodzajów awarii

W tym punkcie opisujemy konsekwencje wystąpienia niektórych rodzajów awarii, w tym również pośrednie konsekwencje awarii wynikające z zastosowania niektórych środków naprawczych, do których należą zerwanie połączenia i zrestartowanie węzła: dwie bardzo uniwersalne, choć brutalne i nie w każdym przypadku skuteczne,  metody radzenia sobie z poważniejszymi awariami (por. p. 9.3.2).

9.4.1 Zerwanie połączenia klient-serwer w warstwie operacyjnej

Zerwanie połączenia między klientem a serwerem w warstwie operacyjnej ma m. in. następujące konsekwencje, wymagające uwzględnienia w projekcie systemu:

Problem zerwanej sesji i blokowanych zasobów sygnalizowany był już w p. 5.3.1.2, zaś zagadnienia związane z utrzymaniem ciągłości sesji omówione są w p. 5.3.2.2. Autor nie znalazł dotąd sensownego rozwiązania tego problemu, które gwarantowałoby pełną automatyzację systemu. 

Zamiast tego, jako środek tymczasowy, autor przyjął w systemie V3 następującą zasadę:

Rozwiązanie to przenosi obowiązek podjęcia decyzji o usunięciu referencji do wolumenu (tj. skojarzenia klienta z wolumenem będącego rezultatem otwarcia wolumenu przez klienta, patrz punkt 8.2.2.2) na administratora, który może sprawdzić stan połączeń, stan systemu klienta, rozstrzygnąć, czy rozłączenia są wynikiem chwilowej awarii czy stanem permanentnym i, w tym drugim przypadku, zatwierdzić je, likwidując informacje o takich rozłączeniach i umożliwiając odłączenie wolumenu w celu wykonania czynności administracyjnych. Oczywiście można sobie wyobrazić bardziej złożone funkcje, umożliwiające zarządzanie pojedynczymi połączeniami klientów, precyzowanie minimalnego czasu, który musi trwać rozłączenie, aby można było usunąć odwołanie do wolumenu itd., jak również dodatkowe automatyczne reguły takie jak np. automatyczne usuwanie odwołań przy rozłączeniach, które trwają kilka godzin. 

Odnośnie drugiej z powyższych konsekwencji, w systemie V3 mamy kilka następujących podstawowych typów obiektów, które wymagają uporządkowania:

W niniejszej pracy nie będziemy omawiać szczegółowo tego zagadnienia. Problem porządkowania zasobów po awarii dotyczy nie tylko jednego, konkretnego typu awarii omawianego w tym punkcie, ale zdecydowanej większości, wiąże się także z modelem obsługi wyjątków zastosowanym w implementacji i metodą raportowania błędów168. System V3 jest na tyle złożony, że zagadnieniu porządkowania zasobów można byłoby poświęcić osobną pracę. Zagadnienie to jest zarazem bardzo silnie związane ze szczegółami implementacji i wymagałoby bardzo precyzyjnego omówienia szczegółów implementacji poszczególnych modułów, co nie jest celem autora.

9.4.2 Zerwanie połączenia procesu schowka z zarządcą serwera

Awaria polegająca na samym tylko zerwaniu połączenia między procesem schowka a zarządcą serwera jest mało prawdopodobna, gdyż jest to połączenie lokalne (między procesami na tym samym węźle). Zerwanie połączenia jest jednak jednym ze skutków innych, poważniejszych awarii, w szczególności np. awarii procesu schowka albo zawieszenia się procesu i niemożności reagowania na żądania zarządcy serwera lub odwrotnie, awarii zarządcy serwera lub np. planowego restartu zarządcy w celu usunięcia wewnętrznej awarii lub uaktualnienia wersji. 

Nie będziemy koncentrować się na omawianiu z osobna skutków wymienionych wyżej poszczególnych rodzajów awarii, lecz wskażemy podstawowe konsekwencje utraty lokalnego połączenia między warstwami:

Dzięki uniezależnieniu warstwy operacyjnej od administracyjnej udało się uniknąć kilku innych niekorzystnych konsekwencji. Doskonałym przykładem może być sposób obsługi błędów. W systemie V3 polityka obsługi błędów, choć ustalana przez warstwą administracyjną, jest przekazywana warstwie operacyjnej i uaktualniana w razie zmian. Warstwa operacyjna, znając politykę obsługi błędów, jest w stanie, w razie zerwania połączenia, obsługiwać błędy w zakresie umożliwiającym dalszą pracę. Autor początkowo rozważał raportowanie błędów do warstwy administracyjnej i podejmowanie decyzji w tej warstwie, jednak zrezygnował z tego rozwiązania ze względu na wyżej wymienione niekorzystne konsekwencje. 

Oprócz wyżej wymienionych dosyć niekorzystną konsekwencją zerwania połączenia jest także to, że po jego odtworzeniu obie warstwy muszą nawzajem poinformować się o zmianach. Warstwa administracyjna przekazuje nową konfigurację, np. nowe wolumeny. Aby móc to uczynić, musi albo śledzić zmiany od czasu zerwania połączenia, albo poprosić proces schowka o podanie jego aktualnej konfiguracji, tak, aby można było odnaleźć różnice i przesłać jedynie listę zmian do wykonania. Ale informacja jest przekazywana również w drugą stronę, np. jeżeli warstwa administracyjna rejestruje otwarte wolumeny, to musi odpytać proces schowka o listę aktualnie otwartych wolumenów. 

W systemie V3 autor również dokonuje takich uaktualnień. Jeżeli w trakcie, gdy połączenie było zerwane, administrator zdefiniuje nowe wolumeny, to po odtworzeniu połączenia informacje o nich zostaną automatycznie przesłane do procesu schowka, dzięki czemu staną się one dostępne dla klientów. Zarazem w trakcie, gdy nie ma połączenia, zabronione jest np. usunięcie wolumenu.

9.5 Modele obsługi błędów

9.5.1 Obsługa błędów w procesie schowka

Obsługa błędów w procesie schowka jest zrealizowana w szczególny sposób z kilku powodów:

Spośród powyższych powodów najistotniejszym problemem jest potokowa organizacja procesu, która powoduje, że nie ma w procesie kilku ustalonych punktów, w których skupione jest sterowanie (tzn. z których inicjowane są wywołania funkcji pozostałych modułów i gdzie zawsze system ostatecznie wraca po wykonaniu wywołanych funkcji) i gdzie można byłoby w ostateczności umieścić obsługę sytuacji awaryjnych. Fakt ten powoduje, że nie ma większego sensu stosowanie wyjątków, podczas gdy obsługa błędów za pomocą bardzo licznie zgłaszanych i wyłapywanych wyjątków jest podstawą obsługi błędów w zarządcy serwera (p. 9.5.2). 

Autor przyjął następujący model:

Dla każdego błędu w tym modelu mamy więc dostępne dwa rodzaje reakcji: reakcję natychmiastową, która daje możliwość kontynuacji pracy systemu, i reakcję opóźnioną, realizowaną już przez inny moduł, gdzie powinna się znaleźć kompleksowa obsługa błędu (o ile nie udało się zawrzeć jej w całości w reakcji natychmiastowej). 

Przykładem działania tego modelu może być błąd odczytu z dysku. Funkcja raportująca powinna zalecić jako akcję natychmiastową zasygnalizowanie błędu realizacji żądania. Żądanie zostanie przekazane dalej, np. bezpośrednio do wątku wysyłającego w celu odesłania klientowi komunikatu z informacją o błędzie. Oprócz tego jednak zostanie zaplanowane działanie asynchroniczne, polegające np. na przeniesieniu danych wolumenu na inny dysk (jeśli jest jakiś dostępny w wolnej puli zasobów) i zasygnalizowaniu administratorowi systemu konieczności wymiany uszkodzonego podzespołu. 

Dosyć istotnym elementem tego modelu jest możliwość określania polityki obsługi błędów. Użytkownik powinien mieć możliwość wyboru spośród dostępnych działań dla każdego rodzaju awarii, system również musi sam być w stanie dokonywać niektórych wyborów. Przede wszystkim zaś polityką obsługi błędów musi zarządzać proces schowka. W systemie V3 jednak w chwili obecnej polityka obsługi błędów jest ustalona, bez możliwości administracji, ponieważ w systemie nie istnieją bardziej złożone mechanizmy obsługi awarii, takie jak. migracja danych, możliwość stosowania wolumenów typi RAID1 itp.

9.5.2 Obsługa błędów w zarządcy serwera

Zarządca serwera, jak już wcześniej (p. 9.5.1) wspomniano, wykorzystuje wyjątki C++. Przetwarzanie jest tu skupione w kilku punktach (moduł odbierający żądania klientów i kilka modułów posiadających własne wątki i reagujących na zdarzenia), w systemie nie ma obiegu żądań, lecz wzajemne wywołania funkcji przez moduły. W zastosowanym tutaj modelu obsługi błędów są jednak pewne podobieństwa do modelu wbudowanego w proces schowka. 

Przyjęty tutaj model opiera się na następujących zasadach:

W systemie V3 autor zrezygnował z możliwości dynamicznego zarządzania działaniami natychmiastowymi w zarządcy serwera, natychmiastowa obsługa jest ustalona, wbudowana bezpośrednio w kod. Powodem tego jest fakt, iż w większości sytuacji nie ma żadnej dowolności – czynność, którą należy wykonać, jest jednoznacznie określona.

9.6 Podsumowanie

W rozdziale tym wprowadziliśmy podstawowe pojęcia związane z obsługą awarii, takie jak dostępność, izolacja itp., dokonaliśmy krótkiego przeglądu metod zapewniania wysokiej dostępności, podaliśmy garść odniesień do poprzednich rozdziałów (gdzie zastosowania niektórych z tych metod zostały bardziej szczegółowo opisane), opisaliśmy nieco bardziej szczegółowo kwestię izolacji. Podaliśmy ważniejsze konsekwencje zerwania połączeń między klientem i serwerem oraz lokalnie między warstwami. Autor brał te konsekwencje pod uwagę podejmując decyzje projektowe. Na końcu przedstawiono dwa modele obsługi błędów, zastosowane w zarządcy serwera i w procesie schowka. Oba modele istotnie różnią się od siebie, co wynika z odmiennej organizacji działania obu elementów systemu: jeden z nich zorganizowany jest zdarzeniowo, a drugi potokowo. Oba modele mają też jednak cechy wspólne. 

System V3 w obecnej fazie rozwoju nie może być uznany za odporny na awarie i wysokodostępny. Wiele z wysuwanych przez autora w tej pracy postulatów nie zostało jeszcze zrealizowanych. Najbardziej dokuczliwym wydaje się brak możliwości tworzenia wolumenów RAID1 i brak możliwości archiwizacji danych. Właśnie te dwie funkcje autor uważa w obecnej fazie rozwoju systemu za najbardziej priorytetowe.


155 Przy czym przez obsługę błędów rozumiemy tu obsługę wyjątków w programie oraz działania podejmowane przez program, gdy wywołania funkcji systemowych zakończą się niepowodzeniem; obsługa błędów oznacza więc przyjęty model programistyczny (który może polegać np. na przekazywaniu sterowania przez wyjątki, generowaniu zdarzeń i przekazywaniu ich między modułami, natychmiastowej obsłudze błędów itd.). Obsługą awarii natomiast nazywamy tutaj strategię bardziej wysokopoziomową: działania podejmowane wspólnie przez różne moduły systemu w celu usunięcia awarii.
156 Źródło: IEEE Computer, kwiecień 1995, powyższy diagram znajduje się również w [49].
157 W pracy źródłowej, z której pochodzą te dane, awarie komunikacyjne zaliczono do awarii programowych. Chodzi tu więc o awarie komunikacyjne polegające na przypadkowym zerwaniu połączenia, zagubieniu pakietu itd., czyli awarie łatwo usuwalne programowo (przez ponowienie żądania, odtworzenie połączenia itp.) – stąd przyjęta klasyfikacja i nazewnictwo. Awarie komunikacyjne polegające na fizycznym uszkodzeniu połączenia lub awarii sprzętu (kart sieciowych, kontrolerów przełączających) zaliczone zostały do grupy awarii sprzętowych, a nie komunikacyjnych (mimo, iż dotyczą sprzętu służącego do komunikacji).
158 Stwierdzenie takie znajduje się w [9], w rozdziale dotyczącym wysokiej dostępności.
159 Bardziej precyzyjna definicja obu pojęć i różnice między nimi bardzo dobrze opisane są w [52].
160 Definicja została zaczerpnięta z [49], tam również znajduje się najpełniejszy opis tego pojęcia.
161 Autor opiera się tutaj na własnych doświadczeniach, ale także na informacjach zawartych w [49] i [33], gdzie znajduje się bardzo pełne i precyzyjne omówienie problemu wysokiej dostępności.
162 Odpowiedź na żądanie musi nadejść po tym samym połączeniu, po którym wysłano żądanie.
163 Zapis w modelu RDMA i wystaw-odbierz został omówiony w punkcie 4.4.
164 W dyskusjach biznesowych unika się wchodzenia w szczegóły, rozważania prawdopodobieństw, wzajemnych zależności między awariami, istnieje raczej tendencja do wyrażania właściwości systemu w prostych, łatwych do zrozumienia przez laika kategoriach, takich jak np. brak pojedynczego punktu awarii. Konsekwencją tego jest, iż większość systemów bywa obecnie projektowana pod kątem zapewnienia takich „klarownych” własności, zaś mało „efektowne” usprawnienia bywają lekceważone. Opinia ta pochodzi od współuczestników projektu V3.
165 W pracy [30] w dość formalny i teoretyczny sposób opisano algorytm zarządzania procesem replikacji w dużych systemach składowania danych.
166 Różne zagadnienia związane z wysoką dostępnością, odtwarzaniem danych i stabilnego stanu systemu po awarii w systemach rozproszonych, bazach danych itp. znajduje się m. in. w pracach [43], [48], [4] i [10].
167 Opis wykorzystywanej w implementacji biblioteki komunikacyjnej ACL, jak również informacje na temat standardu VIA, znaleźć można w [14].
168 Uwagi na temat modelu raportowania błędów w procesie schowka znajdują się w p. 9.5.1.
169 Patrz także p. 4.4.3 oraz 4.4.5, gdzie zobaczymy budowę wewnętrzną procesu schowka i ścieżkę, jaką przebywają prostsze żądania (tzn. te do lokalnie zdefiniowanych wolumenów sekwencyjnych).
170 Przez zasłonięcie wyjątku rozumiemy opisywany tu właśnie proces polegający na wyłapaniu wyjątku bardziej szczegółowego i zastąpieniu go wyjątkiem bardziej ogólnym.