Memory leak w Home Assistant — diagnoza przez SSH
Jak zidentyfikować co zjada RAM w HA: recorder, custom_components, czy bug w core. Krok po kroku z safe mode, wyłączaniem integracji i downgrade.
Problem
Home Assistant po kilku dniach albo po aktualizacji zaczyna zżerać coraz więcej pamięci. W końcu RAM jest pełen, system wpada w swap thrashing (kernel rzuca stronami z RAM na dysk i z powrotem), load skacze do kilkunastu, CPU pokazuje 100% mimo że żadna automatyzacja nic szczególnego nie robi, UI przestaje odpowiadać. Klasyczny memory leak.
Ten wpis to przewodnik diagnostyczny — od potwierdzenia że to faktycznie leak, przez identyfikację winowajcy, po naprawę. Wszystko z linii poleceń przez SSH (jak skonfigurować dostęp — tu).
Potwierdzenie diagnozy
Najpierw sprawdź czy to faktycznie leak, czy może po prostu duża instalacja.
$free -h
Patrz na pola:
total used free shared buff/cache available
Mem: 7.8Gi 6.5Gi 42Mi 148Ki 115Mi 1.2Gi
Swap: 2.6Gi 2.6Gi 164Ki
usedbliskototal+availableponiżej 500 MB = za mało RAMSwap usedbliskoSwap total= swap też zapchany → wszystko czeka na IOfreew okolicach kilku-kilkudziesięciu MB = system w agonii
Sprawdź który kontener żre:
$docker stats --no-stream
Jeśli homeassistant siedzi na kilku GB (przy Pi 4 8GB normalna wartość to 400-800 MB, przy mniejszych instalacjach jeszcze mniej) — masz leak.
Czy to wzrost ciągły, czy plateau
Restart kontenera i obserwuj jak rośnie:
$docker restart homeassistant$sleep 60 && docker stats --no-stream homeassistant$sleep 300 && docker stats --no-stream homeassistant$sleep 600 && docker stats --no-stream homeassistant
Trzy snapshoty: po 1, 6, 16 minutach od startu. Interpretacja:
- Liniowy wzrost (np. +50 MB/min) → leak
- Wzrost do 1-2 GB i stabilizacja → po prostu duża instalacja, nie leak
- Skoki nieregularne → coś okresowo alokuje (mogą być nawet eventy raz na minutę)
Ranking podejrzanych
W kolejności od najczęstszych w realnej praktyce:
- Custom integracje z HACS — szczególnie te trzymające połączenia z urządzeniami (Sonoff LAN, Xiaomi Mi, Tuya, Bluetooth) albo cache stanów
- Recorder z napuchniętą bazą SQLite — sama baza w pliku to nie problem, ale cache i query potrafią zjeść RAM
- Oficjalne integracje chmurowe z błędem retry (HACS, ESPHome, Matter, MQTT przy lawinie wiadomości)
- Bug w HA Core po świeżej aktualizacji — rzadziej, ale się zdarza
- Frontend WebSocket leaks — gdy UI jest często odświeżane, każde otwarcie zostawia state
Idziemy od najprawdopodobniejszego.
Krok 1 — sprawdź custom_components
To pierwszy strzał. Zobacz co masz:
$ls /config/custom_components/
Każdy katalog to osobna integracja. Im więcej tych, które trzymają stałe połączenia (urządzenia LAN, cloud API, BLE), tym większe szanse na leaka.
Wyłącz wszystkie naraz przez zmianę nazwy katalogu (HA przy starcie nie znajdzie i zignoruje):
$docker stop homeassistant$mv /config/custom_components /config/custom_components.DISABLED$docker start homeassistant$sleep 60 && docker stats --no-stream homeassistant$sleep 600 && docker stats --no-stream homeassistant
Jeśli RAM teraz stabilny przy 400-800 MB → leak był w custom_components. Identyfikujemy winowajcę po jednym.
Bisekcja — który custom_component leakuje
Wróć katalog na miejsce i wyłączaj integracje pojedynczo:
$docker stop homeassistant$mv /config/custom_components.DISABLED /config/custom_components$mv /config/custom_components/sonoff /config/custom_components/sonoff.DISABLED$docker start homeassistant
Czekaj 10-15 minut, obserwuj docker stats. Jeśli RAM stabilny → sonoff był winowajcą. Jeśli dalej rośnie → włącz sonoff, wyłącz następną:
$mv /config/custom_components/sonoff.DISABLED /config/custom_components/sonoff$mv /config/custom_components/inna_integracja /config/custom_components/inna_integracja.DISABLED$docker restart homeassistant
Powtarzaj aż znajdziesz.
Co zrobić, gdy znajdziesz winowajcę
Sprawdź wersję integracji:
$cat /config/custom_components/<nazwa>/manifest.json | grep version
Idź na repo GitHub integracji (jest w manifeście w polu documentation albo issue_tracker) → Issues → wpisz "memory leak". Często znajdziesz świeże zgłoszenia.
Naprawa:
- Update do najnowszej (jeśli leak był w starej wersji, mogą już mieć fix) — przez HACS w UI albo ręcznie z GitHuba
- Downgrade do poprzedniej (jeśli leak pojawił się w nowej wersji) — w HACS są opcje wyboru wersji, albo ręcznie:
$docker stop homeassistant$rm -rf /config/custom_components/<nazwa>$cd /tmp$wget https://github.com/<autor>/<repo>/archive/refs/tags/vX.Y.Z.tar.gz$tar xzf vX.Y.Z.tar.gz$cp -r <repo>-X.Y.Z/custom_components/<nazwa> /config/custom_components/$docker start homeassistant
- Wyłącz na stałe, jeśli integracja nie jest krytyczna a alternatywy są — zostaw przemianowaną z
.DISABLED
Krok 2 — recorder i baza SQLite
Jeśli custom_components nie były winowajcą, sprawdź recordera.
$ls -lh /config/home-assistant_v2.db*
Trzy pliki: główna baza, WAL (Write-Ahead Log) i SHM (shared memory). Rozmiary do interpretacji:
- <200 MB — wszystko OK, nie kombinuj
- 200 MB – 1 GB — duża instalacja albo długa historia, mogłaby być przyczyną przy słabym Pi z mało RAM, ale na 8GB raczej nie sama z siebie
- 1-3 GB — za dużo, prawie na pewno wpływa na RAM, czas na purge
- >3 GB — patologia, baza nie była czyszczona, ratunek przez nuke
Pierwsza pomoc — VACUUM bazy
VACUUM w SQLite kompaktuje bazę i odbudowuje strukturę. Może zwolnić znaczne miejsce i czasem rozwiązuje problemy z cache.
$df -h /config
Sprawdź wolne miejsce — VACUUM wymaga 2x rozmiar bazy wolnego. Jeśli masz, odpalaj:
$docker stop homeassistant$docker run --rm -v /mnt/data/supervisor/homeassistant:/config alpine \sh -c "apk add sqlite && sqlite3 /config/home-assistant_v2.db 'VACUUM;'"$docker start homeassistant
(Ścieżka woluminu może się różnić — sprawdź docker inspect homeassistant | grep -A2 Mounts.)
Po VACUUM zobacz nowy rozmiar:
$ls -lh /config/home-assistant_v2.db
Test — wymuś bazę w RAM
Najpewniejszy test czy to recorder leakuje: tymczasowo wymuś bazę w pamięci. Edytuj /config/configuration.yaml:
$docker stop homeassistant$cp /config/configuration.yaml /config/configuration.yaml.bak$cat >> /config/configuration.yaml << 'EOF'recorder:db_url: "sqlite:///:memory:"purge_keep_days: 1EOF$docker start homeassistant
Czekaj 15-30 minut, obserwuj RAM. Jeśli stabilny → recorder z dyskową bazą był winowajcą.
Naprawa — wyczyść bazę
Jeśli recorder potwierdzony jako winowajca:
$docker stop homeassistant$mv /config/home-assistant_v2.db /config/home-assistant_v2.db.OLD$rm -f /config/home-assistant_v2.db-shm /config/home-assistant_v2.db-wal
Przywróć normalny config recordera w configuration.yaml:
recorder:
purge_keep_days: 7
commit_interval: 30
exclude:
domains:
- automation
- updater
entity_globs:
- sensor.weather_*
purge_keep_days: 7 zamiast domyślnego 10 oszczędza miejsce. exclude wyrzuca encje, których historia ci nie potrzebna — najbardziej spamują sensor.* aktualizujące się co kilka sekund.
$docker start homeassistant
HA odtworzy pustą bazę. Stracisz historię, ale system będzie działał.
Krok 3 — safe mode HA
Jeśli ani custom_components, ani recorder nie były winowajcami, podejrzewamy oficjalną integrację lub bug w core. Safe mode wyłącza wszystkie integracje custom i ładuje minimalny zestaw oficjalnych — to środowisko testowe.
Aktywujesz przez wymuszony double-crash — Supervisor po dwóch nieudanych startach przełącza HA w safe mode:
$docker stop homeassistant$docker stop homeassistant$docker start homeassistant
W UI zobaczysz baner "Home Assistant is running in safe mode". Większość integracji wyłączona.
Sprawdź RAM:
$sleep 600 && docker stats --no-stream homeassistant
Jeśli stabilny → leak był w którejś integracji oficjalnej. Wchodź na UI, w Settings → Devices & Services wyłączaj po jednej (Disable), restart HA normalnie:
$docker restart homeassistant
I obserwuj który wyłącznik zatrzymał wzrost.
Jeśli w safe mode dalej leakuje → bug w HA Core. Idziemy w downgrade.
Krok 4 — downgrade HA Core
Skoro leak siedzi w samym core, najszybsza droga to cofnięcie wersji. Zobacz historię aktualizacji:
$ha supervisor logs 2>&1 | grep -iE "core.*update|home assistant.*[0-9]+\.[0-9]+" | tail -20
Sprawdź też aktualną wersję:
$ha core info | grep version
Cofnij na poprzednią:
$ha core update --version 2026.4.5
Podstaw wersję sprzed update (sprawdź na github.com/home-assistant/core/releases). Supervisor pobierze starszy obraz Docker, restart zajmie 5-10 minut. Po starcie kolejny test RAM.
Jeśli teraz stabilnie — masz potwierdzenie bugu w nowej wersji. Trzymaj się starszej dopóki nie wyjdzie fix.
Profilowanie — gdy podstawowe metody zawodzą
Jeśli żaden z powyższych kroków nie ujawnił winowajcy, czas na narzędzia profilujące.
py-spy
Standardowe narzędzie, ale uwaga — py-spy 0.4.x nie wspiera Pythona 3.14, którego używa nowy HA. Jeśli masz HA na starszej wersji Python (3.12, 3.13), py-spy działa świetnie:
$docker exec homeassistant pip install py-spy --break-system-packages$docker exec homeassistant apk add libunwind$docker exec homeassistant py-spy top --pid 1
Jeśli Failed to find python version from target process — masz Python 3.14, py-spy odpada. Idź dalej.
Wbudowany profiler HA
W UI: Developer Tools → Services → profiler.memory (zrzut zużycia pamięci) albo profiler.start (CPU). Trzeba mieć dostęp do UI — przy load 12 to mało wykonalne, ale gdy po którymś z powyższych kroków UI ruszy, użyj.
Wynik trafia do /config/ jako plik .cprof (CPU) albo .mprof (memory). Analizujesz lokalnie przez snakeviz albo memory_profiler.
tracemalloc przez API
HA ma wbudowany tracemalloc, który możesz włączyć w trakcie działania. Wymaga tokenu Long-Lived Access Token (z UI: profil → Security → Create Token).
$TOKEN="twoj_token"$curl -X POST http://localhost:8123/api/services/profiler/memory \-H "Authorization: Bearer $TOKEN" \-H "Content-Type: application/json" \-d '{"seconds": 60}'
Po 60 sekundach plik .mprof w /config/. Pokaże top alokujące moduły.
Prewencja
Po naprawie:
- Włącz purge w recorderze —
purge_keep_days: 7-14to rozsądna wartość dla domowego HA - Wyklucz spamujące encje z recordera (
excludew configu) - Wyłącz auto-update HA Core i HACS integracji — patrz wcześniejszy wpis o backupie. Aktualizuj ręcznie kilka dni po release, sprawdzając forum i issues najpierw
- Regularny ręczny backup — zwłaszcza zanim zaktualizujesz HA. Z hasłem zapisanym w managerze (przypominam — hasła z
default_backup_passwordw/config/.storage/backupmożna też wyciągnąć po fakcie, jeśli zapomniałeś)
Co się dzieje pod spodem
Memory leak w Pythonie to typowo cykl referencyjny, którego garbage collector nie potrafi rozplątać, albo struktura, do której coś dorzuca dane i nigdy nie usuwa. W HA klasyczne wzorce:
- Listener eventów dodawany przy każdej aktualizacji entity, nigdy nie usuwany
- Cache stanów w integracji, rosnący z każdą wiadomością MQTT
- Sesja HTTP/WebSocket trzymająca bufor odpowiedzi po zamknięciu
tracemalloc umie te pokazać, ale wymaga porównania snapshotów z dłuższego okresu — stąd profiler HA daje tylko surowy materiał, interpretacja jest manualna.
Recorder leakuje inaczej — to SQLite cache, który przy bardzo dużych bazach (lub bardzo częstych zapisach) potrafi rosnąć w tle. Na HA OS z kartą SD problem jest podwójny: dyskowy IO jest wolny, więc każdy commit trzyma pamięć dłużej. Stąd commit_interval: 30 w configu zamiast domyślnego 1 — komitowanie raz na 30 sekund redukuje pressure.