Recenzja Release It!

Release It! Design and Deploy Production-Ready Software autorstwa Micheala T. Nygarda do nowości wydawniczych nie należy. Właściwie już za trochę ponad 2 tygodnie minie równo 10 lat od wydania.

Tematyka

Książka traktuje o przygotowaniu oprogramowania do życia na produkcji. Autor otwartym tekstem pisze, że mimo przejścia wszystkich testów i przepchnięcia kodu przez działy QA pełnych testerów zdeterminowanych do tego, by wynikową aplikację popsuć, to i tak produkt czeka klapa. O ile nie zostanie przygotowany do wejścia i działania w środowisku produkcyjnym.

If you fail to design your system for a production environment, your life after release will be filled with “excitement.”

Pozycja jest zorganizowana w cztery części:

  • Stability
  • Capacity
  • General Design Issues
  • Operations

Pierwsza traktuje (jak nietrudno się domyślić) o zapewnianiu bezawaryjnego działania i ograniczaniu skutek potencjalnych incydentów. Wylicza zagrożenia czyhające na działający system, możliwe punkty zapalne i przedstawia sposoby radzenia sobie z nimi. Pokazuje jakie zagrożenia niosą za sobą między innymi wszystkie punkty integracji, użytkownicy, brak stosowania timeout’ów przy korzystaniu z komunikacji czy brak LIMITowania wyników zapytań.

Druga część uczy o badaniu wydajności, przepustowości i przepływu danych przez aplikacje. Problemy omawiane w tej części dotyczą głównie skalowalności, a zakres tematyczny rozciąga się od ryzyka związanego z obsługą puli dowolnych współdzielonych zasobów, przez „zarastanie” baz danych informacjami, które dawno nie są potrzebne aż do tuningu Garbage Collector’a.  (Dygresja – to ostatnie potrafi zaoszczędzić grube pieniądze – historia prosto z Instagrama o wyłączeniu Garbage Collectora w Pythonie).

Trzecia część książki to wiadomości uzupełniające o awaryjności sieci, rozważaniach dotyczących bezpieczeństwa (na przykład zasada minimalnych uprawnień), dostępności usługi i administrowania nią.

Wreszcie ostatnia, czwarta część mówi o procesach dostarczania oprogramowania, logach, audycie i monitoringu oraz podkreśla konieczność ciągłej adaptacji.

Każda z części jest otwierana rozbudowanym studium przypadku, gdzie szczegółowej analizie poddawana jest jakaś katastrofa, powiązana tematycznie z sekcją książki, którą otwiera.

Ocena

To chyba pierwsza książka techniczna, podczas czytania której śmiałem się na głos. Autor często wplata żarty i boleśnie prawdziwe uwagi pozwalając czytelnikowi się rozluźnić i uśmiechnąć. Oczywiście może to być związane z tym, że tematyka dotyczy w dużej mierze mojej pracy, ale czytając

How many times have you seen this one: some reporting system generates a report and sends it to a distribution list, and half the people on the list have a rule in Microsoft Outlook that automatically deletes the report? It’s classic.

nie mogłem się powstrzymać od śmiechu, bo sam mam podobne reguły w konfiguracji poczty.

Co do treści nie mam zastrzeżeń, poza tym, że miejscami jest już nieaktualna. Szczegóły w kolejnym akapicie. Książka jest niezwykle rozbudowanym kompendium wszelakiej wiedzy o oprogramowaniu, a do tego tworzy spójną całość.

Aktualność

Ze względu na wiek pozycji można się obawiać, że nie warto już do niej zaglądać w 2017. roku. Nie jest to prawda – większość treści nie zdezaktualizowała się. Jeżeli autor przywołuje przykłady, to najczęściej dotyczą systemów napisanych w Javie, która ciągle ma bardzo mocną pozycję na rynku.

