🏠

Home Assistant Recorderの壊れたSQLiteを修復してPostgreSQLへ移行した
July 27, 2026August 08, 2026
#Home Assistant#Develop

Home Assistant の Energy Dashboard が開けなくなった。Recorder のログにはdatabase disk image is malformed。約 802 MB あった SQLite は破損ファイルとして退避され、Home Assistant は約 8.3 MB の新しい DB で記録を再開していた。

調べると、主な問題はix_states_attributes_idのインデックス不整合だった。REINDEX後はPRAGMA integrity_checkokとなり、過去データを救出できた。

これで SQLite へ戻すこともできたが、破損を経験したのを機に、既に Zabbix 用に運用していた PostgreSQL サーバーへ Recorder を移した。ところが pgloader でコピーするだけでは終わらず、psycopg2不足、Sequence 欠落、障害後の新しい SQLite に残った統計のマージ、statistics.sumの不連続へ順番に対応することになった。

最終的に Recorder は PostgreSQL で動き、Energy Dashboard も実際の値とほぼ合うところまで戻った。ただし、SQLite が壊れた根本原因は分かっていない。

データベースを直接操作する場合は Home Assistant を停止し、必ず元ファイルと PostgreSQL のバックアップを取得してほしい。

環境

  • インストール方法: Home Assistant Core
  • Core: 2026.6.1
  • フロントエンド: 20260527.4
  • Home Assistant Core の実行環境: Incus コンテナ
  • Home Assistant の Python 環境: /srv/homeassistant/
  • Python バージョン: 3.14
  • 移行前の Recorder DB: SQLite home-assistant_v2.db
  • 移行後の Recorder DB: PostgreSQL
  • PostgreSQL: 同じ Incus ホスト上の別コンテナで稼働する既存の Zabbix 用サーバーを流用
  • SQLite から PostgreSQL への取り込み: pgloader
  • 障害後の統計マージ: Python
  • タイムゾーン: Asia/Tokyo

SQLite の破損を確認する

Recorder のログには次のエラーが出ていた。

Unrecoverable sqlite3 database corruption detected
sqlite3.DatabaseError: database disk image is malformed

エラーはpurge_old_dataUPDATE states付近で発生していた。Home Assistant は破損を検出すると、元の DB を次の名前へ退避した。

home-assistant_v2.db.corrupt.<timestamp>

その時点のファイルサイズは次のとおりだった。

home-assistant_v2.db                                  8.3M
home-assistant_v2.db.corrupt.<timestamp>             802M
home-assistant_v2.db-wal                              4.3M
home-assistant_v2.db-shm                               32K

約 802 MB の旧 DB が退避され、Home Assistant は約 8.3 MB の新しい SQLite を作って運用を再開していた。このため、履歴と Energy Dashboard から過去データが消えたように見えていた。

SQLite の破損原因を確認するため、退避された DB に対して整合性チェックを実行した。

PRAGMA integrity_check;

結果には次のエラーが含まれていた。

row xxxx missing from index ix_states_attributes_id
wrong # of entries in index

database disk image is malformedというログから、当初は DB 全体が救出できない可能性も考えた。しかし確認できた主な問題はix_states_attributes_idのインデックス不整合だった。

REINDEX で旧 SQLite を救出する

壊れていたインデックスを再構築し、もう一度整合性を確認した。

REINDEX ix_states_attributes_id;
PRAGMA integrity_check;

結果は次のとおりだった。

ok

旧 SQLite は整合性チェックが通る状態まで修復できた。「SQLite が完全に破損して過去データを捨てる」という最悪の事態は回避できた。

SQLite の破損そのものの根本原因は分かっていないため、原因を断定することはできなかった。

SQLite から PostgreSQL へ移行する

SQLite は修復できたが、今回の破損を契機に今後の Recorder は PostgreSQL で運用することにした。同じ Incus ホスト上の別コンテナで既に運用していた Zabbix 用 PostgreSQL サーバーへ Home Assistant 用の DB を作成し、修復済み SQLite のデータを取り込む方針とした。

修復済みの旧SQLite
        ↓
PostgreSQLへ取り込み
        ↓
障害後に新SQLiteへ記録された統計を追加マージ
        ↓
RecorderをPostgreSQLで運用

同時アクセスや大容量 DB への対応、WAL、VACUUM、バックアップの扱いやすさに加え、既存の PostgreSQL サーバーを流用できることも変更理由だった。

PostgreSQL は WAL を使ってクラッシュ後に未反映の変更を再実行できる。一方、SQLite も ACID トランザクションとクラッシュ耐性を備えており、Home Assistant 公式では SQLite が既定かつ推奨で、最もテストされている。したがって、PostgreSQL の方が常に堅牢だとは断定できない。

