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:
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user