Kolejną cechą stanowiącą o aktualności pozycji stanowi koncentracja autora na zasadach i wzorcach, które nie są specyficzne dla żadnego konkretnego stacku technologicznego. Nawet jeżeli konkretny przykład jest oparty na Javie, to takie same problemy dotyczą też systemów napisanych w innych językach.

Próby czasu nie wytrzymał z pewnością rozdział o AJAX. Autor pisze, że poleganie na asynchronicznych żądaniach z frontendu to błąd projektowy i stanowi formę ataku Denial Of Service. Złośliwie trendy rozwijania oprogramowania poszły jednak w tę stronę i w 2017. roku mamy pełno aplikacji w architekturze SPA. 🙂

Nostalgiczny uśmiech na twarzy wywołuje też wspomnienie rozdział o oszczędzaniu białych znaków w HTML. Na szczęście nie trzeba się tym już martwić.

Dla kogo?

Z lektury najlepiej skorzystają adepci architektury systemów informatycznych, osoby utrzymujące takowe systemy oraz zaawansowani programiści. Dodatkowo każdy zainteresowany sposobami, w jaki aplikacja może się popsuć znajdzie tu istną kopalnię potencjalnych pułapek i recepty na ustrzeżenie się przed nimi.

Podsumowanie

Z czystym sercem polecam lekturę. Przepełniona żartami, dobrze się czyta. Dość zaawansowany angielski może trochę utrudniać brnięcie przez kolejne rozdziały, ale przynajmniej można się poduczyć przy okazji. 😉

Utrzymywalne oprogramowanie – część 1

Jest to pierwszy z serii wpisów o utrzymywalnym oprogramowaniu. Ta część jest poświęcona wyjaśnieniu, co decyduje o stopniu trudności utrzymania software’u oraz umotywowaniu znaczenia tej cechy.

Definicja

Wypada zacząć od określenia, czym jest samo utrzymywanie oprogramowania (ang. software maintenance). W prostych słowach są to wszystkie działania podejmowane w celu zapewnienia ciągłości działania usług. Pożądanymi rezultatami są między innymi poprawienie wydajności, eliminowanie błędów (na przykład zgłoszonych przez użytkowników), a także przystosowanie do uruchomienia w nowym środowisku.

Obrazek ilustrujący ten post przedstawia rzeźbę Atlasa – mitycznego tytana, który utrzymuje na swoich barkach całe niebo. Ten potworny ciężar miał być karą za udział w buncie przeciwko Zeusowi – a przynajmniej tak to sobie wyobrażali starożytni Grecy.

Utrzymywanie oprogramowania też jest ciężarem, tyle że dźwiganym przez ludzi z krwi i kości. Zaczyna się, gdy wersja 1.0 ląduje na produkcji. Trwa przez cały czas, gdy oprogramowanie jest używane i zarabia pieniądze. Kończy się dopiero po całkowitym zamknięciu projektu. Jest więc procesem będącym integralną częścią życia softu. Z tego powodu trochę zaskakującym jest fakt, że trudno znaleźć na ten temat dobre opracowanie. Utrzymanie jest najchętniej traktowane jako zło konieczne, zauważane dopiero gdy produkt przestaje działać albo jego koszt stale rośnie, pożerając kolejne zasoby. Jest to tym bardziej niezrozumiałe, że utrzymanie stanowi świetne źródło informacji zwrotnej o projekcie w warunkach polowych. Chyba nic nie powie więcej o niezawodności i naturze oprogramowania niż obserwacje sposobów, w jaki się ono załamuje – jak i kiedy przestaje działać.

Utrzymywalność jest zatem miarą kosztu utrzymania oprogramowania. Im mniej telefonów po godzinach, ręcznych interwencji jest wymaganych do utrzymania systemu na chodzie, im krótszy czas od wykrycia błędu do wdrożenia poprawki, tym bardziej utrzymywalne jest oprogramowanie.

Znaczenie utrzymywalności

