SQL Statements zum Finden von Ursachen einer zu großen Datenbank (für den, der aufräumen möchte)

Hi

Ich habe mich heute hier angemeldet, hatte mir aber in der Vergangenheit schon öfters Anregungen geholt. Danke an alle, die hier helfen, insbesondere an Simon, dessen Videos vieles erleichterte. Heute will ich ein Stück zurückgeben.

Derzeit optimiere ich einige Aspekte meines Homeassistant-Setups, mit dem Ziel, eine möglichst schlanke Datenbank zu erreichen. Ich nutze MariaDB 2.7.1 und greife über das Addon phpMyAdmin 0.9.1 auf die Datenbank zu. Auf der Suche nach den größten Speicherverbrauchern haben mir diese SQL-Abfragen geholfen. Ich habe nur grundlegende SQL-Kenntnisse, aber mit der Hilfe von ChatGPT ist vieles möglich.

Lange Zeit habe ich das Thema ignoriert, bis die Datenbankgröße über 5 GB hinauswuchs. Das tägliche Vollbackup dauerte gefühlt ewig, und da ich auf eine NAS sichere, begann auch dort das rotierende Backup sich aufzublähen. Heute beträgt die Größe meiner Datenbank etwa 1,3 GB, mit 11 Tagen Historie und 1290 Entities.

Gutes Gelingen und viel Spaß beim Aufräumen :slightly_smiling_face:

1. Listet auf welche Sensoren am meisten Daten liefern

Braucht man die wirklich alle in der Fülle? Ich ging dann alle durch und entschied ja oder nein und bei nein, schloß ich die Statistik über configuration.xaml aus. Manche Entities benutzte ich gar nicht und der Informationswert war auch beschränkt. In diesen Fällen habe ich die Entity gleich deaktiviert. Das sind Abwägungsfragen, die jeder für sich beantworten muß.

SELECT
  ROUND((COUNT(*) * 100.0 / (SELECT COUNT(*) FROM states)), 0) AS cnt_pct,
  COUNT(*) AS cnt,
  states_meta.entity_id
FROM states
INNER JOIN states_meta ON states.metadata_id = states_meta.metadata_id
GROUP BY states_meta.entity_id
ORDER BY cnt DESC;

2. Zeigt welche Event Typen am meisten gefeuert werden

Ich war erstaunt, daß timer.restart bei mir zu den Top3 gehörte und ging dem nach. Am Ende ließ ich über einen Template Trigger 2 Automatisierung nur dann starten bzw. den Timer erneuern, wenn der Timer bereits seit 30 min läuft. Dies reichte für den Zweck vollkommen aus.

SELECT
  ROUND((COUNT(*) * 100.0 / (SELECT COUNT(*) FROM events)), 0) AS cnt_pct,
  COUNT(*) AS cnt,
  event_types.event_type
FROM events
INNER JOIN event_types ON events.event_type_id = event_types.event_type_id
GROUP BY event_types.event_type
ORDER BY cnt DESC;

3. Zeigt wie häufig welche Automatisierung getriggert wurde

Den Ursachen ging ich auf den Grund und vor allem bei Bewegungsmeldern wurde ich fündig. Man kann jede Bewegung eine Automatisierung starten lassen und z.B. die gewünschten über Zeit-Conditions abfangen. Man kann aber auch einen Template Trigger nehmen und Bewegung und Zeit kombinieren und nur dann startet die Automatisierung und schont somit die Datenbank.

Das SQL Statement kannst Du auch für andere EVENT_TYPES nehmen wie
“script_started” oder “shelly.click” nehmen, je nachdem was am meisten angezeigt wird und auch Sinn ergibt. “script_started” fand ich noch hilfreich aber am Ende führt das meistens wieder auf Automatisierung zurück.

