Drugiego października o ósmej rano otworzyłem log nocnej kopii i przeczytałem dwie linijki, które do dziś pamiętam. Pierwsza: kopia serwera poczty gotowa, 257 GB, zaszyfrowana. Druga, czterdzieści pięć minut później: sprzątanie po przerwanym zadaniu, plik usunięty. Osiem godzin pracy serwera, cała noc, i na końcu automat grzecznie posprzątał po sobie, wyrzucając do kosza dokładnie to, po co go uruchomiłem.
Nikt się nie włamał, dysk się nie zepsuł, nie było awarii prądu. Automat zrobił to, co mu kazałem. A kazałem mu dwie rzeczy, które osobno brzmiały rozsądnie, a razem dały gotową receptę na stratę. Prowadzę agencję AI i automatyzacji dla MŚP, utrzymuję serwery, na których stoi poczta i strony klientów, i wszystko, co radzę firmom, najpierw sprawdzam u siebie. Tym razem sprawdziłem na własnej skórze, jak nie projektować procesu kopii. Opiszę oba błędy, przebudowę na fazy i to, co zadziałało dopiero za trzecim podejściem.
Spis treści
- Po co w ogóle robiłem tę kopię nocą?
- Jak automat mógł skasować coś, co sam przed chwilą zrobił?
- Pierwszy błąd: jeden zegar na całą robotę
- Drugi błąd: sprzątanie „wszystkiego”
- Jak podzieliłem kopię na fazy?
- Czy trzeci przebieg poszedł gładko? Prawie
- Kiedy wolno skasować oryginał?
- Gdzie w tym wszystkim jest AI?
- Jak sprawdzić, czy twój proces kopii nie ma tej samej pułapki?
- Słownik pojęć
Po co w ogóle robiłem tę kopię nocą?
Chodziło o serwer poczty. Ten, przez który przechodzi poczta firmowa moja i części klientów, z wieloletnimi skrzynkami. Chciałem mieć jego pełną kopię na drugim, niezależnym serwerze. Nie kopię pojedynczych skrzynek, tylko obraz całej maszyny wirtualnej, który da się odtworzyć w godzinę, jeśli pierwotny serwer zniknie.
Kopia całej maszyny ma jedną niewygodną cechę: obciąża ją. Przez kilka godzin system czyta cały dysk, kompresuje go, szyfruje. Na serwerze poczty oznacza to, że skrzynki mogą odpowiadać wolniej. W nocy nikt tego nie zauważy. O ósmej rano, kiedy ludzie siadają do maili, zauważy każdy.
Dlatego zbudowałem coś, co nazwałem strażnikiem. To mały program, który pilnuje kopii od startu do końca. Co piętnaście minut sprawdza, jak szybko odpowiada poczta. Jeśli dwa razy z rzędu odpowiada wyraźnie wolniej niż zwykle, przerywa kopię, bo ważniejsze jest, żeby poczta działała, niż żeby kopia skończyła się tej nocy. Do tego dostał twardy limit: najwyżej osiem godzin pracy i bezwzględny koniec o 8:30 rano, zanim zacznie się dzień roboczy.
Cały proces wyglądał tak: zrzut maszyny, kompresja, szyfrowanie, przesłanie pliku na drugi serwer, porównanie sumy kontrolnej, sprzątanie plików tymczasowych. Sześć kroków w jednym ciągu. Na papierze wszystko się zgadzało.
Pierwsza noc padła zresztą na czymś zupełnie innym i dość śmiesznym. Strażnik uznał pocztę za przeciążoną, bo próg ustawiłem jako trzykrotność normalnego czasu odpowiedzi. Normalny czas wynosił 5 milisekund, więc alarm włączał się przy 15. Mrugnięcie oka trwa kilkanaście razy dłużej, więc każde drobne drgnięcie w sieci wyglądało dla strażnika jak zawał. Dopisałem podłogę: próg to trzy razy normalny czas, ale nigdy mniej niż 300 milisekund. Druga noc miała już być tą właściwą.
Jak automat mógł skasować coś, co sam przed chwilą zrobił?
Druga noc zaczęła się o północy. Poczta przez całą noc odpowiadała w 26 - 34 milisekundy, czyli spokojnie poniżej progu. Strażnik ani razu nie musiał interweniować. Zrzut, kompresja i szyfrowanie 257 GB skończyły się około 7:15. Wtedy ruszył transfer na drugi serwer z prędkością około 60 MB na sekundę. Przy takim rozmiarze to mniej więcej 75 minut.
O 8:00 minęło osiem godzin od startu.
Strażnik zrobił to, co miał w instrukcji: limit przekroczony, przerwij zadanie. Zabezpieczenie, które dopisałem wcześniej, zadziałało: strażnik nie zabił procesu transferu, bo ten nie był zrzutem maszyny. Transfer jednak i tak stanął, bo w następnym kroku strażnik uruchomił procedurę sprzątania po przerwanej kopii. A ta procedura kasowała z katalogu tymczasowego wszystko, co dotyczyło tej maszyny. Łącznie z gotowym, zaszyfrowanym plikiem, który właśnie był w drodze.
Transfer urwał się z błędem. Na drugim serwerze nie było niczego. Na pierwszym też już nie. Alarm w systemie monitoringu zadziałał poprawnie, o 8:02 dostałem powiadomienie, że kopia się nie udała. Tylko że to powiadomienie mówiło prawdę o skutku, a przyczynę napisałem sam.
Szczerze? Byłem wściekły. Nie na automat, bo ten wykonał co do joty to, co mu zapisałem. Na siebie, bo oba błędy były w projekcie od początku, a ja widziałem w nim same zabezpieczenia.
Pierwszy błąd: jeden zegar na całą robotę
Limit ośmiu godzin miał jeden cel: chronić pocztę przed spowolnieniem w godzinach pracy. Rzecz w tym, że pocztę obciążała tylko część procesu - zrzut maszyny. Transfer gotowego pliku na inny serwer w ogóle jej nie dotyka. Kopiuje się plik, który leży obok, a serwer poczty nawet o tym nie wie.
A ja założyłem jeden zegar na całość. To tak, jakby w restauracji obowiązywała zasada „kuchnia pracuje do 22:00, bo potem sąsiedzi skarżą się na hałas wentylatora”, a kelner, który o 22:01 niesie gotowe danie do stolika, musiał je wyrzucić, bo przekroczył godzinę pracy kuchni. Hałas robiła kuchnia. Kelner niczego nie hałasował.
Limit czasu powinien chronić przed konkretnym kosztem, a nie przed tym, że coś po prostu trwa. Kiedy przypinasz go do całego procesu, zaczyna karać kroki, które tego kosztu w ogóle nie powodują. W moim przypadku ukarał krok najtańszy i najbliższy końca.
Dlaczego tego nie zauważyłem przy projektowaniu?
Bo myślałem o kopii jak o jednej czynności. „Kopia trwa około siedmiu godzin, limit osiem, zapas jest.” Tymczasem ta jedna czynność składała się z kroków o zupełnie innym charakterze: jeden obciąża produkcję, drugi obciąża tylko łącze, trzeci zajmuje sam serwer docelowy. Każdy z nich zasługiwał na inną regułę. Zrzut z kompresją i szyfrowaniem zajął ponad siedem godzin, transfer potrzebował jeszcze ponad godziny. Razem wychodziło więcej niż osiem, a ja tego nie policzyłem, bo w głowie miałem jedną kopię, a nie trzy różne kroki. Nigdy nie ustawiaj limitu na wąskim zapasie dla procesu, którego czasu nie zmierzyłeś przynajmniej raz.
Drugi błąd: sprzątanie „wszystkiego”
Sam limit nie zabiłby kopii. Gdyby po przerwaniu gotowy plik został na miejscu, rano wystarczyłoby dokończyć transfer. Kopię zabiło dopiero sprzątanie.
Procedurę sprzątania napisałem z dobrą intencją. Przerwany zrzut maszyny zostawia po sobie niekompletne pliki, często po kilkaset gigabajtów. Jeśli ich nie usuniesz, następna próba może nie zmieścić się na dysku. Więc dopisałem regułę: po przerwaniu usuń z katalogu tymczasowego pliki tej maszyny. Wszystkie.
To słowo „wszystkie” było drugim błędem. Procedura nie odróżniała pliku urwanego w połowie od pliku kompletnego i zweryfikowanego. Dla niej jedno i drugie było „pozostałością po przerwanej kopii”. A to zupełnie różne rzeczy. Pierwszy jest śmieciem. Drugi jest wynikiem ośmiu godzin pracy, któremu brakuje tylko przeprowadzki.
Jak odróżnić śmieci od gotowego towaru?
Pomyśl o przeprowadzce. Kartony, które ktoś zaczął pakować i zostawił w połowie, możesz spokojnie rozpakować z powrotem albo wyrzucić. Kartony zaklejone, opisane i ustawione przy drzwiach to coś innego, z nimi nie robisz nic, dopóki nie dotrą na miejsce. Moja procedura sprzątania traktowała zaklejone kartony jak porzucone, bo nie miała jak ich odróżnić. Nikt na nich nie przykleił etykiety „gotowe”.
I to była dokładnie ta zmiana, którą zrobiłem.
Jak podzieliłem kopię na fazy?
Przebudowa zajęła mi jeden ranek. Nie dopisywałem kolejnych zabezpieczeń do starego projektu, tylko podzieliłem cały proces na trzy fazy, z których każda ma własne reguły.
Faza zrzutu. Tylko ona obciąża serwer poczty, więc tylko ona ma limit ośmiu godzin, twardy koniec o 8:30 i pomiar szybkości poczty co piętnaście minut. Jeśli poczta zwalnia albo kończy się czas, strażnik przerywa wyłącznie proces zrzutu, sprawdzając po identyfikatorze i nazwie, że trafia dokładnie w niego. Sprzątanie usuwa tylko pliki, które nie zostały oznaczone jako gotowe.
Faza transferu. Zaczyna się w chwili, gdy zaszyfrowany plik jest kompletny. Pierwsze, co wtedy robi skrypt, to liczy sumę kontrolną pliku i zapisuje ją w małym pliku obok, znaczniku gotowości. To jest ta etykieta na kartonie. Od tej chwili obowiązują inne zasady: nie ma limitu czasu, bo transfer nikomu nie przeszkadza. Jest za to czujnik bezczynności. Co pół minuty strażnik sprawdza, ile danych dotarło na drugi serwer. Jeśli przez piętnaście minut nie przybyło ani bajta, uznaje to za błąd, zatrzymuje transfer, ale gotowego pliku i znacznika nie rusza.
Faza zakończenia. Na serwerze docelowym liczy się suma kontrolna przesłanego pliku. Jeśli jest identyczna z zapisaną w znaczniku, dopiero wtedy kasuje się plik tymczasowy u źródła.
Co, jeśli transfer się urwie?
Do tego potrzebny był jeszcze jeden element: transfer, który można wznowić. Narzędzie, którego używam do przesyłania plików, potrafi zostawić na celu niedokończony fragment i przy następnym uruchomieniu dosłać tylko brakującą część, sprawdzając przy okazji, czy to, co już dotarło, się zgadza. Tak samo działa porządny menedżer pobierania w przeglądarce: zerwane połączenie nie oznacza zaczynania od zera.
A skrypt na starcie najpierw sprawdza, czy w katalogu tymczasowym leży gotowy plik ze znacznikiem. Jeśli tak, pomija zrzut, kompresję i szyfrowanie, i od razu wraca do transferu. Osiem godzin pracy nie musi być powtarzane tylko dlatego, że ostatnie dwadzieścia minut się nie udało.
Jak to przetestowałem bez czekania na noc?
Nie chciałem się dowiadywać o kolejnym błędzie o ósmej rano. Dopisałem do strażnika dwa tryby próbne. W pierwszym udaje on, że poczta nagle zwolniła w trakcie zrzutu. Sprawdziłem, że przerywa właściwy proces i nie usuwa niczego, co ma znacznik. W drugim podkładam atrapę gotowego pliku ze znacznikiem, symuluję transfer, który rośnie, a potem staje. Sprawdziłem, że po ośmiu sekundach bezczynności (w teście skróciłem czas) strażnik zgłasza błąd, a plik i znacznik zostają na miejscu. Przy okazji test złapał prawdziwy skok czasu odpowiedzi poczty do niecałych dwóch sekund w trakcie fazy transferu. Strażnik go zapisał w logu i niczego nie przerwał, bo w tej fazie poczta już nie decyduje. Dokładnie tak, jak miało być.
Oba testy trwały razem minutę. Kosztowały mnie mniej niż kawa, a dały pewność, której nie dała poprzednia noc.
Czy trzeci przebieg poszedł gładko? Prawie
Trzecia noc ruszyła 2 października o 22:00. Zrzut z szyfrowaniem skończył się o 5:28, transfer o 6:44, wszystko z dużym zapasem przed ósmą. Strażnik przeszedł do ostatniego kroku: liczenia sumy kontrolnej na serwerze docelowym.
I wtedy włączył się czujnik bezczynności.
Liczenie sumy kontrolnej pliku 259 GB trwa. Przez ten czas plik na celu nie rośnie, bo jest już kompletny. Czujnik widział tylko to, że od piętnastu minut nie przybyło ani bajta, i uznał to za zawieszony transfer. Trzeci błąd, tym razem drobniejszy: czujnik bezczynności był dobry dla fazy transferu, ale zabrałem go ze sobą do fazy weryfikacji, w której brak wzrostu jest normalny.
Różnica była jednak zasadnicza. Tym razem nic nie zniknęło. Gotowy plik i znacznik zostały na miejscu, bo nowe reguły nie pozwalały ich ruszyć. Poprawiłem czujnik tak, że po kompletnym transferze obowiązuje osobny limit czterech godzin na weryfikację zamiast pilnowania wzrostu pliku. O 9:29 uruchomiłem wznowienie. Skrypt sam zauważył znacznik, pominął zrzut, dosłał to, czego brakowało (nic), i policzył sumę kontrolną na celu. O 9:50 w logu pojawiło się „transfer zgodny”. Dokładnie 278 041 795 829 bajtów, czyli te 258,9 GB, które pokazuje system. Sprzątanie usunęło pięć plików, każdy wypisany w logu z pełną ścieżką.
Pierwsza pełna kopia serwera poczty na niezależnym serwerze powstała po trzech nocach i jednym wznowieniu.
Co pokazała następna maszyna?
Prawdziwy test projektu przyszedł dwie noce później. Kopia kolejnej maszyny obsługującej pocztę, 256 GB, przeszła przez wszystkie fazy za pierwszym razem, w 7 godzin 54 minuty, bez jednego przerwania i bez zakłóceń dla użytkowników. Gdyby obowiązywały stare zasady, z limitem ośmiu godzin na całość, miałbym sześć minut zapasu. Przy nowych zapas nie ma znaczenia, bo transfer nie ma z czym przegrywać.
Trzecia maszyna, serwer plików z 683 GB danych, nie mieściła się w ogóle w tym schemacie, bo skompresowany i zaszyfrowany plik obok siebie nie zmieściłyby się na dysku. Dla niej zrobiłem wariant strumieniowy, w którym dane płyną od razu na drugi serwer bez pośredniego pliku. Zanim puściłem go na prawdziwą maszynę, przetestowałem całość na małej, testowej: 6,7 GB przesłane w nieco ponad 5 minut, suma kontrolna zgodna, a potem odtworzenie z tej kopii działającej maszyny w 35 sekund.
I tu muszę się przyznać do jeszcze jednej pomyłki. Pierwsze odtworzenie uruchomiłem z pustym kluczem deszyfrującym, bo mój skrypt szukał klucza w niewłaściwym polu w menedżerze haseł. Skończyło się śmieciową maszyną testową, którą trzeba było usunąć. Od tamtej pory każdy potok, który używa sekretu, najpierw sprawdza, czy wyciągnięty klucz nie ma zera bajtów, i jeśli ma, zatrzymuje się, zanim cokolwiek zrobi.
Kiedy wolno skasować oryginał?
Z całej tej historii jedna zasada przeżyła wszystkie poprawki i dziś stosuję ją wszędzie, nie tylko przy kopiach: źródło kasuję dopiero wtedy, gdy mam dowód, że cel jest identyczny. Nie wtedy, gdy narzędzie zakończyło się bez błędu. Nie wtedy, gdy plik na celu ma ten sam rozmiar. Wtedy, gdy suma kontrolna policzona po drugiej stronie zgadza się co do znaku z tą policzoną u źródła.
Suma kontrolna to coś w rodzaju odcisku palca pliku. Z 259 GB danych powstaje krótki ciąg znaków. Zmień w pliku jeden bit, a odcisk wyjdzie zupełnie inny. Porównanie dwóch odcisków trwa ułamek sekundy, choć ich policzenie przy takim rozmiarze zajmuje kilkanaście minut. To niewielka cena za pewność.
Dlaczego nie wystarczy kod wyjścia „zakończono pomyślnie”? Pisałem o tym niedawno w tekście o zadaniu „zakończonym sukcesem”, które niczego nie zrobiło. Narzędzie zgłasza sukces według własnej definicji, a ta definicja rzadko pokrywa się z twoją. Program do przesyłania mówi „skończyłem”, kiedy skończył to, co miał w kolejce. Nie mówi, że to, co jest na celu, da się odczytać.
Tu jest ważne rozróżnienie. Suma kontrolna dowodzi, że kopia dotarła nienaruszona. Nie dowodzi, że da się z niej odtworzyć działający system. To drugie sprawdza się odtworzeniem, i o tym napisałem osobny tekst: backup, którego nikt nie otworzył, nie jest backupem. Dobry proces potrzebuje obu rzeczy. Suma kontrolna przy każdej kopii, bo jest tania i automatyczna. Odtworzenie regularnie, bo jest drogie, ale tylko ono mówi prawdę do końca.
Ta sama zasada poza serwerami
Kiedy przenosiliśmy archiwa z firmowego dysku sieciowego na dysk w chmurze, narzędzie raportowało 100%. Kontrola po rozmiarach plików pokazała ponad 41 tysięcy plików, których po drugiej stronie nie było, bo w trakcie przenosin laptop stracił połączenie z dyskiem, a „100%” dotyczyło tylko tego, co zdążył odczytać. Gdybyśmy po tym raporcie skasowali dysk źródłowy, część archiwum zniknęłaby na zawsze. Nie skasowaliśmy, bo zasada jest ta sama: dowód zgodności najpierw, kasowanie potem.
Gdzie w tym wszystkim jest AI?
Strażnika, przebudowę na fazy, testy próbne i nocne odczyty logów robił u mnie agent AI pracujący na serwerach, pod moim nadzorem. Rano to on czytał log i raportował, co się stało. I tu jest coś, co uważam za ważniejsze od samych kopii.
Agent AI jest jak bardzo pracowity, bardzo dosłowny pracownik. Napisał dokładnie ten strażnik, o który go poprosiłem: z limitem ośmiu godzin i sprzątaniem po przerwaniu. Oba elementy brzmiały w poleceniu rozsądnie i oba wykonał bez zarzutu. Nie zapytał „a co, jeśli limit trafi w transfer gotowego pliku?”. Nie zapytał, bo w poleceniu nie było nic, co by go do tego skłoniło. To nie jest wada AI. Doświadczony administrator też by tego mógł nie zauważyć, gdyby dostał to samo polecenie w tej samej formie.
Mit, z którym spotykam się na rozmowach z klientami, brzmi: „AI napisze nam automatyzację, więc będzie bez błędów”. Napisze bez literówek i bez błędów składni. Błędy projektowe przepisze wiernie, bo to ty je zaprojektowałeś. AI przyspiesza wykonanie, ale za to, co jest nieodwracalne, odpowiadasz dalej ty.
Co realnie się zmieniło po tej historii w pracy z agentem? Każda lekcja z tej przygody trafiła do rejestru reguł, które agent dostaje przy każdym zadaniu infrastrukturalnym. Opisywałem ten mechanizm w tekście o rejestrze luk. Nie zapisuję tam opisu zdarzenia, tylko regułę: „przy przerwaniu procesu kasuj wyłącznie pliki niekompletne, nigdy pliku z potwierdzoną sumą kontrolną”. Opis „kopia się skasowała” nie zmienia zachowania. Reguła zmienia.
Druga zmiana: przy każdej operacji, która coś kasuje, agent musi pokazać listę obiektów i policzyć je, zanim cokolwiek zrobi. Dlatego w logu ostatniego przebiegu było dokładnie pięć plików z pełnymi ścieżkami, a nie „usunięto pliki tymczasowe”.
Jak sprawdzić, czy twój proces kopii nie ma tej samej pułapki?
Nie musisz mieć własnych serwerów, żeby ta historia cię dotyczyła. Jeśli w twojej firmie jakikolwiek automat robi coś długo, a potem po sobie sprząta - kopię, eksport z systemu, nocną synchronizację plików, przenosiny danych - zadaj sobie cztery pytania.
1. Przed czym chroni twój limit czasu? Nazwij konkretny koszt: obciążenie produkcji, okno serwisowe, opłata za transfer. Potem sprawdź, które kroki ten koszt faktycznie powodują. Limit zakładaj tylko na nie. 2. Co dokładnie kasuje twoje sprzątanie? Jeśli odpowiedź brzmi „pliki tymczasowe” albo „wszystko z tego zadania”, masz problem. Sprzątanie musi odróżniać śmieci od gotowego wyniku, a do tego potrzebny jest znacznik gotowości. 3. Czy przerwany transfer da się wznowić? Jeśli każda przerwa oznacza start od zera, to przy dużych danych jedna niestabilna noc kasuje cały wysiłek. Większość porządnych narzędzi do przesyłania potrafi wznawiać, tylko trzeba to włączyć. 4. Kiedy kasujesz źródło? Jedyna dobra odpowiedź: po zgodnej sumie kontrolnej policzonej po drugiej stronie. Ani kod „sukces”, ani rozmiar pliku tego nie zastąpią.
I jedna rada od siebie, za którą zapłaciłem dwiema nocami. Przetestuj na sucho sytuację awaryjną, a nie tylko szczęśliwą ścieżkę. Moje pierwsze testy sprawdzały, czy kopia się robi. Żaden nie sprawdzał, co się stanie, jeśli zostanie przerwana w połowie transferu. A właśnie tam siedział błąd, który kosztował osiem godzin.
Kiedy to przesada
Jeśli twoja firma trzyma wszystko w usługach chmurowych dużych dostawców, a kopie robi za ciebie dostawca, prawdopodobnie nie masz własnego procesu kopii, który mógłby się sam skasować. Wtedy warto tylko raz w roku sprawdzić, czy da się z tej kopii coś odzyskać. Ten tekst jest dla tych, którzy mają choć jeden własny serwer, dysk sieciowy albo skrypt, który w nocy coś przenosi. W małych firmach, z którymi pracuję, to więcej osób, niż się wydaje. Zwykle ktoś to kiedyś ustawił i od tamtej pory nikt nie zaglądał.
Słownik pojęć
- Maszyna wirtualna - komputer działający jako program na fizycznym serwerze. Jej cały dysk to w praktyce jeden duży plik, dlatego można go skopiować i odtworzyć w innym miejscu w całości.
- Zrzut maszyny - kopia całego stanu maszyny wirtualnej w danej chwili, z której da się ją odtworzyć. Robienie zrzutu obciąża serwer, bo czyta cały dysk.
- Suma kontrolna - krótki ciąg znaków wyliczony z zawartości pliku, jego „odcisk palca”. Jeśli dwa pliki mają tę samą sumę kontrolną, są identyczne co do bitu.
- Znacznik gotowości - mały plik obok właściwego pliku kopii, który mówi „ta kopia jest kompletna, nie kasować”. Zawiera jej sumę kontrolną.
- Transfer wznawialny - przesyłanie pliku, które po przerwaniu kontynuuje od miejsca, w którym stanęło, zamiast zaczynać od zera.
- Czujnik bezczynności - reguła, która zgłasza błąd, gdy przez określony czas proces nie robi postępu, zamiast przerywać go po sztywnym limicie czasu.
Zanim twój automat zacznie po sobie sprzątać
Jeśli masz w firmie nocne kopie, synchronizacje albo przenosiny danych i nie wiesz, co dokładnie robią, kiedy coś pójdzie nie tak, napisz do mnie na [email protected]. Zwykle wystarczy jedna rozmowa i przejrzenie jednego skryptu, żeby zobaczyć, czy ktoś kiedyś nie wpisał tam słowa „wszystko” w miejscu, w którym powinno stać „tylko to, co niedokończone”.
własnej firmie cybulski.ai JC


