Dlaczego większość architektur w chmurze psuje się nie przez skalę, tylko przez brak prostoty?
W chmurze bardzo łatwo zbudować architekturę, która wygląda dojrzale.
Na diagramie wszystko się zgadza:
osobne środowiska, sensowny runtime, kilka warstw bezpieczeństwa, monitoring, autoscalowanie, kolejki, osobne pipeline’y, może jeszcze tracing, trochę event-driven flow i kilka decyzji podjętych „na przyszłość”, żeby później nie wracać do tematu.
Wydaje się, że to wszystko może mieć sens.
Problem w tym, że architektura potrafi wyglądać dojrzale dużo wcześniej, niż dojrzewa realny problem, który ma rozwiązywać.
I właśnie tutaj zaczyna się jedna z najczęstszych pułapek projektowania systemów w chmurze.
Większość architektur nie przegrywa dlatego, że nagle nie wytrzymała skali.
Dużo częściej przegrywa wcześniej – wtedy, gdy staje się zbyt złożona jak na swój etap.
Nie dzieje się to spektakularnie.
Zwykle nie ma jednego momentu, w którym wszystko się rozpada.
System po prostu zaczyna stawiać opór.
Trudniej coś zmienić.
Trudniej coś bezpiecznie wdrożyć.
Trudniej wytłumaczyć, co właściwie się dzieje.
Trudniej odróżnić problem aplikacji od problemu warstwy, która została dołożona po drodze.
I to właśnie dlatego prostota nie jest estetyką.
Jest decyzją architektoniczną.
Problemy zwykle nie zaczyna się od złych decyzji
Najczęściej zaczyna się od dobrych intencji.
Zespół chce zrobić wszystko porządnie.
Nie chce budować prowizorki.
Nie chce wracać za chwilę do przepisywania.
Nie chce postawić czegoś, co rozsypie się przy pierwszej zmianie.
To jest całkowicie rozsądne.
Tyle że w praktyce „zrobić porządnie” zaczyna znaczyć:
- dodać więcej warstw,
- rozdzielić więcej odpowiedzialności,
- przewidzieć więcej scenariuszy,
- zbudować więcej elastyczności, niż realnie potrzeba na starcie.
A w ekosystemie chmurowym wszystko jest ułatwione.
AWS daje ogromny wybór usług i wzorców. Sam Well-Architected Framework nie jest katalogiem „co wdrożyć”, tylko sposobem myślenia o kompromisach. AWS podkreśla też w materiałach o odporności systemów, że zwiększanie rezyliencji zwykle oznacza wyższy koszt, większą złożoność projektu i większy wysiłek operacyjny. Konkretnie mówiąc: dodatkowe warstwy nie są darmowe tylko dlatego, że są technicznie możliwe.
To jest kluczowe rozróżnienie.
Bo czym innym jest system gotowy rosnąć, a czym innym system, który od początku nosi ciężar wzrostu, którego jeszcze nie ma.
Skala zwykle przychodzi później. Złożoność boli od razu
W technicznych rozmowach jednym z pierwszych zadawanych pytań to coś w stylu:
Czy to się będzie skalować?
Pytanie to ma sens
Lecz bardzo często zadawane za wcześnie.
A ja dużo częściej wolę zadawać:
Czy ten system da się spokojnie zrozumieć, zmienić i naprawić za pół roku?
Bo ogromna liczba systemów nie dochodzi nawet do momentu, w którym ich realną przeszkodą jest wielka skala.
Dużo wcześniej zaczynają przegrywać z tarciem.
Tarciem, które wcale nie wygląda jakoś widowiskowo.
Nie przypomina awarii z konferencyjnego case study.
Jest dużo bardziej przyziemne, przy tym groźniejsze.
A objawia się tym, że prosta zmiana wymaga dotknięcia zbyt wielu miejsc:
- rollout jest formalnie zautomatyzowany, ale i tak każdy robi go ostrożnie,
- incydent nie jest diagnozą, tylko dochodzeniem,
- observability istnieje, ale coraz częściej kompensuje fakt, że sam system nie jest już intuicyjny,
- granice odpowiedzialności są poprawne na diagramie, ale nieczytelne w codziennej pracy.
Google ujmuje to bardzo trzeźwo w materiałach SRE: prostota nie jest dodatkiem do ładnej architektury, tylko czymś, co silnie powiązane z niezawodnością. Prostsze systemy są łatwiejsze do zrozumienia, testowania, utrzymania i naprawy, kiedy już coś pójdzie źle.
I właśnie dlatego złożoność tak często boli wcześniej niż skala.
Bo skala jest problemem przyszłości.
Tarcie jest problemem codziennym.
Najdroższa złożoność to ta, która wygląda rozsądnie
Najgorsze decyzje architektoniczne rzadko wyglądają źle w dniu, w którym zostały podjęte.
Zwykle brzmią sensownie.
„Zróbmy to od razu porządnie.”
„Lepiej przygotować większą separację.”
„Tracing się przyda, więc dołóżmy go od początku.”
„Ten komponent może kiedyś urosnąć, więc rozdzielmy go już teraz.”
„Warto przygotować model uprawnień bardziej przyszłościowo.”
„Nie chcemy przepisywać systemu za pół roku.”
Wszystko z tego może być prawdziwe.
Problem pojawia się wtedy, gdy ich suma tworzy system bardziej skomplikowany niż problem, który ma rozwiązywać.
To jest moment, w którym architektura przestaje odpowiadać na aktualne napięcia, a zaczyna odpowiadać na kilka hipotetycznych wersji przyszłości naraz.
A przyszłość ma jedną irytującą cechę:
bardzo rzadko przychodzi dokładnie w tej formie, w której została zaprojektowana.
W efekcie zostajemy z systemem, który już teraz kosztuje więcej:
- poznawczo,
- operacyjnie,
- organizacyjnie,
bo został zaprojektowany dla jutra, które może nigdy nie nadejść w tej wersji.
A tutaj nie trzeba od razu budować wszystkiego, co „ma sens”
To jest ten punkt, który w praktyce bywa najbardziej uwierający.
Bo nowoczesna architektura zwłaszcza chmurowa potrafi kusić dostępnością szerokiej gamy narzędzi.
Jeśli chcesz zbudować projekt, który ma:
- stabilnie przyjmować ruch,
- obsługiwać kilka przewidywalnych flow,
- korzystać z bazy, kolejki i kilku integracji,
- mieć sensowny deployment i podstawową odporność,
to naprawdę nie zawsze trzeba od razu robić:
- najbardziej rozbudowany model orkiestracji,
- kilka dodatkowych warstw komunikacji między usługami,
- pełny rozproszony tracing wszędzie „dla zasady”,
- model uprawnień przygotowany na każdy możliwy wariant organizacyjny,
- osobne mechanizmy dla scenariuszy, które jeszcze nie istnieją,
- abstrakcje nad abstrakcjami tylko po to, żeby system wyglądał bardziej enterprise.
Czasami wystarczy po prostu:
- czytelny runtime,
- sensowny deployment,
- jasno ustawione granice odpowiedzialności,
- monitoring odpowiadający na realne pytania,
- model bezpieczeństwa dopasowany do obecnego poziomu ryzyka.
To nie jest architektura uboższa.
To jest architektura bardziej adekwatna.
A adekwatność jest bardzo często cenniejsza niż imponujący diagram.
Złożoność prawie zawsze zapuka bocznymi drzwiami
To, co najbardziej zdradliwe, to fakt, że system rzadko komplikuje się dokładnie tam, gdzie spodziewaliśmy się problemu.
Dla przykładu, wybieramy klaster EKS, bo wydaje się rozsądnym standardem dla rozproszonych usług.
Decyzja ta sama w sobie może wydawać się OK.
Ale chwilę później okazuje się, że najtrudniejsza nie jest aplikacja.
Najtrudniejsze stają się rzeczy wokół niej:
- model uprawnień do zasobów,
- relacja między workloadem a odpowiedzialnością platformy,
- dostęp między kontami,
- przepływ sekretów,
- polityki sieciowe,
- rollback, który na papierze jest prosty, a w praktyce dotyka kilku warstw zależności.
I nagle zespół nie rozwiązuje już głównie problemu produktu.
Zaczyna rozwiązywać problem architektury, którą sam wcześniej uznał za „rozsądnie przyszłościową”.
Podobnie bywa z wdrażaniem observability dla projektu.
W pewnym momencie można dojść do architektury, w której tracing, logi, dashboardy i alerty nie służą już przede wszystkim zrozumieniu systemu. Zaczynają kompensować fakt, że system zrobił się zbyt rozproszony, żeby dało się go ogarnąć prostszym modelem myślowym.
To nie jest argument przeciwko Kubernetesowi.
Ani też jest przeciwko tracingowi.
Tym bardziej nie przeciwko bardziej zaawansowanym wzorcom.
To jest argument za proporcją.
Publiczny przykład: więcej warstw nie zawsze daje więcej realnej odporności
Dobrym przypomnieniem tego mechanizmu jest publicznie opisany incydent Cloudflare z 2020 roku. Firma miała redundancję na wielu poziomach, ale częściowo uszkodzony przełącznik doprowadził do nietypowego łańcucha problemów. Nie chodziło o prosty brak failoveru. Problem polegał na tym, że element infrastruktury znalazł się w stanie pośrednim: część mechanizmów działała, część nie, a całość zachowywała się inaczej niż klasyczny model awarii, pod który zwykle projektuje się zabezpieczenia. Cloudflare opisało to jako realny przykład byzantine failure.
To nie jest gadanie pod tytułem: „nie buduj odporności”.
To jest dużo ciekawsza lekcja:
im bardziej rozbudowany system, tym więcej stanów pośrednich, zależności i nietypowych zachowań trzeba umieć rozumieć operacyjnie.
AWS mówi o tym wprost w prescriptive guidance: zwiększanie rezyliencji to gra kompromisów. Poprawa jednego aspektu odporności może oznaczać większą złożoność całego systemu, wyższy koszt operacyjny i malejący zwrot z kolejnych inwestycji.
Nie każda dodatkowa warstwa, która poprawia jeden parametr, poprawia cały system jako całość.
Czasem poprawia tylko wybrany aspekt.
Cała reszta płaci za to większym kosztem zrozumienia.
Dlatego prostota nie jest naiwna. Jest zdyscyplinowana
To ważne, bo prostota bywa mylona z myśleniem „po taniości”.
A to nie o to chodzi, bo prostota nie oznacza:
- robienia rzeczy byle jak,
- ignorowania bezpieczeństwa,
- udawania, że przyszłość nie istnieje,
- odkładania trudnych decyzji bez refleksji.
Prostota oznacza coś trudniejszego:
świadome odrzucanie tych decyzji, które są technicznie możliwe, ale jeszcze nieuzasadnione.
To nie jest mniej ambitne podejście.
To jest bardziej zdyscyplinowane podejście.
Bo dużo łatwiej jest coś dodać, niż czegoś świadomie nie dodać.
Dużo łatwiej jest rozbudować diagram, niż obronić architekturę, która pozostaje prosta mimo dostępnych możliwości.
Co warto chronić przed nadmiarem złożoności.
Najpierw te obszary, które najbardziej obniżają tarcie.
- Granice odpowiedzialności
Jeśli nie wiadomo, co należy do platformy, co do aplikacji, co jest wspólne, a co dedykowane, to żadna technologia nie wprowadzi prawdziwego porządku.
- Główne flow
Jeśli najważniejszego scenariusza systemu nie da się opowiedzieć prosto, to znak, że złożoność zaczyna wygrywać.
- Wyjątki
Pojedynczy wyjątek może być uzasadniony.
System zbudowany z samych uzasadnionych wyjątków zwykle przestaje być spójny.
- Warstwy operacyjne
Każda kolejna warstwa powinna odpowiadać nie na pytanie „czy to jest dobre narzędzie?”, tylko na pytanie:
czy naprawdę potrzebujemy dziś kosztu jego wdrożenia, utrzymania i zrozumienia?
To pytanie brzmi mało efektownie.
Za to bardzo dobrze filtruje architektoniczne nadmiary.
Podsumowanie
Większość architektur w chmurze nie psuje się dlatego, że skala przyszła za szybko.
Psuje się wtedy, gdy liczba warstw, wyjątków i decyzji zaczyna rosnąć szybciej niż realna potrzeba.
I wtedy problemem nie jest już wydajność ani koszt.
Problemem staje się to, że system przestaje być:
- czytelny,
- przewidywalny,
- spokojny w utrzymaniu.
A kiedy system przestaje być spokojny, zespół też przestaje taki być.
Zakończenie
Dobra architektura nie ma imponować.
Ma pozwalać systemowi oddychać.
Ma zostawiać miejsce na zmianę bez dokładania niepotrzebnego ciężaru.
Ma być odporna nie tylko technicznie, ale też poznawczo.
Ma pomagać poruszać się w chaosie technologii bez dokładania kolejnego chaosu od siebie.
Chmura daje ogromne możliwości.
AWS daje ogromne możliwości.
Nowoczesne narzędzia dają ogromne możliwości.
Ale rozwaga architektoniczna nie polega na tym, żeby użyć ich jak najwięcej.
Polega na tym, żeby wiedzieć, czego jeszcze nie trzeba.