2021-12-25 / Bartłomiej Kurek
Integralność danych: #1 - wstęp

W dzisiejszym świecie tworzymy aplikacje w oparciu o wyabstrahowane systemy/frameworki umożliwiające nam szybką realizację prototypu i wdrożenie rozwiązania. Rezultaty często mianujemy akronimem MVP (Minimum Value Product), a zadowoleni z łatwo i szybko uzyskanych rezultatów uznajemy je za product value. W tym artykule nakreślimy krótko różne aspekty techniczne, a w następnej części zajmiemy się tematem od strony praktycznej bazując na prostych przykładach.

Poprawność programów

Komputery umożliwiają nam automatyzację procesów, prowadzenie wydajnych obliczeń, czy symulacji. Programy tworzymy w celu uzyskania wyników oraz automatyzacji akcji. Dążymy do tego, aby akcje i wyniki obliczeń nie zawierały błędów. Program produkujący błędne wyniki lub gubiący dane jest - w gruncie rzeczy - nieużyteczny. Oczywiście - nie każdy system musi być transakcyjny.

Źródeł błędów istnieje wiele - błędy w algorytmach, w językach programowania, w systemach, w założeniach, błędy zaokrągleń liczb, czy błędy ogólnie związane z typami danych. Komputer jedynie liczy, jeśli algorytm nakazuje maszynie liczyć błędnie - takie też wyniki otrzymamy, błędne.

Konksekwencje błędów w programach

Konsekwencje

Konsekwencje błędnego działania programów oraz przeoczeń w ich założeniach mogą mieć różne skutki. Zależy to od natury procesu, który realizujemy wykorzystując w tym celu maszynę. Nie zawsze też uda się wyłapać wszystkie możliwe błędy w fazie testowania. Wylistujmy kilka rzeczywistych (historycznych) i teoretycznych konsekwencji w błędach oprogramowania.

Przykłady: jednostki, obliczenia.

Tutaj można przytoczyć wykłady profesora Waltera Lewina:

Przykłady: bezpieczeństwo.

  • w chwili, kiedy piszę ten artykuł, wielu programistów i administratorów być może zajmuje się "łataniem systemów" i całych infrastruktur związanych z błędami log4j
  • około 2 miesięcy temu sam realizowałem analizę włamania na pewien serwer. Włamanie odbyło się przez aplikację Atlassian Jira, a sam błąd był natury RME (remote code execution). Serwer nie miał monitoringu, informacja o błędzie/wirusie pojawiła się w tym przypadku ze strony usługodawcy, kiedy serwer wykorzystał nagle 15TB limitu transferu danych.

Błędy się zdarzają i będą się zdarzać. Dobrym powiedzeniem jest tutaj: "it's not if but when". Doświadczonym programistom znajoma będzie też fraza defensive programming.

Inne przykłady.
W obecnym świecie aplikacji internetowych logujemy się do usług stosując uwierzytelnianie dwuskładnikowe. Powstały aplikacje, które ułatwiają ten proces (mechanizm push notifications w naszych urządzeniach mobilnych). Pracowałem kiedyś nad aplikacją tego typu. Spisany standard OTP (rfc6238) jasno mówi, że dane walidacyjne (klucze) MUSZĄ być przechowywane w bezpieczny sposób:

   The key store MUST be in a secure area, to avoid, as much as
   possible, direct attack on the validation system and secrets
   database.  Particularly, access to the key material should be limited
   to programs and processes required by the validation system only.

Jeśli klucz powiązany z mechanizmem OTP wycieknie, konsekwencje mogą mieć różny charakter. Przykładowo:

  • ktoś może przejąć nam konto w mediach społecznościowych i opublikować brzydsze zdjęcia kotów
  • ktoś może przejąć nasze konto i uzyskać dostęp do danych poufnych, bądź opublikować treści niszczące nasz biznes
  • ktoś może zalogować się na konto swojego partnera lub swojej partnerki w serwisie randkowym, odkryć zdradę, a następnie popełnić morderstwo/samobójstwo, osierocić dzieci, rozwieść się, itd.

