wiki/z-archive/2026-04-database-health-and-data-cleanup.md
2026-04-16 23:12:56 +02:00

13 KiB

Database Health and Data Cleanup — Admin Page

Beschreibung und Kontext

SysAdmin-Seite unter Admin > System > "Datenbank-Gesundheit" mit zwei Views:

  1. Tabellenstatistiken — Row Count, Size (MB), Index Size, Last Vacuum/Analyze fuer alle Tabellen in allen Datenbanken
  2. Orphan Cleanup — Generische Erkennung verwaister Datensaetze (FK-Referenzen auf nicht-existierende Parent-Records) mit Cleanup-Buttons

Business-Treiber: Beim Entfernen von Demo-Sets (und generell beim Loeschen von Mandanten/Features) bleiben verwaiste Daten im System. Aktuell gibt es keine Sicht auf den Zustand der Datenbanken und keine Moeglichkeit, Orphans systematisch zu erkennen und zu bereinigen.

Abhaengigkeiten:

  • FormGeneratorTable mit Batch-Handling (wird parallel implementiert)
  • Bestehende Interface-Architektur (interfaceDb*, interfaceFeature*)

Risiko bei Nicht-Umsetzung: Verwaiste Daten akkumulieren sich ueber die Zeit, belegen Speicher, und koennen zu Inkonsistenzen fuehren (z.B. Workflows ohne Mandate, Feature-Configs ohne Feature-Instanzen).

Fokus und kritische Details

Dynamische DB-Registry (Selbst-Registrierung)

Jede Datenbank registriert sich selbst in ihrem Interface (interfaceDb*.py oder interfaceFeature*.py). Es gibt keine statische Liste aller Datenbanken. Neue DBs werden automatisch erkannt, entfernte DBs verschwinden.

Pattern: Jedes Interface ruft beim Import oder bei der Initialisierung _registerDatabase("poweron_xyz") auf. Die Registry sammelt alle registrierten DBs dynamisch.

Deklaratives FK-Registry auf Model-Ebene

Jedes *Id-Feld das eine echte FK-Beziehung darstellt, bekommt ein fk_target in json_schema_extra:

mandateId: str = Field(
    ...,
    json_schema_extra={
        ...,
        "fk_target": {"db": "poweron_app", "table": "Mandate"},
    },
)

Felder die keine DB-FK sind (Stripe-IDs, logische IDs, Graph-Node-IDs, polymorphe referenceId) bekommen kein fk_target.

Cross-DB Orphan Detection

Viele FK-Beziehungen gehen ueber Datenbank-Grenzen hinweg (z.B. AutoWorkflow.featureInstanceId in poweron_graphicaleditor referenziert FeatureInstance in poweron_app). Der Scanner muss Parent-IDs aus der Ziel-DB laden und dann im Child-DB pruefen.

Performance

  • Stats: pg_stat_user_tables + pg_total_relation_size sind PostgreSQL-Katalog-Queries (kein Table-Scan). Ueber ~14 DBs mit je 5-15 Tabellen dauert der Call wenige Sekunden.
  • Orphans: Cross-DB-Checks koennen bei grossen Tabellen langsam sein. Ergebnisse werden gecacht (5 Min TTL).
  • n_live_tup ist ein Schaetzwert (aktualisiert nach ANALYZE/VACUUM). Fuer ein Health-Dashboard ausreichend.

Ziel und Nicht-Ziele

Ziel:

  • Dynamische DB-Registry die sich aus den Interfaces selbst befuellt
  • Auto-Registry fuer PowerOnModel-Subklassen (Tabellenname = Klassenname)
  • fk_target Annotationen auf allen echten FK-Feldern
  • Generischer Orphan-Scanner der FK-Graph aus Model-Metadaten aufbaut
  • Tabellenstatistiken via PostgreSQL-Katalog
  • SysAdmin-UI mit zwei Tabs (Stats + Orphans) und Clean-Buttons
  • Navigation-Eintrag unter Admin > System