自分の環境では、既に運用・バックアップしている PostgreSQL サーバーを流用できることと、SQLite より psql の操作に慣れており、今回のような調査や復旧で迷いが少ないことが大きな利点だった。外部 DB の管理とバックアップを自分で引き受けられる環境だったため、PostgreSQL を選んだ。

Home Assistant 公式ドキュメントでは PostgreSQL を Recorder の保存先として利用できる一方、データベース間の履歴移行はサポートされていない。今回は公式の移行機能ではなく、DB を直接扱う作業として実施した。

PostgreSQL 接続時に psycopg2 が不足した

Recorder の保存先を PostgreSQL へ向けた直後は接続できなかった。ログには次のエラーが出ていた。

Error during connection setup: (retrying in 3 seconds)
ModuleNotFoundError: No module named 'psycopg2'

3 秒ごとの再試行後、Recorder は停止した。

Recorder setup failed, recorder shutting down
Setup failed for 'recorder': Integration failed to initialize.

PostgreSQL のスキーマを調べる前に、Home Assistant が動く/srv/homeassistant/の Python 環境へpsycopg2が必要だった。この問題を解消して作業を続けた。

python3 -m pip install psycopg2-binary

pgloader 後に Sequence が欠落した

修復済み SQLite の既存データは pgloader で PostgreSQL へ取り込めた。しかし Recorder を起動すると、次のエラーが大量に発生した。

null value violates not-null constraint

SQLite のAUTOINCREMENTに相当する PostgreSQL の Sequence と ID 列のDEFAULTが正しく再現されておらず、新しい行の ID を自動採番できないことが原因だった。問題になった列として記録に残っているものは次のとおり。

states.state_id
events.event_id
recorder_runs.run_id
statistics.id
statistics_short_term.id
statistics_meta.id

当初の「pgloader でコピーできればそのまま使える」という想定は誤りだった。各テーブルに Sequence を作成し、ID 列へ次の形で自動採番を関連付けた。

CREATE SEQUENCE ...;

ALTER TABLE ...
ALTER COLUMN ...
SET DEFAULT nextval(...);

Sequence 名と現在値は環境ごとに確認し、既存 ID と重複しないように設定する必要がある。

障害後の新 SQLite から統計をマージする

Home Assistant は破損を検出した後、新しいhome-assistant_v2.dbへ数時間分のデータを記録していた。修復した旧 DB だけを PostgreSQL へ移すと、この時間帯のデータが失われる。

旧DB: 約802MB
過去の履歴と統計。破損退避されたがREINDEXで整合性を回復

新DB: 当初約8.3MB
障害検出後からPostgreSQLへの切り替えまでに記録されたデータ

そこで、Energy Dashboard と長期統計に必要な次のテーブルを Python で追加マージした。

statistics_meta
statistics
statistics_short_term

statistics_meta.statistic_idを基準に、SQLite と PostgreSQL で異なるmetadata_idを対応付けてから、statisticsstatistics_short_termを投入した。

stateseventsは参照関係が複雑なため、今回は完全なマージを優先せず、Energy Dashboard と長期統計に必要なデータを優先した。

Recorder はここまでで PostgreSQL を使って正常に記録できる状態へ戻った。ただし、旧 DB と新 DB ではstatistics.sumの基準が異なっており、そのまま結合すると Energy Dashboard に大きな負差分が表示された。

PostgreSQL 移行後に再表示できたものの数値が不自然な Energy Dashboard

調査対象のセンサーを特定する

Energy Dashboard で使っている主なセンサーは次の 3 つだった。

発電
sensor.auto_discovery_measured_cumulative_amount_of_electric_energy_generated

買電
sensor.auto_discovery_measured_cumulative_amount_of_electric_energy_normal_direction

売電
sensor.auto_discovery_measured_cumulative_amount_of_electric_energy_reverse_direction

statistics_metaから内部で使われるmetadata_idを確認する。

SELECT statistic_id, id
FROM statistics_meta
WHERE statistic_id IN (
  'sensor.auto_discovery_measured_cumulative_amount_of_electric_energy_generated',
  'sensor.auto_discovery_measured_cumulative_amount_of_electric_energy_normal_direction',
  'sensor.auto_discovery_measured_cumulative_amount_of_electric_energy_reverse_direction'
);

結果は次のとおりだった。

対象metadata_id
売電139
買電140
発電159

以降の SQL ではこの ID を利用した。ただし、metadata_idは環境固有なので、別環境で同じ値をそのまま使ってはいけない。

移行直後に発生した統計異常

最初の異常は、Home Assistant の障害が日をまたいだ後に発生した。記録再開直後の統計を調べると、主要 3 センサーだけでなく、多数の個別機器で大きな負の差分が見つかった。

sensor.eakon_measured_cumulative_power_consumption
  -831.142kWh