Bufory:
Wiele rozwiązań programowych, w których ważne są m.in. prędkość przetwarzania danych oraz asynchroniczość zapewniająca skalowalność, korzysta z kolejkowania wiadomości. Problemów większych nie ma systemach, w których nie jest wymagana transakcyjność lub gwarancja dostarczenia danych. Inaczej rzecz ma się w systemach transakcyjnych, gdzie nie chcemy zgubić lub opóźnić wiadomości ("alert o trzęsieniu ziemi w ogóle nie dotarł, albo dotarł zbyt późno"). Kolejki buforują dane. Systemy bywają restartowane, a zadania wznawiane. Zadania często mają swoje priorytety. Takimi zagadnieniami zajmowali się już giganci dziedziny, jak Dijkstra. To na ile system gwarantuje poprawność zależy często od wymagań/założeń. Być może jednak nie jest sztuką stworzyć program szybki, ale/bo czasem niepoprawny.

Podobnym zagadnieniem jest zapis danych na dysk. Bazy danych oferują możliwość synchronicznego zapisu (np. fsync w PostgreSQL), jednak to znacznie spowalnia pracę takiego systemu bazodanowego. Systemy transakcyjne implementują różne mechanizmy gwarantujące integralność danych w obrębie transakcji (Write Ahead Log, itp.). Warto jednak mieć świadomość, że wyższe warstwy systemów zbudowane sa na tych niższych. Sam zapis na dysk może zależeć od sposobu działania sterownika. W niektórych dyskach występuje np. mechanizm native command queuing NCQ. Nie wnikając już tutaj w szczegóły zaznaczmy jedynie, że queuing to kolejkowanie, a kolejki to bufory.
"Wszystko działa na czymś".

Automatyzacje:
Żyjemy też w świecie pełnym procesów zautomatyzowanych. Przykładowo - błędy związane z datą i czasem mogą skutkować niewpuszczeniem nas do budynku, brakiem ważności różnych certyfikatów, a mogą też być np. materiałem dowodowym w sprawach sądowych (timestamp na nagraniach z monitoringu, itp).
Daty to koncept, sprowadzone są do liczb, a czasem podlegają również polityce. Polityka może decydować czy używamy zmian czasu z zimowego na letni, czy też ile dany region ma stref czasowych. Można oczywiście wspomnieć też terminy takie jak: Y2k, leap second, unix timestamp. Ja zostawię tutaj jednak link do time programming, w którym znajdziemy również sekcję The tragedy of timezones.

Sam czas to trudny koncept, również w komputerach. Poniższy film nagrałem w tej chwili, a plik jest z przyszłości:

$ stat obs-2021-12-25_14-42-05.mp4 
  File: obs-2021-12-25_14-42-05.mp4
  Size: 17073263        Blocks: 33360      IO Block: 4096   regular file
Device: fd00h/64768d    Inode: 1207808     Links: 1
Access: (0600/-rw-------)  Uid: ( 1000/      me)   Gid: ( 1000/      me)
Access: 2021-12-25 14:42:06.589688604 +0100
Modify: 2031-01-01 00:00:24.036648081 +0100
Change: 2031-01-01 00:00:24.036648081 +0100
 Birth: 2021-12-25 14:42:06.589688604 +0100

Video: monotoniczny upływ czasu

Video: MP4, 486K, 1920x1080. Duration: 00:00:50 Link

25DEC = ??

Większość z nas zna żart Oct 31 = Dec 25.
Nic jednak dzisiaj nie zobrazuje lepiej zagadnienia czasu niż fakt, iż w dniu dzisiejszym wypadają 379 urodziny Isaaca Newtona. Artykuł na Wikipedii podaje jednak dwie daty urodzin Newtona:

Born:   4 January 1643 [O.S. 25 December 1642]

"Czas jest względny".

AI/ML

