7 posts 31 tags 7 domains

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:

text
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:

bash
$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):

bash
$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:

bash
$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.

bash
$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ć:

text
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):

bash
$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:

text
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:

bash
$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ć:

text
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:

bash
$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.