ShineBridge v1.8.28 — history.py: load_recent Fix + periodisches Cleanup

- load_recent(): Window-Funktion durch pro-Sensor-Indexabfragen ersetzt
  (SELECT ... ORDER BY ts DESC LIMIT N per sensor_id) — nutzt Index optimal,
  kein Full-Table-Scan mehr auf 1M+ Zeilen beim Start
- Periodisches Cleanup: täglich via Daemon-Thread statt nur beim Start —
  DB bleibt dauerhaft auf RETENTION_DAYS beschränkt
- RETENTION_DAYS: 7 → 14 (explizites Maximum per Konfiguration)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
retr0
2026-05-26 13:31:46 +02:00
parent c4047fc804
commit d9f94d3f28
2 changed files with 24 additions and 17 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
name: ShineBridge name: ShineBridge
version: "1.8.27" version: "1.8.28"
slug: shinebridge slug: shinebridge
description: Growatt Wechselrichter lokal in Home Assistant — Modbus TCP via ShineLAN-X, MQTT Discovery, Web UI description: Growatt Wechselrichter lokal in Home Assistant — Modbus TCP via ShineLAN-X, MQTT Discovery, Web UI
url: https://gitea.bitfire.work/retr0/shinebridge url: https://gitea.bitfire.work/retr0/shinebridge
+23 -16
View File
@@ -7,7 +7,7 @@ from typing import Dict, List, Optional, Tuple
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
DB_PATH = "/data/history.db" DB_PATH = "/data/history.db"
RETENTION_DAYS = 7 RETENTION_DAYS = 14
_lock = threading.Lock() _lock = threading.Lock()
_conn: sqlite3.Connection | None = None _conn: sqlite3.Connection | None = None
@@ -59,9 +59,19 @@ def init_db():
""") """)
c.commit() c.commit()
cleanup_old() cleanup_old()
_start_cleanup_scheduler()
log.info("History DB initialisiert: %s", DB_PATH) log.info("History DB initialisiert: %s", DB_PATH)
def _start_cleanup_scheduler():
def _loop():
while True:
time.sleep(86400)
cleanup_old()
t = threading.Thread(target=_loop, daemon=True, name="history-cleanup")
t.start()
def period_key(period_type: str, billing_day: int = 1, billing_month: int = 1) -> str: def period_key(period_type: str, billing_day: int = 1, billing_month: int = 1) -> str:
import datetime import datetime
today = datetime.date.today() today = datetime.date.today()
@@ -117,21 +127,18 @@ def write_batch(inv_id: str, ts: float, values: Dict[str, float]):
def load_recent(inv_id: str, limit: int = 300) -> Dict[str, List[Tuple[float, float]]]: def load_recent(inv_id: str, limit: int = 300) -> Dict[str, List[Tuple[float, float]]]:
"""Letzte `limit` Messpunkte pro Sensor — zum Befüllen der In-Memory-Deque beim Start.""" """Letzte `limit` Messpunkte pro Sensor — zum Befüllen der In-Memory-Deque beim Start."""
with _lock: with _lock:
rows = _get_conn().execute(""" c = _get_conn()
SELECT sensor_id, ts, value sensors = [r[0] for r in c.execute(
FROM ( "SELECT DISTINCT sensor_id FROM measurements WHERE inv_id = ?", (inv_id,)
SELECT sensor_id, ts, value, ).fetchall()]
ROW_NUMBER() OVER (PARTITION BY sensor_id ORDER BY ts DESC) AS rn result: Dict[str, List[Tuple[float, float]]] = {}
FROM measurements for sid in sensors:
WHERE inv_id = ? rows = c.execute(
) "SELECT ts, value FROM measurements "
WHERE rn <= ? "WHERE inv_id = ? AND sensor_id = ? ORDER BY ts DESC LIMIT ?",
ORDER BY ts ASC (inv_id, sid, limit),
""", (inv_id, limit)).fetchall() ).fetchall()
result[sid] = [(ts, val) for ts, val in reversed(rows)]
result: Dict[str, List[Tuple[float, float]]] = {}
for sensor_id, ts, value in rows:
result.setdefault(sensor_id, []).append((ts, value))
return result return result