Wytworzenie oprogramowania wiąże się z poniesieniem znacznych kosztów. W 2017 roku oprogramowanie wciąż kosztuje bardzo dużo. Przez to rozumie się głównie czas developmentu, który waha się od kilku miesięcy do lat – wszystko zależnie od projektu. Jeżeli mamy do czynienia z oprogramowaniem na zamówienie, to właściwe utrzymanie zaczyna się często dopiero po oddaniu zakontraktowanego produktu. Czas życia na produkcji vs czas życia w fazie developmentu potrafi mieć się jak 10:1. Utrzymaniem raczej nie będzie się tym zajmować ten sam podmiot, który wytworzył oprogramowanie. Warto byłoby zatem pomyśleć o optymalizacji kosztów oprogramowania, gdy już zejdzie z taśmy produkcyjnej.

Nie zawsze jest to właściwy opis sytuacji. Wszyscy jesteśmy teraz agile, rozumiemy zmienność wymagań, iteracyjne i inkrementalne podejście do wytwarzania i w ogóle 😛 . Innymi słowy utrzymanie może zaczynać się już w trakcie rozwijania produktu. Jeszcze inaczej ma się sprawa z topowymi firmami, które nie produkują softu na zamówienie, ale stale rozwijają własne produkty. Ciągły development zmienia kaliber problemów, z jakimi przychodzi się mierzyć na froncie utrzymania. Wdrożenie nowych wersji to nowe feature’y, ale i zawsze niepewność związana z możliwością wprowadzenia nowych błędów. W takich przypadkach szczególnego znaczenia nabierają praktyki rodzaju Continous Integration. Wracając do tematu – utrzymanie i development interferują ze sobą. Problemy produkcyjne spowalniają rozwój, przez co mogą być traktowane z coraz większą niechęcią jako smutna konieczność, która nie pomaga wytworzyć pożądanej wartości. Tymczasem utrzymanie stanowi świetne źródło informacji o tym, jakie problemy trapią produkt. Jego przetrwanie jest często tożsame z przetrwaniem firmy, która go rozwija i utrzymuje jednocześnie. W takim przypadku dokładanie do własnego produktu trudnych w utrzymaniu elementów (które wcale nie muszą być kosztowe na etapie developmentu!) może w długiej perspektywie przysporzyć więcej strat niż zysków, a jak wiadomo czas utrzymania softu jest zwykle wielokrotnie dłuższy niż wyprodukowania.

Przygotowanie utrzymywalnego produktu daleko wykracza poza standardowe kryteria akceptacyjne, to jest przejście weryfikacji QA. Bardzo trafnie ujął to Michael T. Nygard w Release it:

If you fail to design your system for a production environment, your life after release will be filled with “excitement.”

Tymczasem zaprojektowanie systemu i jego późniejsza ewolucja w kierunku optymalnego działania na produkcji nie może się odbyć bez zbierania feedbacku. Nie da się przecież niczego nauczyć bez kontroli postępów, inaczej wciąż popełnia się te same błędy myśląc, że wszystko jest w porządku. 🙂

Cechy utrzymywalnego oprogramowania

Transparentność

Zdecydowanie najważniejsza cecha. Oznacza możliwość zdobycia informacji o obecnym stanie i kondycji systemu oraz historycznych zmianach tych dwóch. Innymi słowy, można łatwo uzyskać informację o tym ‚co siedzi’ w systemie.

Na tę cechę składają się po pierwsze mechanizmy monitorowania. Poddajemy ciągłej obserwacji środowiska, w których działa aplikacja poprzez parametry niskopoziomowe, takie jak utylizacja CPU czy dysku. Obserwujemy też i powiadamiamy o padach samej aplikacji. Wyciek pamięci potrafi położyć proces, o czym można dowiedzieć się wiele długich minut później bez odpowiednich mechanizmów zabezpieczających i powiadamiających. Jeszcze gorzej, gdy dowiadujemy się o tym od poirytowanych użytkowników. Sprawdzamy także, jak wygląda użycie systemu. Jeżeli jest to jakaś forma sklepu internetowego, a ilość zawartych transakcji kupna – sprzedaży nagle spadła do 0, to coś może być na rzeczy. Niekoniecznie zabójcza promocja u konkurencji. Bardziej wyrafinowany przykład – ilość dodań produktu do koszyka jest wysoka, ale większość z zaczętych tak zakupów kończy się na etapie płatności i nie przechodzi dalej. Widzę dwie możliwości – inwazję Niezdecydowanych lub problem z operatorem płatności.

