Nie umiałbym odtworzyć swojego systemu. Sprawdziłem, i teraz muszę napisać ten wpis.
TL;DR
Zrobiłem sobie audyt kopii zapasowych całego stosu narzędzi z którymi pracuję z AI. Znalazłem trzy grupy artefaktów o których zwykłe myślenie o backupie w ogóle nie wie, kilka scenariuszy w których mechanizm istniał i nie działał, oraz jeden konkretny plik, który był omijany przez każdą warstwę kopii, mimo że każda warstwa była napisana poprawnie. Wychodzi z tego pewien sposób myślenia, który wolałbym mieć wcześniej niż go dostawać podczas prawdziwej awarii.
Ten wpis pokazuje sposób myślenia, nie zestaw narzędzi. Nie znajdziesz tu żadnej mapy "u mnie leży tu i tu", bo taka mapa dla obcego czytelnika to podręcznik ataku. Znajdziesz zamiast tego zestaw pytań, które warto zadać sobie samemu przed najbliższą awarią.
Skąd to się wzięło
Siedziałem nad rutynowym projektem, który wymagał odtworzenia jednej rzeczy w moim asystencie. Nic dramatycznego, żaden pożar, po prostu chciałem zobaczyć, jak mi pójdzie odtworzenie fragmentu, gdyby to była naprawdę awaria. Zajęło mi to trzy dni.
Nie dlatego, że nie miałem kopii. Miałem kilka, w kilku miejscach, robionych regularnie. Zajęło trzy dni dlatego, że każda kopia zapisywała trochę inne fragmenty tej samej rzeczy, żaden z tych zestawów nie był kompletny, a poskładanie ich w całość wymagało pamiętania, jak to wszystko było ze sobą powiązane. Właśnie ta pamięć okazała się najbardziej nieodtwarzalnym elementem systemu.
Wróciłem do biurka i przez następne dwa dni przeszedłem audyt każdego mechanizmu kopii, jaki miałem. Wnioski z tego audytu są tym, co próbuję zapisać, zanim znowu zapomnę.
Trzy kopie, dwa nośniki, jedna poza siedzibą, czyli reguła, która myli
Klasyczna zasada mówi trzy kopie na dwóch nośnikach, jedna poza siedzibą. Problem z nią jest taki, że skupia uwagę na liczeniu kopii, a właściwe pytanie brzmi zupełnie inaczej.
Właściwe pytanie brzmi: jakie pojedyncze zdarzenie zabiera mi wszystkie kopie naraz.
Kiedy tak spojrzysz, wychodzą absurdy. Dwie kopie w jednym pomieszczeniu to jedna kopia wobec zalania, pożaru i kradzieży. Kopia na drugim dysku w tej samej maszynie to jedna kopia wobec ransomware. Kopia synchronizowana dwukierunkowo z chmurą to jedna kopia wobec przypadkowego skasowania, bo skasowanie się propaguje.
Ta sama zasada, sformułowana jako "niezależne tryby awarii", sama zaczyna podpowiadać dobre decyzje. Ile kopii masz, staje się mniej ważne od tego, co je łączy.
Poziom zerowy, o którym się zapomina
RAID nie jest kopią zapasową. Przeżywa awarię dysku i dosłownie nic więcej. Ludzie traktują macierz jak backup, bo intuicyjnie widzą dwa dyski i myślą "jest kopia". Macierz chroni cię przed jednym trybem awarii spośród kilkunastu, jakie mogą się przydarzyć.
Warto to sobie mocno rozdzielić w głowie, żeby przypadkiem nie uspokoić się dwoma dyskami w tej samej obudowie.
Taksonomia artefaktów, czyli sedno wpisu
Tu jest część, która mnie najbardziej otworzyła oczy podczas audytu. Kiedy zacząłem listować, co właściwie mam u siebie, okazało się, że różne rzeczy potrzebują różnego traktowania. Wszystko dzieli się na trzy kategorie.
A. Odtwarzalne z publicznych źródeł
Modele pobierane z otwartych repozytoriów. Obrazy kontenerów. Biblioteki i zależności. To wszystko potrafi zajmować dziesiątki gigabajtów.
Nie backupuj tego. Zapisz jak to pobrać. Kopia dziesiątek gigabajtów, które w każdej chwili są do ściągnięcia z otwartego internetu, marnuje miejsce i czas transferu, a wnosi wartość zerową w stosunku do zapisania jednej linijki instrukcji.
Jest tu jednak pułapka, która przez lata nie istniała, a teraz jest wszędzie. Parametry uruchomienia i strojenia są maleńkie i całkowicie nieodtwarzalne z pamięci. Ile pamięci przydzielić, jaki limit kontekstu, które flagi kwantyzacji, jaki temperature dla którego use case. Ktoś dobierał to metodą prób przez pół dnia i już nie pamięta, dlaczego akurat tak. Plik z tymi ustawieniami waży kilka kilobajtów i jest wart więcej niż wszystkie wagi modeli, które opisuje.
Zasada: nie backupuj tego, czego internet nie zapomniał. Backupuj to, czego ty nie odtworzysz.
B. Odtwarzalne z własnego źródła
Kod, konfiguracja, szablony promptów, dokumentacja. To klasyczna domena kontroli wersji i tu wszyscy wiemy, co robić.
Problem jest inny, subtelniejszy: rozjazd między tym, co jest w repozytorium, a tym, co faktycznie działa. Podczas audytu znalazłem katalog roboczy na maszynie, który różnił się od tego, co siedziało w uruchomionej usłudze. Był to najbardziej oczywisty katalog do odbudowy i był nieaktualny. Gdybym odtwarzał po awarii z tego, co wygląda na źródło prawdy, wdrożyłbym starszą wersję i szukałbym przyczyny dziwnych błędów przez wiele godzin.
Wniosek, który sobie stąd wyciągnąłem: źródłem prawdy jest to, co działa, nie to, co leży w katalogu o właściwej nazwie. Warto mieć mechanizm, który zrzuca stan z żywego procesu, a nie ufa katalogowi roboczemu.
C. Nieodtwarzalne
I teraz robi się ciekawie, bo praca z AI generuje takich rzeczy więcej, niż się wydaje na pierwszy rzut oka.
Modele dostrojone samodzielnie. Trening kosztował czas i prąd, a dane treningowe mogły być zbierane miesiącami. Plik z wagami waży kilkadziesiąt megabajtów i jest niezastępowalny, chyba że masz jeszcze wszystkie dane treningowe plus dokładną procedurę treningu.
Bazy wektorowe. Formalnie odtwarzalne, bo można przeliczyć wszystko od nowa. Praktycznie: godziny obliczeń i konieczność posiadania nadal tych samych danych źródłowych w niezmienionej postaci. Kategoria "odtwarzalne, ale nie chcesz tego robić o trzeciej w nocy w środku awarii".
Próbki referencyjne głosu albo stylu. To jest najciekawszy przypadek i wart własnego akapitu. Kilkanaście sekund nagrania, z którego sklonowano głos. Możesz nagrać się od nowa, ale nowy klon nie będzie identyczny z tym, którym system mówił dotąd. Kod przepiszesz. Tych kilkunastu sekund nie nagrasz drugi raz tak samo, choćbyś nagrywał sto razy.
Notatki, wrzutki, zapisane rozmowy z asystentem. Pamięć systemu. Jeśli twój asystent buduje kontekst z tego, co mu przez miesiące wrzucałeś, to ten zbiór jest nieodtwarzalny w sposób absolutny. Nie ma go skąd wziąć. Wszystkie decyzje, które asystent podejmował na podstawie tego kontekstu, opierają się na czymś, czego nie da się zrekonstruować z niczego innego niż same te notatki.
Konfiguracja tworzona ręcznie przez interfejs. Klasyczny cichy zabójca. Kilkanaście elementów wyklikanych w panelu jakiegoś dostawcy, bez odpowiednika w żadnym pliku po twojej stronie. Nie ma ich w repozytorium, bo nigdy nie były plikiem. Są tylko w bazie danych dostawcy, do której nie masz bezpośredniego dostępu.
Poświadczenia i klucze. Osobna kategoria, bo mają dodatkową właściwość: samo ich skopiowanie tworzy nowe ryzyko. O tym za chwilę osobno.
Warstwy, każda rozwiązuje problem i tworzy nowy
Kolejny widok, który mi pomógł: idąc od najprostszej warstwy do najbardziej odpornej, każda kolejna rozwiązuje pewien problem, ale dokłada nowy.
Zwykłe pliki tekstowe
Tanie, czytelne, przeszukiwalne, otworzą się za dwadzieścia lat na dowolnym komputerze bez żadnej zależności. Dla notatek i wiedzy nadal najlepszy format, bo nie wymaga niczego oprócz systemu plików.
Problem: są bezpieczne dokładnie tak, jak dysk, na którym leżą.
Kontrola wersji
Dokłada historię, zdalną kopię i możliwość cofnięcia się. Dla kodu i notatek to naturalny drugi poziom, tak oczywisty, że wielu ludzi kończy właśnie tutaj i uważa, że problem backupu został załatwiony.
Wprowadza pułapkę, którą wcześniej mało kto zauważał, bo pojawiła się razem z pracą z AI. Reguły ignorowania plików są domyślnie napisane pod projekty programistyczne. Wykluczają pliki binarne, duże, multimedialne. Wykluczają dokładnie tę kategorię, w której mieszkają modele dostrojone, bazy wektorowe, próbki głosu, artefakty generowane. Systemy kontroli wersji nie były projektowane z myślą, że najcenniejszy plik w twoim projekcie może ważyć czterdzieści megabajtów i być pojedynczą próbką dźwięku.
Historia podwójnego wykluczenia
Ten fragment jest osobno, bo to najlepsza pojedyncza obserwacja z całego audytu.
Nagranie referencyjne, którym mój asystent mówi, było wykluczone z kopii dwukrotnie i całkowicie niezależnie. Raz przez regułę ignorowania plików multimedialnych w repozytorium, która zapobiega puchnięciu historii wersji przez binaria. Drugi raz przez wykluczenie tego samego typu plików w skrypcie synchronizującym zawartość katalogu do miejsca poza maszyną, który miał świadomie omijać duże binaria, żeby transfer nie zajmował godzin.
Obie reguły były napisane poprawnie i z dobrych powodów. Każda z nich rozwiązywała realny problem. Ich złożenie dało efekt, którego nikt nie zamierzył: najbardziej nieodtwarzalny plik w całym systemie był aktywnie omijany przez każdy istniejący mechanizm kopii.
Nikt nigdy nie napisał reguły, która by ten plik obejmowała, bo każdy patrzył na swój fragment i widział, że jego fragment działa jak powinien. Reguła ignorowania plików multimedialnych w repozytorium działała. Wykluczenie binariów w skrypcie synchronizacji działało. Nikt nie zobaczył całości.
Morał, który mi z tego wyszedł, jest ogólniejszy niż tylko o backupie: luki rzadko biorą się z tego, że ktoś napisał złą regułę. Zwykle biorą się ze składania dobrych reguł, których nikt nie oglądał jako pełny zestaw.
Kopia poza siedzibą
Jedyna warstwa chroniąca przed utratą całego miejsca. I jedyna, która wprowadza całą klasę nowych problemów.
Kierunek synchronizacji. Narzędzie do synchronizacji chmurowej to nie to samo, co backup. Synchronizacja dwukierunkowa oznacza, że skasowanie w chmurze kasuje oryginał, a skasowanie oryginału kasuje kopię. Do kopii zapasowych właściwy jest wyłącznie kierunek w górę, plus wyraźna opcja nieusuwania w miejscu docelowym. Kosztem takiego ustawienia jest to, że archiwum rośnie i wymaga okresowego przeglądu. To świadomy wybór odporności kosztem porządku.
Szyfrowanie. Jeśli w archiwum jadą żywe poświadczenia, klucze API, pamięć asystenta z rozmów o klientach, to wysyłanie tego niezaszyfrowanego zamienia jedno ryzyko na drugie. Chronisz się przed pożarem w domu, a otwierasz na kompromitację twojego konta u dostawcy chmurowego przez dowolny scenariusz, który nie jest pożarem.
I tu pułapka, która wygląda mądrze, a jest błędem. Klucz deszyfrujący nie może mieszkać w tym, co chroni. Scenariusz, przed którym się bronisz, to utrata maszyny. Klucz zapisany wyłącznie na tej maszynie chroni cię przed niczym, bo znika razem z powodem, dla którego byłby ci potrzebny.
Wariant szczególnie zdradliwy: menedżer haseł hostowany na tej samej maszynie, której kopię właśnie szyfrujesz. Wygląda jak dobra praktyka, bo hasła są w bezpiecznym trezorze. W scenariuszu awarii jest to jedno i to samo pudełko z zamkiem i kluczem w środku.
Awarie, które nie mają jak krzyknąć
Najmocniejszy wątek, jaki mi wyszedł podczas audytu. Znalazłem trzy sytuacje, w których mechanizm istniał, robił coś systematycznie, ale to coś było niewłaściwe, i nikt tego nie zauważył. Nie dlatego, że nikt nie patrzył. Dlatego, że awaria nie miała jak się odezwać.
Cicha awaria. Skrypt kopii miał ustawioną obsługę błędów, ale każdy pojedynczy krok kończył się instrukcją "ignoruj niepowodzenie". W efekcie proces jako całość kończył się kodem sukcesu, mimo że nie udało mu się nic. Awaria trwała cztery kolejne noce i przeszła absolutnie niezauważona, bo nic nigdzie nie zapaliło się na czerwono. Odkryłem to dopiero podczas audytu, otwierając logi, na które przez cztery noce nikt nie miał powodu spojrzeć.
Kolizja harmonogramów. Zadanie kopii uruchamiało się o godzinie, o której maszyna docelowa była wyłączona przez własny harmonogram oszczędzania energii. Zadanie nie mogło się udać nigdy, ani razu. Wystarczyło przesunąć jedno z tych dwóch okien.
Ostrzeżenie do pustego pokoju. Mechanizm kopii bazy danych miał wbudowane zabezpieczenie: wykrywał tabele nieobjęte kopią i wypisywał ostrzeżenie. Ostrzeżenie trafiało do logu, którego nikt nie czyta. Działało poprawnie przez wiele tygodni, informując o realnej luce, i nie zmieniło absolutnie nic, bo szło tam, gdzie nikt nie patrzył.
Wniosek, który sobie z tego wyciągnąłem: wszystkie trzy problemy istniały nie dlatego, że zabrakło mechanizmów. Mechanizmy były i działały. Żaden z nich nie miał jak się odezwać. Kopia, która pada głośno, jest warta więcej niż kopia, która pada po cichu, bo tę drugą odkryjesz w najgorszym możliwym momencie.
Praktyczna zmiana, którą zrobiłem po audycie: awaria kopii trafia teraz tam, gdzie i tak patrzę codziennie, czyli na telefon. Sukces przestaje hałasować, bo alarm, który dzwoni codziennie, przestaje być alarmem. Cisza znaczy sukces, dźwięk znaczy problem. Powinno być tak od początku.
Zapis i odczyt to dwa różne systemy
Subtelny błąd, wart osobnej sekcji, bo pokazuje różnicę między myśleniem powierzchownym a rzeczywistym.
W audytowanym mechanizmie kopii bazy danych dodanie nowej tabeli wymagało zmiany w trzech miejscach jednocześnie: liście tego, co się zapisuje, liście kolejności przy przywracaniu, oraz katalogu kategorii do wyboru przez interfejs. Naturalnym odruchem programisty jest zmiana pierwszego miejsca, bo "właśnie po to jest ta lista".
Efekt takiej połowicznej zmiany: dane są w kopii, ale nie da się ich przywrócić. Widać je w archiwum, waga się zgadza, procedura wygląda na kompletną, tylko odtworzenie kończy się błędem. Jest to gorsze niż brak kopii, bo daje fałszywe poczucie bezpieczeństwa.
Uogólnienie, które wyszło mi z tego jednego przypadku: ścieżka zapisu i ścieżka odczytu to osobne systemy. Testowanie zapisu nie mówi nic o odczycie. Kopia, która nigdy nie została odtworzona, jest hipotezą, nie kopią.
Warstwa, która ginie razem z maszyną
Wątek dla czytelników bardziej technicznych, ale wart uwzględnienia, bo mało kto o nim myśli, dopóki mu jej nie zabraknie.
Zadania cykliczne systemu operacyjnego. Definicje usług startowych. Zadania w panelach administracyjnych. Reguły w panelach dostawców chmurowych. Ta warstwa istnieje wyłącznie na maszynie i nie ma jej w żadnym repozytorium. Ginie razem ze sprzętem, a jest tym, co sprawia, że cokolwiek w ogóle się uruchamia w oczekiwanych momentach.
Przy okazji tego audytu popełniłem własny błąd, wart przytoczenia jako ostrzeżenie. Sprawdziłem repozytorium, nie znalazłem tam definicji jednego z zadań cyklicznych, i wyciągnąłem wniosek, że to zadanie nie istnieje. Istniało, działało od miesięcy, tylko żyło poza repozytorium. Sam wpadłem w pułapkę, którą opisuję.
Dwa wnioski z tego, oba przydatne w codziennym audycie własnego stosu:
Brak czegoś w repozytorium nie znaczy, że to nie istnieje. Audyt oparty wyłącznie o repozytorium nie widzi tej warstwy w ogóle. To, że nie znajdujesz definicji harmonogramu, nie znaczy, że harmonogram nie działa. Może działać, tylko żyje gdzie indziej.
Obecność czegoś w repozytorium nie znaczy, że to działa. Zdarza się odwrotnie: plik jest, wygląda kompletnie, wersja się zgadza, ale to, co faktycznie odpalało się na maszynie, było już inne. Rozjazd między repo a rzeczywistością jest cichy i nie ma automatycznego wykrywania.
Praktyka, którą sobie ustawiłem: każdy element z tej warstwy powinien mieć swój odpowiednik w repozytorium, choćby jako zrzut definicji do pliku. Z jednym zastrzeżeniem, które oczywiste dopiero po chwili: takie pliki często zawierają sekrety, więc do kontroli wersji trafia wersja zredagowana bez tajnych wartości, a pełna wyłącznie do kopii szyfrowanej. Struktura, którą trzeba pamiętać, jest cenniejsza niż sam sekret, który zawsze można wygenerować od nowa.
Trzy własne wpadki, których nauczyłem się przy tym audycie
Zostawiam je celowo, bo wpis, w którym autor pokazuje wyłącznie swoje wnioski, brzmi jak wykład. Ten, w którym autor pokazuje też swoje potknięcia z tego samego tygodnia, brzmi jak dziennik.
Wpadka pierwsza. Kilka razy podczas audytu ogłosiłem sobie samemu, że dany mechanizm działa, na podstawie tego, że skrypt zakończył się kodem sukcesu. Widziałem zielone. Uznałem, że jest zielone. W rzeczywistości zielone było, bo skrypt miał ustawione "ignoruj niepowodzenie" w każdym kroku. Kod sukcesu nie znaczył, że coś się udało, tylko że nikt nie zaprotestował. Nauczyłem się patrzeć nie na kod zakończenia, tylko na to, czy w miejscu docelowym pojawiło się to, co miało się pojawić.
Wpadka druga. Zobaczyłem, że jakiegoś elementu nie ma w repozytorium, i uznałem, że w ogóle nie istnieje. Istniał, tylko żył poza tą warstwą, którą sprawdziłem. Odkryłem swój błąd dopiero, gdy przypadkiem znalazłem ten element w innej warstwie audytu. Ta jedna wpadka nauczyła mnie, że audyt oparty o jedno źródło danych o systemie zawsze będzie niekompletny. Trzeba przechodzić przez każdą warstwę osobno.
Wpadka trzecia. Na wczesnym etapie audytu widziałem duże archiwum leżące w kopii zewnętrznej i uznałem, że skoro tyle ważą, to znaczy, że wszystko w środku. Rozmiar bywa mylący. Duże pliki to zwykle właśnie te, których nie potrzebujesz w kopii, bo są odtwarzalne z publicznych źródeł. Małe pliki, których w archiwum brakowało, były tymi jedynymi, które naprawdę byłyby ci potrzebne w scenariuszu awarii. Waga kopii nie mówi nic o jej kompletności.
Wszystkie trzy pomyłki mają wspólny mianownik: zaufałem sygnałowi, który był łatwy do odczytu, zamiast zweryfikować to, co trudniej sprawdzić. Kod zakończenia jest łatwy. Rozmiar pliku jest łatwy. Obecność w repo jest łatwa. Wszystkie trzy są kiepskim przybliżeniem rzeczywistości.
Weź jak swoje
- Licz tryby awarii, nie kopie. "Ile mam kopii" to złe pytanie. "Jakie pojedyncze zdarzenie zabiera mi wszystkie naraz" to właściwe.
- RAID nie jest kopią. Chroni przed jednym trybem awarii z kilkunastu.
- Nie backupuj tego, czego internet nie zapomniał. Zapisz jak to pobrać. Zapisz parametry uruchomienia. Kilka kilobajtów wygrywa z dziesiątkami gigabajtów.
- Źródłem prawdy jest to, co działa, nie to, co leży w katalogu o dobrej nazwie. Zrzuty stanu z żywego procesu, nie ufność w katalog roboczy.
- Domyślne reguły ignorowania plików wykluczają dokładnie te artefakty, które produkuje praca z AI. Modele dostrojone, bazy wektorowe, próbki głosu, generowane binaria. Sprawdź swoje.
- Synchronizacja dwukierunkowa to nie backup. Do kopii zapasowych właściwy jest wyłącznie kierunek w górę plus nieusuwanie w miejscu docelowym.
- Klucz deszyfrujący nie mieszka w tym, co chroni. Menedżer haseł na maszynie, którą właśnie szyfrujesz, to pudełko z zamkiem i kluczem w środku.
- Kopia, która nigdy nie była odtworzona, jest hipotezą. Ścieżka zapisu i odczytu to osobne systemy.
- Awaria musi mieć jak krzyknąć. Powiadomienie o problemie tam, gdzie i tak patrzysz. Sukces nie hałasuje.
- Luki biorą się ze składania dobrych reguł. Każda z osobna działa, żaden nie widzi całości. Ktoś musi popatrzeć na pełny zestaw.
Puenta
Systemy, w których pracujemy z AI, mają dziś taką właściwość, jakiej nie miały wcześniej: największa część ich wartości leży w rzeczach, które nie mają swojego naturalnego miejsca w klasycznej hierarchii backupu. Pamięć asystenta, dostrojony model, sklonowany głos, konfiguracja wyklikana w panelu, godziny strojenia parametrów. Wszystkie te rzeczy powstały niedawno i wypadły z siatki, w którą byliśmy przyzwyczajeni łapać to, co warto ratować.
Nikt cię nie ostrzeże, że masz lukę, dopóki jej nie znajdziesz sam. Jedyny moment, w którym warto ją znaleźć, to zdecydowanie przed tym drugim momentem, w którym byś ją musiał znaleźć.
Zrób sobie własny audyt. Zajmie ci trzy dni, jeżeli twój stos jest podobnej wielkości do mojego. Trzy dni pracy, którą sam wybierzesz, są lepsze niż trzy dni pracy w środku prawdziwej awarii, kiedy telefon dzwoni w tle, a ty próbujesz odtworzyć rzecz, której nigdy nie odtwarzałeś.
Ten wpis powstał po audycie własnego stosu, w którym sam znalazłem u siebie każdą z opisanych tu pułapek. Jeżeli robisz podobny audyt u siebie i chcesz porównać wnioski, napisz na ratunku@chcedointernetu.pl.