sensor.eakon_measured_cumulative_power_consumption_3
  -772.671kWh

sensor.xi_zhuo_ji_today_s_consumption
  -384.065kWh

sensor.shi_xi_ji_today_s_consumption
  -270.469kWh
個別機器の消費統計にも巨大な棒が残っている状態

多くの機器では*_today_s_consumptionという日次リセット型センサーを使っていた。このため、障害中に 0 時のリセットを取りこぼし、再開時の小さな値が負差分になったのではないかと考えた。

しかし、日次リセットされない累積型のエアコンセンサーにも大きな負差分があった。また、*_this_month_s_consumptionにも同様の異常が見つかったが、こちらは Energy Dashboard では使用していなかった。日次リセットだけで全件を説明することはできなかった。

正常な日跨ぎと比較する

仮説を確認するため、障害がなかった 7 月 2 日から 3 日にかけての統計を調べた。

たとえば食洗機は、日付が変わるとstate1.731から0.330へ下がっている。一方、sumは減らず、当日の使用量だけが加算されていた。

time                 state   sum
2026-07-02 22:00     1.316   265.990
2026-07-02 23:00     1.731   266.405
2026-07-03 00:00     0.330   266.737
2026-07-03 01:00     0.330   266.737

冷蔵庫、テレビ関連機器、空気清浄機などでも同じ挙動を確認できた。正常時は日次センサーのstateがリセットされても、Recorder がリセットを考慮してsumを連続させている。

したがって「today_s_consumptionは 0 時に 0 へ戻るので、それだけで統計が壊れる」という仮説は誤りだった。今回は障害をまたいだことによる統計の不整合が絡んでいたと考えられるが、調査した範囲では全センサーに共通する根本原因までは特定できなかった。

主要な発電・買電・売電のsumを補正すると、Energy Dashboard は実際の発電量や売電量とほぼ合う状態まで戻った。

主要な統計を補正し発電・買電・売電がほぼ正常になった Energy Dashboard

1 時間集計の表示ズレを調べる

大きな異常を補正した後も、Energy Dashboard の 1 時間ごとの表示に小さな差が残った。食洗機の 15 時から 16 時が0.47kWh増えているように見えたため、短期統計と長期統計を比較した。

短期統計では 15 時 30 分から 15 時 45 分にかけて次のように推移していた。

15:30 state=1.557 sum=273.551
15:45 state=1.558 sum=273.552

長期統計を再確認すると、15 時から 16 時のsumの差は約0.001kWhだった。

15:00 sum=273.55099999999715
16:00 sum=273.55199999999720

全 kWh 統計を調べても、この時間帯に0.47kWh増加した統計は存在しなかった。この件はデータベース上の大きな不整合ではなく、軽微な表示上のズレとして一度様子を見ることにした。

途中で表示値の0.47をパーセントだと読み違えたが、単位は kWh だった。Energy Dashboard を調べる際は、画面上の単位と DB に保存されたunit_of_measurementを先に確認した方がよい。

移行を終えて

database disk image is malformedを見たときは過去データを失ったと思ったが、今回はインデックスのREINDEXで救出できた。そこから PostgreSQL へ移す方が大変で、pgloader 後の Sequence 修正と、旧 DB・新 DB に分かれた統計の結合が必要だった。

PostgreSQL が SQLite より常に堅牢なわけではない。それでも、普段から psql を使っていて既存のバックアップ運用にも載せられる自分の環境では、移行後の方が扱いやすい。想定以上に手数はかかったが、Energy Dashboard まで戻せたのでひとまず安心した。

後日、買電と売電に別の数千 kWh 級の異常が再発した。この件は元のstatesまで遡って直接原因を確認できたため、別の記事で扱う。

続きの記事:

Home Assistantのtotal_increasingセンサーに一瞬の異常値が入り数千kWh加算された

Energy Dashboardに再発した数千kWhの異常値をstatisticsとstatesから追跡し、元センサーの瞬間的な急減を特定した記録

sakakinox.net

Home Assistantのtotal_increasingセンサーに一瞬の異常値が入り数千kWh加算された

参考

https://pgloader.readthedocs.io/en/latest/quickstart.html

おしまい



Home Assistant に「さんあ〜る」のゴミ出しスケジュールを表示する方法
August 25, 2025November 29, 2025

はじめに ゴミ出しスケジュールは大きく変わらないものの、年末年始や祝日には変則的になる場合があります。 私の住んでいる自治体では「さんあ〜る」というサービスでゴミ出しスケジュールを提供しており、これを利用して Home Assistant のダッシュボード上に当日・翌日の予定を表示できるようにしました。 この記事では…

Continue reading...
sakakinox

Written by sakakinox
Server enginier

Copyright © sakakinox.net 2021-2026.