Explizit NICHT:

  • Keine automatische Bereinigung (nur manuell per Button)
  • Keine Schema-Migration oder Aenderung an bestehenden DB-Constraints
  • Kein Monitoring/Alerting (nur On-Demand-Scan)
  • Keine Aenderung an bestehenden Interface-Initialisierungen (nur Registrierungs-Call hinzufuegen)

Betroffene Module

  • Gateway: modules/shared/dbRegistry.py (neu), modules/shared/fkRegistry.py (neu), modules/system/databaseHealth.py (neu), modules/routes/routeAdminDatabaseHealth.py (neu), modules/datamodels/datamodelBase.py (Model-Registry), ~20 Datamodel-Dateien (fk_target Annotationen), ~13 Interface-Dateien (DB-Registrierung)
  • Frontend: AdminDatabaseHealthPage.tsx (neu), Routing-Eintrag
  • DB-Migration: nein
  • Andere Komponenten: keine

Entscheidungen

Datum Entscheidung Begruendung
2026-04-13 Dynamische DB-Registry statt statischer Liste Interfaces registrieren sich selbst — bei Add/Remove von DBs muss die Registry nicht manuell gepflegt werden
2026-04-13 fk_target in json_schema_extra statt DB-Constraints als Quelle Ermoeglicht Cross-DB Orphan Detection (z.B. Greenfield → App DB); DB-Constraints existieren nur in poweron_app
2026-04-13 __init_subclass__ fuer Model-Registry Automatisch, kein manuelles Registrieren noetig; jede PowerOnModel-Subklasse ist sofort im Registry
2026-04-13 pg_stat_user_tables fuer Row Counts statt SELECT COUNT(*) Schnell (Katalog-Query), ausreichend genau fuer Health-Dashboard
2026-04-13 SysAdmin-only Zugriff Datenbank-Gesundheit ist eine System-Funktion, nicht mandantenspezifisch

Umsetzungs-Checkliste

Phase 1: Infrastruktur (Backend)

LLM: Opus 4.6 (Architektur-Entscheidungen, Cross-File-Refactoring)

  • DB-Registry (modules/shared/dbRegistry.py)

    • registerDatabase(dbName: str, configPrefix: str = "DB") — oeffentliche API, registriert eine DB
    • _getRegisteredDatabases() -> Dict[str, str] — intern, gibt alle registrierten DBs zurueck
    • _getConnectorForDb(dbName: str) -> DatabaseConnector — intern, Factory fuer Connector
    • Thread-safe (Lock oder atomare Operationen)
    • registerDatabase ohne _ Prefix weil explizit als oeffentliche API fuer andere Module gedacht
  • Selbst-Registrierung in allen Interfaces (~13 Dateien)

    • Jedes interfaceDb*.py und interfaceFeature*.py ruft registerDatabase() auf Modul-Ebene auf
    • Kein Umbau der bestehenden Connector-Logik — nur ein zusaetzlicher Registrierungs-Call
  • Model-Registry (modules/datamodels/datamodelBase.py)

    • _MODEL_REGISTRY: Dict[str, Type[PowerOnModel]] — automatisch via __init_subclass__
    • _getModelByTableName(tableName: str) -> Optional[Type[PowerOnModel]] — Lookup

Phase 2: FK-Annotationen (Backend)