WITH triggered_events AS (
    SELECT 
        e.event_id,
        JSON_UNQUOTE(JSON_EXTRACT(ed.shared_data, '$.entity_id')) AS automation_id
    FROM 
        events e
    JOIN 
        event_types et ON e.event_type_id = et.event_type_id
    JOIN 
        event_data ed ON e.data_id = ed.data_id
    WHERE 
        et.event_type = 'automation_triggered'
)
SELECT 
    ROUND((COUNT(te.event_id) * 100.0 / total_triggers.total_count), 0) AS cnt_pct,
    COUNT(te.event_id) AS trigger_count,
    te.automation_id
FROM 
    triggered_events te
CROSS JOIN 
    (SELECT COUNT(*) AS total_count FROM triggered_events) total_triggers
GROUP BY 
    te.automation_id,
    total_triggers.total_count
ORDER BY 
    trigger_count DESC;

4. Welcher Trigger triggert Automatisierung XYZ am meisten?

Ich habe eine Top-Automatisierung mit fast 10 Triggern und ich wollte wissen welchen ich optimieren sollte. Und siehe da, 2 Sensortrigger die bei beliebiger Statusänderung anschlugen. Dies grenzte ich auf einige ausgewählte Attributsänderungen ein.

WITH automation_events AS (
    SELECT 
        e.event_id,
        JSON_UNQUOTE(JSON_EXTRACT(ed.shared_data, '$.source')) AS source
    FROM 
        events e
    JOIN 
        event_types et ON e.event_type_id = et.event_type_id
    JOIN 
        event_data ed ON e.data_id = ed.data_id
    WHERE 
        et.event_type = 'automation_triggered'
        AND JSON_UNQUOTE(JSON_EXTRACT(ed.shared_data, '$.entity_id')) = 'automation.XYZ'
)
SELECT 
    ROUND((COUNT(ae.event_id) * 100.0 / total_triggers.total_count), 0) AS cnt_pct,
    COUNT(ae.event_id) AS trigger_count,
    ae.source
FROM 
    automation_events ae
CROSS JOIN 
    (SELECT COUNT(*) AS total_count FROM automation_events) total_triggers
GROUP BY 
    ae.source,
    total_triggers.total_count
ORDER BY 
    trigger_count DESC;

3 „Gefällt mir“

Interessant, für was genau steht cnt_pct und cnt? Bei den triggern konnte ich es mehr herleiten, aber bei den Statistiken nicht so ganz…

cnt = count, pct = percentage, wobei ich die Prozentzahl runde. Die Ursprungsidee hatte ich mir aus einem englischen Forum geholt und übernommen.
Da die Ergebnisse absteigend sortiert sind, fange die oberen zu untersuchen an. Aus welchem Gründen sind es die top Werte? Nicht immer ist das gleich eine unzureichende Programmierung, manchmal ist das einfach so. Ich addiere zum Beispiel den Stromverbrauch einiger Stecker in einem Sensor. Der liefert dann mit Abstand die meisten Daten.
Noch eine Bemerkung: Ich arbeite erst seit 4 Wochen mit ChatGPT und bin begeistert über die Möglichkeiten, die es einem “Freizeitentwickler” bietet. Gib einfach das Script ein und frage was es tut. Immer ein guter Einstieg.

Ich verstehe was das Script tut :slight_smile: wollte nur nochmal sicher gehen ob wir von gleichen reden, vor allem bei percantage sahen ein paar Ergebnisse komisch aus. Muss ich nochmal prüfen.

Bisher habe ich keine große Auffälligkeiten gefunden, was gut ist.

Mein WR liefert die meisten Daten was nicht verwunderlich ist.
Tatsächlich war ich über die Top 2 Automationen überrascht, da werden die Räume für die Staubsauger zurückgesetzt, weil die Roboter ihren Status wohl einige mal übertragen. Aber 48 mal auf den Tag gesehen eine input liste zurücksetzen sieht nicht danach aus, als ob ich da optimieren muss.

Danke auf jeden Fall coole Idee

freut mich, schönes WE

Ich habe noch einen Weg gefunden, meinen TOP1 Speicherverbraucher zu reduzieren. Anstelle jede einzelne Stromschwankung mitzuschneiden, sage ich erst ab xxx Veränderung gegenüber Vorwert.
Vielleicht kann das jemand gebrauchen.

