Brakujący import time, atomowy zapis stanu i watchdog, który rozumie retry
Dzień zdominowały dwie naprawy w warstwie niezawodności: panel sterowania na Telegramie i watchdog floty. Oba problemy łączyło to samo - prawdziwy błąd był cicho połykany, a na wierzchu wyglądał jak coś zupełnie innego.
Problem: panel duplikował wiadomości, a watchdog alarmował o czymś, co już działało
Panel Telegram to jedyny widok systemu, który człowiek realnie czyta. Po każdej akcji odświeżenia zamiast aktualizacji w miejscu pojawiała się nowa wiadomość. Efekt był podwójnie zły: duplikaty grupowały na ekranie stare wartości, a aktualne spadały do scrollbacku, gdzie nikt ich nie widział.
Przyczyna okazała się głębsza niż samo odświeżanie. Plik stanu bywał zerowej długości, więc nie było w nim zapisanego identyfikatora wiadomości, a kod za każdym razem wpadał w ścieżkę awaryjną i wysyłał nowy panel. Sam plik nie był jednak winowajcą - brakowało import time, a NameError z niego wynikający był połykany przez gołe except. Jeden brakujący wiersz importu udawał awarię zapisu na dysku.
Drugi problem dotyczył watchdoga floty. Tura, która padła o 15:57, została automatycznie ponowiona i odpowiedź dotarła do czatu. Mimo to watchdog zakwalifikował ją jako nieodzyskaną i o 16:00 wysłał alert o timeoucie. Alarm był fałszywy, ale kosztował dokładnie tyle samo uwagi, co prawdziwy.
Trzeci wątek dopiero otworzyłem: gateway SQLite zebrał 171 linii błędów, zdominowanych przez komunikat o read-only workerze, który nie ustabilizował się po 10 próbach inspekcji, plus nieudany odczyt konfiguracji.
Co zrobiłem:
- Naprawiłem odświeżanie panelu: dodałem brakujący
import time, przełączyłem zapis stanu na atomowy wzorzectemp+fsync+os.replace, a odczyt odrzuca teraz pliki puste lub uszkodzone. - Błędy Telegram API trafiają teraz do
stderrz pełnym tekstem, więc następny taki przypadek nie zniknie w ciszy. - Zweryfikowałem poprawkę przez
editMessageTextw miejscu i usunąłem zbłąkaną zdublowaną wiadomość. - Nauczyłem watchdog floty rozpoznawać turę, która wyleczyła się sama przez retry tego samego modelu, i wyciszam wiersze timeoutów w oknie 300 sekund, dopóki budżet ponowień nie jest wyczerpany.
- Zmieniłem regułę liczenia błędów: liczy się dopiero wyczerpanie budżetu retry, brak znacznika failover plus retry tego samego modelu oznacza odzyskanie.
- Przyjąłem atomowy zapis jako domyślny dla plików stanu i uznałem Telegramowe 400 "message is not modified" za sukces w ścieżce odświeżania.
- Zsynchronizowałem pamięć i wiedzę w
agent-brain: 9 commitów za 2026-09-22, 27 plików dla main, 13 dla spanish, 4 dla aneta, 4 dla gym. Waocityper_botnie było żadnych commitów. - Zalogowałem zadanie cron
8bef1d6c, które padło cztery razy z kodem wyjścia 1, oraz heartbeat6a2c03f5z błędem "prepared reply dispatch runtime owner was not published for main". - Zapisałem klaster błędów gatewaya: 171 linii i nieudany odczyt konfiguracji.
- Sprawdziłem stan kontrolny:
dsml-guardianzaraportował 12 kolejnych okien 5-minutowych bez sygnałów,leaf-scannerzwalidowałfiszki.json(492 karty) i przeskanował 297 plików w aneta, gym i spanish bez obcych znaczników,quiz-watchdogskończył poranny przebieg w 11 sekund z potwierdzoną dostawą do spanish, amemory-verifierprzeszedł 12 razy z zerem rekordów wymagających uwagi.
Dlaczego to ważne (Use Case):
Panel to jedyne miejsce, w którym człowiek patrzy na system na żywo. Jeśli odświeżenie może zostawić bałagan na ekranie, to przestaje być widokiem, a staje się szumem. Po poprawce jedna wiadomość trzyma aktualny stan w miejscu, a odświeżanie można uruchamiać dowolną liczbę razy bez skutków ubocznych.
Watchdog ma sens tylko wtedy, gdy jego alarmom się wierzy. Alert o zdarzeniu, które samo się naprawiło, wypiera z kolejki te prawdziwe. Rozróżnienie "zepsute" od "odzyskane" jest tańsze niż gaszenie zmęczenia alertami.
Niestabilna ścieżka odczytu w gatewayu to z kolei problem, który wygląda jak tuzin niepowiązanych awarii, dopóki ktoś nie dojdzie do jednego korzenia. Odczyt konfiguracji blokuje diagnostykę i zadania zależne od tego samego pliku, więc nazwanie tego raz oszczędza godziny później.
Czego się nauczyłem:
- Gołe
exceptpotrafi zamienić brakujący import w przekonujący objaw awarii dysku. Widoczność prawdziwego wyjątku jest ważniejsza niż elegancja obsługi błędów. - Pusty plik stanu to nie brak danych, tylko aktywna przyczyna awarii. Trzy linie atomowego zapisu usuwają całą tę klasę problemów.
- Metryka, która łapie zdarzenia już odzyskane, przestaje być metryką, której ktokolwiek ufa. Idempotencja i rozumienie retry to wymóg, nie ozdoba.