Die Persistenzschicht einer TopLogic-Anwendung versioniert normalerweise (wenn nichts anderes konfiguriert ist). D.h. es gibt keine Möglichkeit, Daten von versionierten Tabellen echt zu löschen. Alle vergangenen Versionen von aktuellen Objekten als auch alle Stände von gelöschten Objekten bleiben (für immer) verfügbar.
Es kann die Anforderung geben, dass eine Anwendung Informationen nach einer gewissen Mindest-Aufbewahrungszeit tatsächlich physikalisch löscht.
Verbesserung
Wartungsfunktion compactHistory(beforeDate), die den Datenbestand so modifiziert, dass
- alle Versionen von Objekten, die vor dem angegebenen Zeitpunkt durch eine Änderung ersetzt wurden, tatsächlich gelöscht wurden.
- Dies impliziert, dass Objekte, die vor dem angegebenen Zeitpunkt gelöscht wurden, mit all ihren Versionen komplett aus dem Datenbestand gelöscht werden.
- Versionen von Objekten, die vor dem angegebenen Zeitpunkt erzeugt wurden aber noch nach dem Zeitpunkt gültig waren, sollen so geändert werden, dass die Version mit einer künstlichen Revision "Compact-Revision" (zum Zeitpunkt beforeDate) erstellt wurde.
- Die Create-Revision von Objekten, die vor dem angegebenen Zeitpunkt liegt, soll auf die "Compact-Revision" geändert werden.
Design
Kompaktierungsrevision
Die Kompaktierungsrevision C` ist die '''älteste erhaltene Revision''': die neueste Revision, deren Datum nicht nach `beforeDate liegt. Der Datenstand zu Revision C` bleibt exakt lesbar; alle Revisionen vor `C fallen in C` zusammen. Zeilen sind gültig für `REV_MIN <= R <= REV_MAX; gelöscht werden daher Zeilen mit REV_MAX < C (nicht <= C, sonst fehlten zu C` sichtbare Zeilen). Revisionsnummern werden nicht umnummeriert; in Anwendungsdaten gehaltene Revisionsnummern `>= C bleiben gültig.
Intern wird die Operation als Bereichsform compactRevisions(Y, X) (Revisionen Y < R < X fallen in X`) umgesetzt; die globale Kompaktierung ist der Fall `Y = 0. Die Statements unterscheiden sich nur um ein Prädikat (Zeilen vollständig innerhalb des Bereichs werden gelöscht, REV_MIN im Bereich wird auf X` angehoben, `REV_MAX im Bereich wird auf X - 1 gesetzt, vgl. comment:6). Die Bereichsform ermöglicht später eine mit dem Alter abnehmende Versionsdichte (comment:5); nach außen wird zunächst nur die globale Form angeboten.
Schritte
Jeder Schritt ist idempotent und einzeln committierbar, so dass ein abgebrochener Lauf einfach wiederholt werden kann. Große Tabellen werden in Batches gelöscht (vgl. das PostgreSQL-Problem in #28053).
- Referenz-Pins umschreiben: Jede _REV-Spalte einer Referenz mit history-type="historic" oder "mixed" mit Wert < C wird auf C` gesetzt. Dies geschieht vor dem Löschen, damit zu jedem Zeitpunkt entweder die alten Zeilen noch existieren oder die Referenz bereits auf `C zeigt. Ein gepinntes Objekt, das zu C` nicht mehr existiert, ergibt einen ins Leere zeigenden Verweis; das ist in der Anforderung enthalten, der Resolver in `KnowledgeReferenceStorageImpl muss dann null liefern statt zu scheitern (zu prüfen).
- Pro Item-Tabelle: DELETE ... WHERE REV_MAX < C; danach REV_MIN und REV_CREATE auf C anheben, wo sie kleiner sind.
- FLEX_DATA: gleiches Löschen und Anheben von REV_MIN.
- BRANCH: BASE_REV und REV_CREATE auf C anheben, wo sie kleiner sind.
- REVISION / REVISION_XREF: Zeilen mit rev < C löschen; die Metadaten der Revision C` durch eine synthetische System-Revision (Autor system, Log "compacted") ersetzen, da ihr nun alle angehobenen Zeilen zugeschrieben werden; XRef-Einträge für `C aus allen (BRANCH, Typ)-Paaren mit REV_MIN = C einfügen.
Unversionierte Tabellen werden vom bestehenden HistoryCleanup bereits aufgeräumt; die Kompaktierung behandelt sie identisch (das Anheben von REV_MIN ist dort unschädlich).
Grenzen
Von Anwendungen als einfache Zahl gespeicherte Revisionsnummern < C (z.B. aus TL-Script) können nicht gefunden und umgeschrieben werden. Dies ist eine dokumentierte Einschränkung.
Ausführung
Die Knowledge-Base cached Items und Revisionen im Speicher, auf jedem Cluster-Knoten. Die Kompaktierung läuft daher im Wartungsfenster; anschließend wird die Persistenzschicht auf allen Knoten neu gestartet. Bereitstellung als Administrationsaktion sowie optional als geplanter Task mit konfigurierter Aufbewahrungsdauer.
Gemeinsame Infrastruktur mit #29603
Das gezielte physische Löschen gelöschter Objekte (#29603) und die Kompaktierung benötigen denselben Kern: einen Durchlauf über das Typ-Repository, der alle Item-Tabellen, deren Referenzspalten (_ID, _REV, _BRC, _TYPE gemäß AbstractMOReference; fehlende _BRC/`_TYPE`-Spalten werden aus dem BRANCH der verweisenden Zeile bzw. dem konfigurierten monomorphen Zieltyp aufgelöst) und FLEX_DATA aufzählt und daraus dialektneutrale Statements über SQLFactory erzeugt. Vorbild ist HistoryCleanup.
Tests
Tests über AbstractDBKnowledgeBaseTest: Objekte anlegen, mehrfach ändern, teilweise löschen, historische und gemischte Referenzen auf alte Versionen setzen, Branch anlegen; nach compactHistory muss die historische Sicht zu C` dem Stand vor der Kompaktierung entsprechen, Revisionen `< C dürfen nicht mehr auflösbar sein, Referenz-Pins müssen auf C` zeigen. Invariante: ein `KnowledgeBaseDumper-Dump ab Revision C` ist bis auf die synthetische Revision `C unverändert.
Lösung
SQL-Kern `HistoryCompaction`
Klasse HistoryCompaction in com.top_logic.knowledge.service.db2, nach dem Vorbild von HistoryCleanup: erzeugt aus ConnectionPool, Typ-Repository und SQL-Dialekt alle Statements über SQLFactory und führt sie auf einer Schreibverbindung des Pools ohne Knowledge-Base-Transaktion aus. Operationen:
- resolveCompactionRevision(beforeDate): die neueste Revision, deren Datum nicht nach dem Stichtag liegt (dieselbe Abfrage wie die Revisionssuche nach Datum der Knowledge-Base).
- compactRevisions(Y, X) als allgemeine Bereichsform, compactHistory(beforeDate) als Fall Y = 0.
- analyzeRevisions(Y, X) / analyzeHistory(beforeDate): Trockenlauf, der mit denselben Prädikaten nur zählt.
- Ergebnis ist ein Report mit Kompaktierungsrevision und Zeilenzahlen je Tabelle (gelöscht, umgeschrieben, verschobene Pins, geleerte Pins).
Die Aufzählung der Item-Tabellen (alle konkreten Item-Untertypen mit REV_MIN/`REV_MAX`, versioniert wie unversioniert; die Systemtabellen Revision, RevisionXref, Branch, BranchSwitch, FlexData sind keine Item-Untertypen und damit ausgeschlossen), ihrer revisionspinnenden Referenzen und der FLEX_DATA-Tabelle liegt in der eigenständigen Klasse ItemTables, auf der #29603 aufsetzen kann. Für den Trockenlauf wurde die Aggregatfunktion count in SQLFactory/`SQLFun` ergänzt.
Bereichssemantik gemäß comment:6, je Schritt idempotent und einzeln committiert, in dieser Reihenfolge:
- Referenz-Pins: _REV-Spalten historischer und gemischter Referenzen innerhalb (Y, X) werden X`. Die Marker `Long.MAX_VALUE (gemischte Referenz auf den aktuellen Stand) und 0` (leere Referenz) liegen konstruktionsbedingt außerhalb jedes Bereichs. Anschließend werden '''ins Leere zeigende Pins geleert''': Ein auf `X zeigender Pin, dessen Zielobjekt zu X` keine Zeile hat (Zielobjekt innerhalb des Bereichs gelöscht), wird auf die Null-Darstellung einer Referenz gesetzt (`_ID Null-ID, _TYPE null, _REV/`_BRC` 0). Die Existenzprüfung liest die Kandidaten (nur Zeilen, die den Lauf überleben) und prüft je Zieltyp mit IN-Mengen; das Leeren erfolgt als Batch-Update über den Primärschlüssel. Ein solcher Verweis liefert danach null.
- Pro Item-Tabelle: Zeilen vollständig innerhalb (Y, X) werden gelöscht, in Fenstern über REV_MAX mit einem Commit je Fenster (#28053); danach REV_MIN und REV_CREATE innerhalb des Bereichs auf X`, `REV_MAX innerhalb des Bereichs auf X - 1.
- FLEX_DATA: gleiches Löschen und Umschreiben von REV_MIN/`REV_MAX`.
- BRANCH: BASE_REV und Erzeugungsrevision innerhalb des Bereichs auf X.
- REVISION / REVISION_XREF: Revisionen innerhalb des Bereichs werden gelöscht; Revision X` erhält den System-Autor und die Meldung "History compacted". XRef-Einträge innerhalb des Bereichs werden gelöscht, die Einträge für `X werden aus allen Item-Tabellen mit REV_MIN = X oder REV_MAX = X - 1 neu aufgebaut (Löschen und INSERT ... SELECT DISTINCT wie in MigrationContext.revalidateXRef; eine Änderung nur an Flex-Attributen schreibt ebenfalls eine neue Item-Zeile, so dass die Item-Tabellen dafür ausreichen).
Ausführung
HistoryCompactionOperation (com.top_logic.knowledge.service.compact) führt die Kompaktierung auf der Standard-Knowledge-Base der laufenden Anwendung aus:
- Voraussetzungen für die echte Kompaktierung: aktives Wartungsfenster; im Cluster-Betrieb muss dieser Knoten der einzige aktive Knoten sein (andere Knoten würden veraltete Caches behalten, ein entfernter Neustart existiert nicht). Bei Verletzung bricht die Operation mit einer Meldung ab. Die Analyse (Trockenlauf) hat keine Voraussetzungen.
- Die Kompaktierung läuft bei laufender Knowledge-Base innerhalb des Wartungsfensters (keine Benutzersitzungen; der Datenstand der aktuellen Revision wird nicht verändert). Anschließend wird das Modul KnowledgeBaseFactory neu gestartet, damit die Caches für Items, Revisionen und Branches verworfen werden. Der Neustart beendet alle Sitzungen und alle abhängigen Dienste (auch den Scheduler) und wird deshalb über den SchedulerService wenige Sekunden später ausgeführt, nie auf dem auslösenden Thread. Das Wartungsfenster ist eine Cluster-Eigenschaft, die beim Start neu gelesen wird, und überdauert den Neustart.
Administrationsaktion
Wartungsseite CompactHistory.jsp (jsp/administration/maintenance/, Administration > Technik > Wartung) nach dem Muster der Export-/Import-Seiten: CompactHistoryCommand öffnet CompactHistoryDialog mit dem Stichtag (Vorgabe: ein Jahr zurück). "Analysieren" zeigt den Bericht des Trockenlaufs (Kompaktierungsrevision, Zeilenzahlen je Tabelle) in einem Fortschrittsdialog. "Komprimieren" prüft die Voraussetzungen, fragt nach Bestätigung, führt die Kompaktierung aus und zeigt den Bericht; mit dem Schließen des Berichts wird die Persistenzschicht neu gestartet und die Sitzung beendet. Das Wartungsfenster wird vom Administrator vorher manuell betreten und nachher verlassen.
Geplanter Task
CompactHistoryTask (TaskImpl, @InApp) mit konfigurierter Aufbewahrungsdauer (retention-period); kompaktiert die Historie vor "jetzt minus Aufbewahrungsdauer" und stößt danach den Neustart der Persistenzschicht an. needs-maintenance-mode ist für diesen Task standardmäßig aktiv, d.h. der Scheduler betritt das Wartungsfenster vor dem Lauf (mit der konfigurierten Verzögerung) und verlässt es danach; run-on-startup ist standardmäßig aus, damit ein verpasster Termin die Kompaktierung nicht beim Systemstart auslöst. Ein manuell ohne Wartungsfenster gestarteter Lauf scheitert mit der Voraussetzungsmeldung. Die Demo-Anwendung registriert den Task deaktiviert zum manuellen Auslösen.
Tests
TestHistoryCompaction (AbstractDBKnowledgeBaseTest, mit Branches): Objekte mit mehreren Versionen, vor dem Schnitt gelöschte Objekte, historische und gemischte Pins (monomorph, polymorph mit Untertyp, branch-global), Branch; nach der Kompaktierung: aktueller Stand und Stand zu C` unverändert, Revisionen und Zeilen unterhalb `C gelöscht, Erzeugungsrevision C`, überlebende Pins auf `C, ins Leere zeigende Pins geleert und null, Branch-Revisionen angehoben, XRef für C` (auch für einen nur in Flex-Attributen geänderten Typ), Revision `C als System-Commit, zweiter Lauf leer, Trockenlauf gleich dem echten Lauf, kleines Löschfenster, Bereichsform mit Y > 0, Stichtagsauflösung. Manuell verifiziert in der Demo-Anwendung: Analyse, Voraussetzungsfehler ohne Wartungsfenster, echte Kompaktierung mit Neustart und erneuter Anmeldung, leerer zweiter Lauf, manuell ausgelöster Task.
Grenzen
- Von Anwendungen als einfache Zahl gespeicherte Revisionsnummern < C können nicht umgeschrieben werden.
- Andere Cluster-Knoten werden nicht neu gestartet; die Operation verweigert den Lauf, solange sie aktiv sind.
- Das gezielte Purge gelöschter Objekte (#29603) ist nicht enthalten; nur ItemTables ist gemeinsam.
- Lokal wurde nur H2 getestet; die neuen Statements (IN-Mengen, Batch-Update, INSERT ... SELECT DISTINCT mit Literalen, count) sind auf Oracle/PostgreSQL/MySQL/MSSQL nicht ausgeführt worden.