LLM: Composer Fast (repetitive Annotation-Arbeit, klares Pattern)

  • fk_target auf allen FK-Feldern (~80-90 Felder in ~20 Dateien)
    • modules/datamodels/datamodelMembership.py — UserMandate, FeatureAccess, Junctions
    • modules/datamodels/datamodelRbac.py — Role, AccessRule
    • modules/datamodels/datamodelFeatures.py — FeatureInstance
    • modules/datamodels/datamodelChat.py — ChatWorkflow, ChatMessage, ChatLog, ChatDocument
    • modules/datamodels/datamodelFiles.py — FileItem, FileFolder
    • modules/datamodels/datamodelKnowledge.py — FileContentIndex, ContentChunk, WorkflowMemory
    • modules/datamodels/datamodelBilling.py — BillingAccount, BillingTransaction, BillingSettings
    • modules/datamodels/datamodelSubscription.py — MandateSubscription
    • modules/datamodels/datamodelInvitation.py — Invitation
    • modules/datamodels/datamodelDataSource.py — DataSource
    • modules/datamodels/datamodelFeatureDataSource.py — FeatureDataSource
    • modules/datamodels/datamodelUam.py — Token, AuthEvent, UserConnection, UserVoicePreferences, UserNotification
    • modules/features/graphicalEditor/datamodelFeatureGraphicalEditor.py — AutoWorkflow, AutoVersion, AutoRun, AutoStepLog, AutoTask
    • modules/features/trustee/datamodelFeatureTrustee.py — alle Trustee-Models
    • modules/features/workspace/datamodelFeatureWorkspace.py — WorkspaceUserSettings
    • modules/features/neutralization/datamodelFeatureNeutralizer.py — DataNeutraliserConfig
    • modules/features/realEstate/datamodelFeatureRealEstate.py — Kanton, Parzelle, Projekt
    • modules/datamodels/datamodelContent.py — ContentChunk, ContentObject (falls vorhanden)
    • modules/interfaces/interfaceDbManagement.py — MessagingSubscription, Prompt (falls Models dort)

Phase 3: FK-Discovery und Orphan-Scanner (Backend)

LLM: Opus 4.6 (komplexe Cross-DB-Logik, SQL-Generierung)

  • FK-Discovery (modules/shared/fkRegistry.py)

    • _discoverFkRelationships() -> List[FkRelationship] — scannt Model-Registry, liest fk_target
    • Cached nach erstem Scan (Models aendern sich nicht zur Laufzeit)
    • FkRelationship Dataclass: sourceDb, sourceTable, sourceColumn, targetDb, targetTable, targetColumn
    • Table-to-DB Mapping automatisch aus fk_target-Annotationen + Catalog-Fallback
  • Orphan-Scanner (modules/system/databaseHealth.py)

    • _scanOrphans(dbFilter: Optional[str]) -> List[OrphanResult]
    • Same-DB: NOT EXISTS (SELECT 1 FROM parent WHERE parent.col = source.col)
    • Cross-DB: Parent-IDs laden, dann NOT IN (unnest(...))
    • _cleanOrphans(db, table, column) -> int — loescht Orphans, gibt Count zurueck
    • _cleanAllOrphans() -> List[dict] — alle Orphans bereinigen
    • _getTableStats(dbFilter: Optional[str]) -> List[TableStats] — pg_stat_user_tables Query
    • 5-Minuten-Cache fuer Orphan-Ergebnisse

Phase 4: API-Endpunkte (Backend)

LLM: Composer Fast (Standard-Route-Pattern, klar definiert)

  • Route (modules/routes/routeAdminDatabaseHealth.py)

    • Prefix: /api/admin/database-health
    • GET /stats — Tabellenstatistiken (optional ?db=...)
    • GET /orphans — Orphan-Scan (optional ?db=...)
    • POST /orphans/clean — Cleanup Body: {"db": "...", "table": "...", "column": "..."}
    • POST /orphans/clean-all — Alle Orphans bereinigen
    • Alle Endpunkte: SysAdmin-only via requireSysAdminRole
  • Router in app.py registrieren

Phase 5: Navigation (Backend)

LLM: Composer Fast (einzelne Zeile in mainSystem.py)

  • Navigation-Eintrag in modules/system/mainSystem.py
    • Unter admin-system-group, order 98, sysAdminOnly
    • Icon: FaDatabase, Path: /admin/database-health

Phase 6: Frontend