Postęp technologiczny przynosi nam wszelkie zalety i zagrożenia związane z dziedzinami sztucznej inteligencji, predykcją, podejmowaniem przez maszynę automatycznych decyzji na podstawie różnych danych zbieranych z otoczenia. AI/ML to bardzo głębokie dziedziny wiedzy, w których kluczową rolę odgrywa matematyka. Występują jednak takie zjawiska jak uprzedzenia (bias), aproksymacje, błędne klasyfikacje, czy generalizacje ("skoro było tak wcześniej i tylko takie dane mamy, to zakładamy, iż kolejny przypadek będzie miał taką samą charakterystykę, wszak nie ma innego zapisu historycznego w naszych danych").

O ile dziedziny AI/ML są fascynujące, o tyle nie sposób nie zauważyć, iż sprowadzone do biznesu niekoniecznie wnoszą w ludzkie życie same udogodnienia. Kiedyś były "automatyczne sekretarki", voice mail, dzisiaj mamy chatboty, teleporady "lekarskie", czy rasizm algorytmów, które - niestety - naśladują ludzi. Monkey see, monkey do. Najczęściej widzimy, że systemy/roboty AI są podobne do ludzi - rozmawiają, wnioskują, często mają antropomorficzny kształt. Budujemy je na własne podobieństwo. Oczywiście AI zapewnia też szereg możliwości, a któż przewidzi co jeszcze przyszłość przyniesie...

Jeszcze kilka lat temu świat mediów epatował doniesieniami o self driving cars, a inna część społeczności wieszczyła AI winter. Można postawić tezę, że "epoki lodowcowe" są cykliczne. Ja osobiście chciałbym móc dyktować do Emacs kod programów i treść artykułów, chciałbym aby poprawiał literówki i przecinki, ale nie chciałbym oddawać mu możliwości generowania tych treści. Warto jednak zauważyć, że postęp w dziedzinie AI prowadzi do nowych osiągnięć, które w wymiarze ludzkim są pozytywnym postępem. Wszystko zależy od nas samych i naszych celów.

Testowanie

Nie zawsze testowanie nam pomoże. Przypadkiem jaki pierwszy przychodzi mi do głowy, jest historia, której nie umiem w tej chwili znaleźć w zasobach sieci Inernet. Niemniej - kiedyś rakieta bojowa wbiła się w szkołę pełną dzieci. Powodem był błąd konwersji jednostek w oprogramowaniu rakiety. Rakietę rozwijały dwa zespoły programistów - jedni używali systemu metrycznego (SI), drudzy jednostek imperialnych. Odległość celu wyrażona liczbowo niewiele znaczy jeśli nie wiemy czy (dla przykładu) liczba 2 oznacza kilometry czy mile. Oba zespoły mogły mieć nawet 100% pokrycia kodu testami, ale po integracji w jedną całość system zawierał błąd. Nie zawsze też wszyscy sprawdzają integrację systemów, a w przypadku rakiet bojowych na końcu i tak jest wybuch. Być może prawdą jest, że w takich przypadkach kwestie finansowe decydują o fazie testów.

"Beware of bugs in the above code; I have only proved it correct, not tried it."
- Donald Knuth

Coverage 100%:
Wystarczy spojrzeć na "swoje podwórko".
Sprawdzałem czy hasło jest hashowane przy pierwszym zapisie, ale już nie sprawdzałem:

  • czy hash pozostaje bez zmian przy aktualizacji innych danych rekordu
  • czy nowe prawidłowo się hashuje

Coverage był 100%. I co z tego?

vmapp/users/models.py                         31      0   100%

Podsumowanie

Ten artykuł kończymy na tych krótkich przykładach. Rodzajów i źródeł błędów jest wiele, a wśród nich takie, na które mamy wpływ, oraz te - poniekąd - niezależne od nas samych. W następnej części przyjrzymy się zagadnieniu integralności danych na krótkich fragmentach kodu i założeniach systemów, które stosujemy na co dzień.