PABLO.ENGINEERING
// WRÓĆ DO WSZYSTKICH WPISÓW
// 4 min

Zatkany tmpfs, backup bez końca i osobny błąd watchdoga

#daily#agents#aneta#eval#ops

Dzisiejszy dzień to głównie porządki w jednym zasobie: skończyło się miejsce na dysku roboczym, a kilka funkcji zachowywało się tak, jakby psuły się niezależnie od siebie. Drugi wątek to watchdog, który padał z powodu, którego z brakiem miejsca nie łączę.

Problem: brak miejsca uderza w funkcje, które wyglądają na niepowiązane

tmpfs to system plików trzymany w pamięci RAM, więc jest mały i ulotny - po restarcie jego zawartość znika. Pełnił u mnie rolę scratch disku, czyli miejsca na pliki tymczasowe, których nie potrzebuję na stałe. Problem w tym, że gdy taki dysk się zapełni, padają rzeczy, które z zapisem plików na pierwszy rzut oka nie mają nic wspólnego.

Gdy tmpfs 4 GB był zajęty w 98%, wtyczka nie wczytała się (16 błędów ładowania), 9 tur heartbeat nie przeszło, a w logach nazbierało się około 260 błędów gatewaya; tury kończyły się, zanim zdążyły odpowiedzieć. Ten sam zajęty tmpfs przewrócił nocny backup o 03:30: kopia padła w połowie kopiowania (rc=1, bez wpisu w state database), a watchdog o 04:00 poprawnie zgłosił brak backupu.

Osobna historia to fleet_watchdog.py, który padł 7 razy z TypeError: cannot unpack non-iterable bool object. Przyczyną był helper wysyłający, który zwracał samotny False, gdy brakowało poświadczenia do komunikatora. Poświadczenia zabrakło, bo plik env gatewaya miał 0 bajtów od 00:21. Dostarczanie alertów było martwe około 30 minut, a kontrole po pierwszej już się nie uruchamiały. Tego crashu nie wiążę z zapełnionym dyskiem - to odrębny błąd na ścieżce obsługi błędów.

Co zrobiłem:

  • Przeniosłem do kosza katalog model-catalog z 41 zagnieżdżonymi folderami buildów po około 341 MB każdy, czyli 2,1 GB danych, i zszedłem z ~98% do ~6% zajętości (235 MB z 3,8 GB).
  • Zrestartowałem gateway o 09:46. Od 09:49 nie ma błędów braku miejsca ani nieudanych tur.
  • Zmieniłem TEMP_DIR w skrypcie backupu z /tmp na katalog scratch obok celu kopii, a potem sprawdziłem składnię i uruchomiłem pełny ręczny przebieg: rc=0, archiwum 49 MB, wszystkie sprawdzenia integralności bazy ok.
  • Zdiagnozowałem crash watchdoga: 7 wystąpień błędu, źródło w helperze zwracającym False tam, gdzie wywołujący oczekiwali pary wartości, przy braku poświadczenia i pliku env o rozmiarze 0 bajtów.
  • Zanotowałem 9 zmian w repo agent-brain: main (29 plików), spanish (14), aneta (5), gym (4).
  • Uruchomiłem leaf scanner o 04:30 na 98 + 87 + 93 plikach trzech agentów - brak obcych markerów, a magazyn fiszek to poprawny JSON z 492 kartami. Doszło 12 czystych okien guardiana, 12 przejść memory-verifier (0 zaległych rekordów) i 3-sekundowy quiz w dniu bez słów do powtórki.
  • Zapisałem regułę, że katalog scratch skryptu musi leżeć na tym samym systemie plików co jego wynik, i zachowałem kopię oryginału jako *.bak-20260920.

Dlaczego to ważne (Use Case):

Wspólny zasób, jakim jest miejsce na dysku, jest ryzykiem dostępności dla wszystkiego na hoście. W praktyce oznacza to, że jedna zatkana partycja potrafi wyłączyć wtyczkę, wyzerować tury heartbeat i przewrócić backup, a objawy wyglądają na trzy niezależne awarie. Po odblokowaniu miejsca problemy zniknęły bez zmian w kodzie.

Backup ma sens tylko wtedy, gdy dobiega końca i da się go odczytać. Kopia, która pada w połowie i nie zostawia wpisu w state database, jest obciążeniem, nie siatką bezpieczeństwa. Po przeniesieniu katalogu tymczasowego archiwum i każdy plik bazy przechodzą sprawdzenia integralności.

Osobno: monitoringu, który pada na własnej ścieżce błędu, nie da się traktować jak monitoringu. Przez pół godziny flota wyglądała na zdrową, choć alerty nie wychodziły.

Czego się nauczyłem:

  • Skutki pełnego scratch disku są rozproszone i myląco wyglądają na osobne awarie, więc przy diagnozie warto najpierw sprawdzić wspólne zasoby hosta.
  • Brak poświadczenia powinien degradować alert do stanu "nie wysłano", nigdy do crasha procesu, który pilnuje reszty systemu.
  • Katalog tymczasowy na tym samym systemie plików co wynik usuwa całą klasę awarii typu "wczoraj działało", bo mały, ulotny ramdisk zapełnia się pierwszy.