Oprogramowanie krytyczne to kategoria definiowana nie przez złożoność, ale przez konsekwencje awarii. Gdy zawodzi oprogramowanie korporacyjne, użytkownicy widzą komunikat o błędzie i czekają na naprawę. Gdy zawodzi oprogramowanie krytyczne — bojowy system dowodzenia i kierowania, aplikacja kontroli ruchu lotniczego, sterownik urządzenia medycznego — konsekwencje mogą obejmować utratę świadomości sytuacyjnej, podejmowanie błędnych decyzji w oparciu o nieaktualne dane lub bezpośrednią szkodę fizyczną. Architektura zapobiegająca tym awariom jest zasadniczo inna od tego, co wystarcza w konwencjonalnym oprogramowaniu.

Niniejszy artykuł omawia wzorce architektoniczne i podejścia inżynieryjne stosowane w domenach obronnych i innych wysokiego ryzyka w celu osiągnięcia niezawodności, dostępności i odporności na błędy wymaganej przez systemy krytyczne.

Czym oprogramowanie krytyczne różni się od korporacyjnego

Różnica nie dotyczy przede wszystkim złożoności funkcji ani wolumenu danych. Oprogramowanie krytyczne różni się od korporacyjnego wzdłuż trzech osi kształtujących decyzje architektoniczne.

Konsekwencje awarii. Oprogramowanie korporacyjne zazwyczaj zawodzi w sposób odwracalny. Oprogramowanie krytyczne może zawodzić w sposób nieodwracalny — system fuzji sensorowej, który traci śledzenie w krytycznej fazie, nie może odtworzyć utraconych danych. Ta asymetria konsekwencji oznacza, że zapobieganie awarii jest warte znacznie większych inwestycji inżynieryjnych niż odzyskiwanie po niej.

Środowisko operacyjne. Oprogramowanie obronne często działa w zdegradowanych warunkach: systemy montowane na pojazdach na trudnym terenie, rozlokowany sprzęt w ekstremalnych temperaturach, łączność satelitarna z wysokimi opóźnieniami i ograniczoną przepustowością. Architektura musi uwzględniać warunki środowiskowe, których systemy korporacyjne nigdy nie napotykają.

Ograniczenia czasu rzeczywistego. Wiele systemów krytycznych ma twarde wymagania czasu rzeczywistego. Oprogramowanie krytyczne z wymaganiami czasu rzeczywistego musi dotrzymywać terminów deterministycznie, a nie statystycznie.

Podstawowe wzorce architektoniczne

Kilka wzorców pojawia się konsekwentnie w architekturach systemów krytycznych. Nie wykluczają się wzajemnie; dojrzałe systemy zazwyczaj łączą wiele wzorców w celu osiągnięcia wymaganego profilu niezawodności.

Redundancja aktywno-aktywna. W konfiguracji aktywno-aktywnej wiele instancji serwisu działa jednocześnie, wszystkie przetwarzając żądania i utrzymując zsynchronizowany stan. Jeśli jedna instancja zawiedzie, pozostałe kontynuują pracę bez przerwy. Aktywno-aktywna to konfiguracja o najwyższej dostępności, ale niesie ze sobą najwyższy koszt złożoności: synchronizacja stanu między instancjami jest technicznie trudna, szczególnie w warunkach partycji sieciowej.

Redundancja aktywno-pasywna. W konfiguracji aktywno-pasywnej instancja podstawowa obsługuje cały ruch, podczas gdy instancja zapasowa pozostaje w stanie gotowości. Gdy podstawowa zawiedzie, zapasowa przejmuje jej rolę — proces, który zajmuje pewien mierzalny czas i może wiązać się z krótką przerwą w obsłudze. Aktywno-pasywna jest prostsza w implementacji i często stanowi pragmatyczny wybór dla systemów, w których krótki czas przełączania awaryjnego jest akceptowalny.

Wzorzec wyłącznika (Circuit Breaker). Zapożyczony z elektrotechniki, wzorzec wyłącznika rozwiązuje specyficzny tryb awarii: kaskadowe błędy spowodowane przez komponent próbujący komunikować się z niedostępną zależnością. Wyłącznik monitoruje wywołania do zależności; gdy błędy przekraczają próg, „otwiera się" i natychmiast zwraca błąd lub buforowaną odpowiedź zastępczą zamiast próbować wywołania.

Wzorzec izolacji (Bulkhead). Nazwany na cześć wodoszczelnych grodzi w kadłubach okrętów, wzorzec izolacji oddziela komponenty od siebie, tak aby awaria jednego nie wyczerpywała zasobów potrzebnych innym. W praktyce oznacza to zazwyczaj przydzielanie oddzielnych pul wątków lub połączeń różnym podsystemom.

Zasada architektoniczna: Celem odporności na błędy nie jest zapobieganie wszystkim awariom — co jest niemożliwe w rzeczywistych warunkach operacyjnych. Celem jest zapewnienie, że awarie pozostają lokalne, a nie się propagują; degradacja jest łagodna, a nie katastrofalna; odzyskiwanie jest automatyczne lub kierowane, a nie wymagające ręcznej interwencji pod presją.

Łagodna degradacja podczas przerwy sieciowej

Systemy obronne często działają w środowiskach, gdzie łączność z systemami centralnymi jest przerywana lub nieobecna. System zaprojektowany tylko do pracy w trybie połączonym całkowicie zawiedzie po utracie łączności. Systemy krytyczne muszą mieć jawne możliwości pracy w trybie zdegradowanym — system musi mieć zdefiniowane, przetestowane zachowanie dla każdego możliwego stanu łączności.

Projektowanie łagodnej degradacji zaczyna się od inwentaryzacji możliwości: które wymagają łączności, które mogą działać z danymi buforowanymi o akceptowalnej aktualności, a które mogą działać w pełni offline. Synchronizacja stanu po ponownym połączeniu jest jednym z najtrudniejszych problemów w pracy rozłączonej. Polityki rozwiązywania konfliktów muszą być zdefiniowane jawnie na etapie projektowania, a nie obsługiwane za pomocą doraźnej logiki podczas implementacji.

Testowanie: chaos engineering, wstrzykiwanie błędów i testy stresowe

Architektura odporności, która nie została zwalidowana w warunkach awarii, jest hipotezą, a nie faktem inżynieryjnym. Systemy krytyczne wymagają rygorystycznego testowania trybów awarii.

Testowanie wstrzykiwania błędów celowo wprowadza awarie do działającego systemu, aby zweryfikować, że obsługa błędów zachowuje się zgodnie ze specyfikacją. Obejmuje to wprowadzanie opóźnień sieciowych i utraty pakietów, powodowanie awarii procesów oraz symulowanie awarii sprzętu.

Chaos engineering rozszerza wstrzykiwanie błędów do środowisk podobnych do produkcyjnych, celowo wprowadzając losowe awarie w celu ujawnienia słabości, które deterministyczne testowanie może przeoczyć. W kontekście obronnym chaos engineering musi być przeprowadzany w reprezentatywnych środowiskach testowych.

Testy stresowe oceniają zachowanie systemu, gdy zbliżają się lub przekraczają limity zasobów. Systemy krytyczne muszą mieć zdefiniowane zachowanie w warunkach obciążenia wykraczającego poza normalne parametry operacyjne — jawne ograniczanie obciążenia lub łagodna awaria z odpowiednim alertingiem, a nie niezdefiniowane zachowanie.