LLM: Opus 4.6 (neue Seite mit Tabs, FormGeneratorTable-Integration)

  • AdminDatabaseHealthPage.tsx mit zwei Tabs
    • Tab 1 "Statistiken": Sortierbare Tabelle mit DB, Tabelle, Rows, Total Size, Index Size, Last Vacuum, Last Analyze. Summary-Zeile oben. DB-Filter.
    • Tab 2 "Orphan Cleanup": Tabelle mit Source DB, Tabelle, FK-Spalte, Referenz (inkl. cross-db Badge), Orphans, Clean-Button. Filter "Nur Probleme". Batch-Clean-All.
  • Routing in Frontend PAGE_REGISTRY + App.tsx Route + admin/index.ts Export
  • i18n — alle Labels mit t() getaggt

Querschnitt-Checks

  • API-Endpunkte: 4 neue (stats, orphans, clean, clean-all)
  • DB-Schema / Migration: nein
  • Frontend-Komponenten: 1 neue Seite (2 Tabs)
  • RBAC / Permissions: SysAdmin-only
  • Neutralisierung betroffen? nein
  • Navigation / Routing: 1 neuer Eintrag
  • Billing-Impact? nein

Akzeptanzkriterien

# Kriterium (Given-When-Then) Prio
1 Given SysAdmin auf /admin/database-health, When Tab "Statistiken" geladen, Then werden alle Tabellen aller registrierten DBs mit Row Count und Size angezeigt must
2 Given SysAdmin auf Tab "Orphan Cleanup", When Scan ausgefuehrt, Then werden alle FK-Beziehungen mit Orphan-Count angezeigt must
3 Given Orphans in AutoWorkflow.featureInstanceId (Cross-DB), When "Clean" geklickt, Then werden die verwaisten AutoWorkflow-Zeilen geloescht und Count aktualisiert must
4 Given neue Feature-DB wird hinzugefuegt (z.B. poweron_newfeature), When Interface sich registriert und Scan laeuft, Then erscheint die neue DB automatisch in Stats und Orphan-Scan must
5 Given kein SysAdmin-User, When /admin/database-health aufgerufen, Then 403 Forbidden must
6 Given grosse Tabelle (>100k Rows), When Orphan-Scan laeuft, Then antwortet der Scan innerhalb von 30 Sekunden should
7 Given "Alle bereinigen" geklickt, When mehrere Tabellen Orphans haben, Then werden alle Orphans in allen Tabellen bereinigt und Summary angezeigt should

Testplan

ID AC Art Automatisiert Methode Status
T1 1 api nein Manuell: GET /stats, pruefen ob alle DBs und Tabellen erscheinen pending
T2 2 api nein Manuell: Demo laden, Demo entfernen (ohne Cascade), GET /orphans pruefen pending
T3 3 api nein Manuell: POST /orphans/clean fuer AutoWorkflow, dann GET /orphans erneut pending
T4 4 unit nein Manuell: _registerDatabase() aufrufen, pruefen ob in _getRegisteredDatabases() pending
T5 5 api nein Manuell: Non-SysAdmin Request, 403 pruefen pending
T6 7 e2e nein Manuell: Mehrere Orphan-Typen erzeugen, "Alle bereinigen", Stats pruefen pending
  • Cursor-Plan: .cursor/plans/data_cleanup_admin_page_0a610562.plan.md
  • Demo-Config Bug-Fix: gateway/modules/demoConfigs/investorDemo2026.py (remove() Cascade)
  • deleteMandate Cascade-Fix: gateway/modules/interfaces/interfaceDbApp.py (_cascadeDeleteGraphicalEditorData)
  • Coding-Conventions: wiki/d-guides/coding-conventions.md

Abschluss

  • b-reference/ aktualisiert (platform/database-architecture.md — neuer Abschnitt "Database Health")
  • TOPICS.md aktualisiert (neues Thema "Database Health / Data Cleanup")
  • Dieses Dokument → z-archive/ verschoben