Kolejną rzeczą jest powiadamianie o błędach w aplikacji – w najprostszej formie wielki try … catch w głównej pętli programu przechwytujący niezłapane wyżej wyjątki i wysyłający o nich powiadomienia dowolnym kanałem komunikacji, chociażby mailowo.

Dobry monitoring i zestaw powiadomień o problemach w aplikacji jest krytycznym aspektem transparentności. Pozwala reagować na problemy zanim dowiemy się od nich od użytkowników. O ile w ogóle się dowiemy, zanim zirytowany klient nie opuści strony/odinstaluje programu i skorzysta z produktu konkurencji. Z tego powodu bardzo podoba mi się motto Sentry:

Stop hoping your users will report errors

Kolejnym sposobem manifestacji transparentności jest obecność dobrych logów. Na ich podstawie powinno się dać odtworzyć stan aplikacji, który doprowadził do wystąpienia błędu. Absolutnym minimum jest zapisywanie informacji o żądaniach zmiany stanu systemu.  Swoista forma access_loga znanego z serwerów HTTP też jest wskazana.

Logowanie numerów kart płatniczych i haseł klientów można sobie z czystym sumieniem odpuścić.

Resilience

Niestety nie ma dobrego dosłownego tłumaczenia tego słowa na język polski, które brzmiałoby ładnie w kontekście oprogramowania, więc posłużę się omówieniem:

zdolność do samoczynnego powrotu do poprawnego stanu po szkodliwym incydencie, bez żadnej ingerencji z zewnątrz

Posłużę się trywialnym przykładem, niech będzie to serwer HTTP nginx z PHP uruchomionym przez FastCGI, chociaż z tego obrazu Dockera. Jeżeli jeden z procesów PHP obsługujący żądania zginie (np przekroczy dozwoloną ilość pamięci), to zostanie zastąpiony nowym. Stało się bez jakiejkolwiek interwencji człowieka. W produkcyjnych warunkach taka sytuacja mogłaby wystąpić w trakcie obsługi żądania użytkownika. Wtedy prawdopodobnie zakończyłoby się to 500tką, ale po tym system wróciłby do normalnego stanu i dalej obsługiwał żądania jak gdyby nic się nie stało.

W dobie mikroserwisów i systemów złożonych z wielu usług resilience jest bardziej złożonym zagadnieniem. Przygotowanie do jego osiągnięcia obejmuje między innymi obsługę automatycznego wznawiania połączeń po ich zerwaniu, ponawianie prób żądań, wprowadzanie redundantnych usług, ograniczenie zasięgu lokalnych awarii pojedynczej usługi i zabezpieczenia przed kaskadową propagacją skutków blokady jednego z elementu systemu.

Jak można testować resilience swojego systemu? Spojrzeć na rysunek architektury, zatrzymywać wzrok przy kolejnych elementach i zadać sobie pytanie – czy system sam odzyska stabilność (i ile to zajmie), gdy stanie się jedno z następujących:

  • aplikacja X przestanie działać
  • połączenie sieciowe między serwerami A i B zostanie zerwane

Netflix wypuścił narzędzie zwane Chaos Monkey, które ma testować tę cechę systemów.

Łatwość wdrażania nowych wersji

Ostatnie, ale nie najmniej ważne. Usterka w oprogramowaniu została wykryta i zdiagnozowana, przygotowano odpowiedni patch. To, co się dzieje dalej zależy od sposobu, w jaki release’owane jest oprogramowanie.

