Home Assistant na 1000% CPU — diagnostyka i naprawa uszkodzonej bazy Zigbee
Jak namierzyć przeciążenie HA przez SSH i naprawić skorumpowaną zigbee.db bez utraty urządzeń.
Problem
Home Assistant na Raspberry Pi nagle wszedł w stan, w którym top na hoście pokazywał load average ~29 przy us 97% i obciążeniu rzędu 1000% CPU (równowartość 10 rdzeni). UI nie odpowiadało, ha core stats zwracał timeout od Supervisora, plik home-assistant.log nie istniał, a home-assistant.log.fault był pusty.
Po twardym resecie HA wstał, ale w logach pojawiał się powtarzający wzorzec:
ERROR (MainThread) [zigpy.appdb] Zigbee database is corrupted, integrity check failed!
ERROR (MainThread) [zigpy.appdb] Zigbee database is corrupted, foreign key check failed!
WARNING (Recorder) [homeassistant.components.recorder.util] The system could not validate that the sqlite3 database at //config/home-assistant_v2.db was shutdown cleanly
Diagnoza: uszkodzony zigbee.db. Zigpy przy starcie wykrywał niespójność, wchodził w pętlę recovery i zarzynał CPU.
Diagnostyka przez SSH
Standardowy dodatek Terminal & SSH ma ograniczony dostęp — brak docker, htop, brak widoku procesów hosta. Ale ha CLI i top z perspektywy kontenera dodatku wystarczają, jeśli wiesz czego szukać.
Sprawdzenie ogólnego stanu:
$ha core stats$ha supervisor stats$ha supervisor logs | tail -n 80
Gdy ha core stats zwraca context deadline exceeded — HA jest tak zajęty, że nie odpowiada Supervisorowi. To mocny sygnał, że problem jest w samym Core, nie w add-onach.
Logi HA znajdują się pod /homeassistant/ (nie /config/ — to jest katalog konfiguracyjny dodatku SSH):
$tail -n 200 /homeassistant/home-assistant.log$ls -lah /homeassistant/home-assistant.log*
Plik home-assistant.log.fault powstaje przy crashu. Pusty .fault oznacza, że HA został zabity tak twardo (np. przez OOM killer), że nie zdążył nic zapisać.
Stan bazy recordera:
$ls -lah /homeassistant/home-assistant_v2.db*
Aktywny plik .db-wal rosnący co kilka minut wskazuje, że recorder pracuje intensywnie — ale nie zawsze to jest źródło problemu.
Pierwsze rozwiązanie — odbudowa bazy z koordynatora
Najszybsza naprawa, gdy zigbee.db jest uszkodzona, to skasować bazę i pozwolić ZHA odbudować ją z koordynatora Zigbee.
$ha core stop$cd /homeassistant$mv zigbee.db zigbee.db.broken$ha core start
ZHA po starcie zapyta koordynator o listę urządzeń. Urządzenia zostają w sieci Zigbee (nie trzeba parować od nowa), ale w HA pojawiają się jako nowe — bez nazw, pokojów i przypisań do automatyzacji.
Bateryjne urządzenia (czujniki, piloty) odezwą się dopiero przy następnej interakcji albo cyklicznym raportowaniu — pełna sieć wraca w ciągu kilku godzin. W logach przez ten czas widać:
WARNING [zigpy.application] Unknown device AddrModeAddress(addr_mode=<AddrMode.NWK: 2>, address=0x5C5D)
ERROR [zha/helpers.py] AssertionError (assert ieee in self.device_proxies)
To normalne objawy odbudowy — ZHA dostaje pakiety od urządzeń, których jeszcze nie zna, i je dopisuje.
Sprawdzenie integralności SQLite
Domyślnie nie ma sqlite3 w Terminal & SSH. Można doinstalować na czas diagnostyki (Alpine):
$apk add sqlite$sqlite3 /homeassistant/zigbee.db "PRAGMA integrity_check; PRAGMA foreign_key_check;"
Zdrowa baza zwraca ok i pusty wynik foreign_key_check. Uszkodzona pokazuje coś takiego:
wrong # of entries in index devices_idx_v13
row 24 missing from index devices_idx_v13
endpoints_v13|56|devices_v13|0
attributes_cache_v13|249|devices_v13|0
Naprawa przez dump/reload
Jeśli baza ma realne uszkodzenia (sierotki w foreign keys, niespójne indeksy), naprawa polega na wyeksportowaniu danych do SQL i zaimportowaniu do świeżej bazy:
$ha core stop#Sprawdź że WAL/SHM zniknęły lub są małe$ls -lah /homeassistant/zigbee.db*$cd /homeassistant$cp zigbee.db zigbee.db.beforefix#Eksport do SQL$sqlite3 zigbee.db ".dump" > /tmp/zigbee.sql$ls -lah /tmp/zigbee.sql#Usuń starą bazę i pliki pomocnicze$rm zigbee.db$rm zigbee.db-wal 2>/dev/null$rm zigbee.db-shm 2>/dev/null#Import do świeżej bazy$sqlite3 zigbee.db < /tmp/zigbee.sql#Weryfikacja$sqlite3 zigbee.db "PRAGMA integrity_check; PRAGMA foreign_key_check;"
Pułapka — zigpy nie wstanie po dump/reload
Po imporcie zigpy potrafi rzucić:
zigpy.exceptions.CorruptDatabase: The `zigbee.db` database version (0) does not match its max table version (15). The database is inconsistent.
Dlatego, że .dump w SQLite nie eksportuje wartości PRAGMA user_version. Świeża baza ma user_version = 0, a struktura tabel jest na poziomie v15 — zigpy widzi to jako niespójność.
Naprawa to ustawienie wartości ręcznie:
$ha core stop$sqlite3 /homeassistant/zigbee.db "PRAGMA user_version = 15;"$sqlite3 /homeassistant/zigbee.db "PRAGMA user_version;"15$ha core start
Co się dzieje pod spodem
Trzy rzeczy warte zapamiętania:
WAL maskuje stan bazy. Pliki .db-wal i .db-shm to mechanizm Write-Ahead Logging w SQLite. Transakcje trafiają tam najpierw, a do głównego .db są scalane przy checkpointach albo zamknięciu. Sprawdzanie integralności na otwartej bazie jest niewiarygodne.
Dump/reload nie kopiuje wszystkiego. PRAGMA user_version to metadane przechowywane w nagłówku pliku, nie w tabelach. .dump eksportuje schemat i dane tabel — pragmy trzeba ustawić ręcznie po imporcie. To samo dotyczy application_id, journal_mode i kilku innych.
Pusty .fault + brak home-assistant.log = twarde zabicie procesu. Nie zapisał nic, bo dostał SIGKILL zanim zdążył flushować bufory. Najczęstsza przyczyna to OOM killer albo nieczyste odcięcie zasilania. Jeśli to się powtarza, sprawdź ha host logs | grep -iE "mmc|i/o error|ext4-fs" — uszkodzona karta SD w Rpi to klasyczny sprawca.