{# Idee: Ich addiere von ca. 15 Geräten die momentane Wattzahl in einen Sensor sensor.stromverbrauch. Gelöst hatte ich dies erst über eine Gruppe, die dann bei jeder kleinsten Watt Änderung den Status in die Datenbank scrieb und so zum Top 1 Größen-Treiber wurde. Aber eigentlich interessierte mich der Stromverbrauch erst ab einer gewissen Größenordnung. Mit Hilfe des unteren Template UI Sensors wird erst ein Wert geschrieben, wenn die Wattzahl mehr als 10 schwankt.#}

{% set delta_watt = 10 -%}
{% set previous_value = states('sensor.stromverbrauch') | float(default=0) -%}

{% set sensor_names = [
  'sensor.xxx1_power',
  'sensor.xxx2_power',
  'sensor.xxx3_power',
  'Füge mehr Sensoren hinzu'
] -%}
{% set current_total = namespace(value=0) -%}

{% for sensor in sensor_names -%}
  {% set consumption = states(sensor) | float(default=0) -%}
  {% set current_total.value = current_total.value + consumption -%}
{% endfor -%}

{% if (current_total.value - previous_value) | abs > delta_watt -%}
  {{ current_total.value | round(0) }}
{% else -%}
  {{ previous_value | round(0) }}
{% endif %}

Nach 9 Tagen der obig zuletzt genannten Veränderung des sensor.stromverbrauch hat sich das Schreiben der States geändert

VON

cnt_pct cnt entity_id
14 402216 sensor.stromverbrauch

AUF

cnt_pct cnt entity_id
5 106846 sensor.stromverbrauch

Das ist der Wahnsinn und mehr als ich mir erhofft hatte.

Die Maria DB Size ist auf von 1.3 auf < 1 GB geschrumpft. “Traumwerte” wie ich sie schon seit 1 Jahr nicht mehr hatte.

Ja, man kann immer auf potentere Hardware umsteigen. Man kann aber auch durch durchdachtere Programmierung etwas erreichen.

@Bacardi vielen Dank dafür.
copy/paste und voll umfangreiche/aussagekräftige Ergebnisse.

Danke, ich mach das auch noch von Zeit zu Zeit mal wieder.

Am effektivsten hat sich aber Folgendes gezeigt: Bei jedem Anlegen eines neuen Helfers/Sensors oder eines Gerätes frage ich mich, brauchst Du wirklich diese Entities oder gar die History davon. In den meisten Fällen ist die Antwort auf letzteren Teil nein. Nach knapp 2 Jahren HA liege ich derzeit bei etwas über 1800 Entities und 1,4 GB DB Size (Raspi 4).

1 „Gefällt mir“

Ja das ist immer eine gute Frage, aber teilweise auch schwer vorher abzusehen

Moin,

habe alles auf Standard belassen und meine SQLite Datenbank ist aktuell


Bei ~ 2000 Entitäten.

Also ist der Standard nicht so weit weg, von einer optimierten / angepassten Installation :slight_smile:

Aber hier geht es ja nicht um wer hat den längeren, ähm kleinere Datenbank :slight_smile:

VG
Bernd

Interessanter Vergleich, gebe ich zu. Ich war mit Standard aber auch schon bei 5 GB bei weit weniger Entities, siehe oben und habe mich dann wieder heruntergearbeitet. Ich vermute, die Anzahl der History Tage ist auch ein wesentlicher Treiber, habe mich auf 11 festgelegt.

Was bei mir auch zu einem spürbaren Effekt geführt hatte, war die Summierung aller Stromdaten so zu verändern, daß erst bei einem Delta von xxx Watt der Sensor neu geschrieben wurde. Diese Logik hatte ich auch später beim Einbau des Shelly 3 EM pro übernommen.

Moin,

kann mich nicht an den genauen zeitlichen Ablauf erinnern, aber ja, im vergangenen Jahr waren einige HA Releases wohl, vorsichtig ausgedrückt, fehlerhaft, da wurde ganz schöner Bockmist mit der Datenbank angestellt, oder das Prunen ging nicht durch usw.
Aber seit einigen HA Versionen hat es sich bei mir so eingependelt, wie geschrieben alles auf Standard, keine Anpassungen am Prune, oder am recorder.

Auch meine zweite Instanz von HA, die als LXC läuft, hat eine Größe von 1,2 GB bei ~ 2000 Entitäten.

Trotz allem, sind Deine Ausarbeitungen immer wieder lesenswert und dass Du diese Dinge mit uns teilst ist echt super, vielen lieben Dank dafür.

VG
Bernd

Bernd, ich werde ja noch rot im Gesicht :innocent: lch glaube an das Prinzip geben und nehmen, auch im Job. Auf mittlere Sicht zahlt sich das immer aus. Und was ich hier im Forum schon an Ideen bekam. Du und noch 5 andere sind aber auch super aktiv. Ich vermisse Oserkon und seine Beiträge, schade daß er eine Auszeit nimmt. Bis dann

2 „Gefällt mir“

Wie ermittelt ihr denn die Anzahl eurer Entitäten?

{{states | count}}
1 „Gefällt mir“

Hast das mit deinen Datenbank Enitäten verglichen? Bei mir kommen da 2 verschiedene Ergebnisse raus :joy:.

Aber ja bin so bei knapp 3500 Entitäten bei 1GB DB. Die Entitäten kommen mir ein wenig viel vor :sweat_smile:

Eine interessante Frage und ich kann nur spekulieren. Das kann sicher nur eine HA/DB Fachmann erklären.

{{states | count}} → 1831

In der DB habe ich das Sql Statement genommen

SELECT COUNT(*) AS anzahl_entities
FROM (
  SELECT DISTINCT sm.entity_id
  FROM homeassistant.states_meta sm
  UNION
  SELECT DISTINCT stm.statistic_id
  FROM homeassistant.statistics_meta stm
) AS alle_entities;

→ 1116 also es fehlen ca. 700

Erklärungsversuche

  • Obiges SQL zählt verkehrt oder unvollständig
  • Ich habe momentan 40 Entities mit unknown, none oder unavailable Status, die nicht in der DB stehen
  • In der DB werden nur Entities gespeichert, die einen Status geschrieben haben? Ich rate nur

Wie hast Du denn in der DB gezählt?

Ich habe einfach nur mal getestet

SELECT COUNT(*) FROM states_meta;

Ich muss mal schauen warum ich soviele habe :D.

Aus heiterem Himmel kam mir unter der Dusche heute Morgen die Idee und ein paar Tests beweisen es hinreichend genug

  • states |count = ca. 1800 aber in MariaDB nur 1100 Entities
  • In den Entwicklerwerkzeugen habe ich mir die Namen aller 1800 Entities ausgeben lassen
{{states | count}}
{% for state in states %}
{{ state.entity_id }} - {{ state.name }}
{% endfor %}
  • Dann bin ich auf die Datenbank gegangen und habe mir die Namen der Entities dort ausgeben lassen
SELECT DISTINCT sm.entity_id AS entity_name
FROM homeassistant.states_meta sm
UNION
SELECT DISTINCT stm.statistic_id AS entity_name
FROM homeassistant.statistics_meta stm;
  • Und dann die Idee unter der Dusche: Welche kommen nicht vor … und das sind zu 90 % die, die ich in der configuration.yaml von der Statistik ausgeschlossen habe.
recorder:
  db_url: mysql://homeassistant:xxxcharset=utf8mb4
  purge_keep_days: 11
  exclude: #https://www.home-assistant.io/integrations/recorder/
    domains:
      - camera
      - media_player
      - schedule
    entity_globs:
      - automation.om_aut_tech_dashboard_*
      - binary_sensor.sonos*
      - ... ca. 400 aber mit Wildcards gearbeitet

Ohne alle Fälle geklärt zu haben, denke ich nun in der DB landet nur was eine Statistik schreibt während states | count alle in HA verwalteten zeigt.

Danke für die Denksportaufgabe.