Aplikację w chmurze, nad którą ma się kontrolę dużo łatwiej zaktualizować niż zainstalowaną na komputerach użytkowników. Z drugiej strony Microsoft i upierdliwe aktualizacje Windowsa włączające się zawsze wtedy gdy nie potrzeba są przykładem, że można. 😛 Na wzmiankę zasługuje też sposób, w jaki Niantic wymusza update aplikacji klienckiej na smartfonach w swoim Pokemon Go – nie pograsz, dopóki nie ściągniesz najnowszej aktualizacji. Ma to sens – nie muszą wspierać dziesiątek wersji jednocześnie.

W przypadku aplikacji internetowych w chmurze pozostaje też aspekt czysto techniczny związany z samym umieszczeniem kodu. Jeżeli jest to język skryptowy, a aplikacja bezstanowa i można ją restartować bez większych konsekwencji, to patcha można zaaplikować bezpośrednio. W przypadku języków kompilowanych stworzenie nowego wydania to dużo więcej pracy.

W imponujący sposób ten problem został rozwiązany w Erlangu, będącym przecież językiem kompilowanym. Poprzez całkowitą separację danych aplikacji od kodu umożliwia on podmianę implementacji i migracje stanu pomiędzy wersjami bez przerywania obsługi żądań. O tym, jak to jest zrobione napiszę osobnego posta.

 

photo credit: mattk1979 Atlas Sculpture, Rockefeller Center, New York via photopin (license)

Docker – kontener z bazą danych do testów end-to-end

Automatyczne testowanie aplikacji stało się powszechnym i docenianym etapem wytwarzania oprogramowania. Służy nie tylko ochronie przed regresją (psuciem istniejących funkcjonalności), ale także jako punkt odniesienia ‚gdzie jesteśmy’ w obecnej iteracji. Dobre testy w połączeniu z ciągłą integracją w wydatny sposób zwiększają stopień, w jakim panujemy nad wytwarzanym produktem. Poza tym jak powszechnie wiadomo

kod nieprzetestowany z definicji jest zepsuty

Czytaj dalej Docker – kontener z bazą danych do testów end-to-end

Co siedzi w działającej aplikacji? – manhole

Zdjęcie, które ilustruje ten wpis wymaga słowa komentarza. Przedstawia właz do ulicznego kanału, po angielsku – manhole.

manhole – a covered opening in a road that a worker can enter in order to reach underground pipes, wires, or drains that need to be examined or repaired – Cambridge Dictionary

Jak to się ma zaglądania w bebechy działającej aplikacji przekonasz się czytając kolejne akapity.

Czytaj dalej Co siedzi w działającej aplikacji? – manhole

Dlaczego nie powinieneś pracować sam nad projektem mając obok zespół?

Wszyscy zapewne mieliśmy chociaż raz do czynienia z programistą – gwiazdą rocka (ang. rock star). Taka osoba jest indywidualistą, często bardzo błyskotliwym i w efekcie pracującym bardziej obok niż w zespole, który za nim nie nadąża. Jeszcze gorzej ma się sprawa w przypadku zwyczajnego developera, który dostał samodzielny projekt, żeby mógł się „wykazać”. Takie solówki w wytwarzaniu oprogramowania mają kilka bardzo poważnych wad.

Czytaj dalej Dlaczego nie powinieneś pracować sam nad projektem mając obok zespół?

Programowanie przez permutację

Znasz to uczucie, gdy uparcie próbujesz rozwiązać jakiś problem w kodzie, a rozwiązanie wydaje się być na wyciągnięcie ręki, tylko jakoś nie możesz do niego dojść, a minuty lecą?

Jeszcze tylko ten drobiazg i tamta pierdoła. Uruchamiasz kod często, testujesz i bach – to, co przed chwilą działało już nie działa, ale za to naprawiłeś coś innego. Uważaj – możesz właśnie programować przez permutację.

Czytaj dalej Programowanie przez permutację