{ "conf": "FOSSGIS 2015", "where":"Muenster", "when":"2015-03-12T15:00:00+01:00"}
{"geometry": {"type": "Point", "coordinates": [7.613056, 51.9637] }}
Hintergründe
Motivation
Umsetzung
Roadmap
Versionsverwaltung eines proprietären DBMS
Lineare Versionierung mit Zeitstempel
Erlaubt Arbeiten in verschiedenen "Workspaces"
Merging von Workspaces ist möglich
In der Theorie super, in der Praxis ... schwierig ...
Klare Trennung von Produktions- und Archivdaten
Transparenz bei archivierten Daten
Datenbankperformance darf nicht leiden
Versionsverwaltung für PostgreSQL in der Datenbank
Lineare Versionierung durch getriggerte Aufzeichnungen
Zugriff auf komplette frühere Versionen nur über Ausspielung in separates Schema einer Datenbank (entweder als Sicht oder Tabelle)
Aber: Direktes Revertieren einzelner Transaktionen
Hierarchische Versionierung ebenfalls über Schemata lösen
neue API seit 9.4 für Zugriff auf Replikationsströme von Daten
dekodiert die Aufzeichnungen des Write-Ahead-Log
ermöglicht Bidirektionale Replikation >> Multi-Master-Cluster
Ausspielung von Veränderungen in Replication Slots
Auditing und Versionierung möglich
Output plugins: WAL-Format >> gewünschtes Format
Weitere Infos: Doku, Postgres Open, FOSDEM, PgConfRu
Tabellenzeile >> row_to_json(OLD) >> JSON
JSON >> json_populate_record(null::test_table, JSON) >> Tabellenzeile
PL/pgSQL Skripte ausführen
Trigger anlegen
Zusätzliche Audit_ID Spalte anlegen
Bestehende Daten als "eingefügt" loggen
transaction_log ( txid BIGINT, stmt_date TIMESTAMP, user_name TEXT, client_name TEXT); table_event_log ( id SERIAL, transaction_id BIGINT, op_id SMALLINT, table_operation VARCHAR(8), schema_name TEXT, table_name TEXT, table_relid OID); row_log ( id BIGSERIAL, event_id INTEGER, audit_id BIGINT, changes JSONB);
UPDATE building SET measured_height = 12.8 WHERE id = 1;
CREATE INDEX row_log_changes_idx ON pgmemento.row_log USING GIN (changes);
SELECT t.txid
FROM pgmemento.transaction_log t
JOIN pgmemento.table_event_log e ON t.txid = e.transaction_id
JOIN pgmemento.row_log r ON r.event_id = e.id
WHERE
r.audit_id = 4
AND
(r.changes ? 'measured_height');
-> Index Scan using row_log_audit_idx on row_log r
Index Cond: (audit_id = 4)
Filter: (changes ? 'measured_height'::text)
SELECT DISTINCT audit_id FROM pgmemento.row_log
WHERE
changes @> '{"measured_height": 0.0}'::jsonb;
-> Bitmap Index Scan on row_log_changes_idx
Index Cond: (changes @> '{"measured_height": 0.0}'::jsonb)
SELECT pgmemento.restore_schema_state(
1002, -- Zeitpunkt vor Transaktions ID
'citydb', -- Quellschema
'state_before_delete', -- Zielschema
'TABLE', -- Wiederherstellungstyp
ARRAY['spatial_ref_sys'] -- Ausnahmen
);
) -- hier endet WITH Block, der valide audit_ids sammelte
SELECT v.log_entry FROM valid_ids f
JOIN LATERAL ( -- muss für jede audit_id erfolgen
SELECT json_build_object(
q1.key, q1.value,
q2.key, q2.value,
...)::jsonb AS log_entry
FROM ( -- Abfragen nach alten Werten (vorheriger Schritt)
SELECT q.key, q.value FROM (
SELECT 'id' AS key, r.changes -> 'id'
FROM pgmemento.row_log r ...
)q ) q1,
SELECT q.key, q.value FROM (
SELECT 'name' AS key, r.changes -> 'name'
FROM pgmemento.row_log r ...
)q ) q2, ...
) -- die vorherigen Schritte erfolgten in einem WITH Block
SELECT p.* FROM restore rq
JOIN LATERAL (
SELECT * FROM jsonb_populate_record(
null::citydb.building, -- Template
rq.log_entry -- zusammengesetztes JSONB
)
) p ON (true)
SELECT * FROM (
(SELECT r.audit_id, r.changes, e.schema_name, e.table_name, e.op_id
FROM pgmemento.row_log r
JOIN pgmemento.table_event_log e ON r.event_id = e.id
JOIN pgmemento.transaction_log t ON t.txid = e.transaction_id
WHERE t.txid = $1 AND e.op_id > 2 -- DELETE oder TRUNCATE
ORDER BY r.audit_id ASC) -- die ältesten Werte zuerst einfügen
UNION ALL
(SELECT ...
WHERE t.txid = $1 AND e.op_id = 2 -- UPDATE
ORDER BY r.audit_id DESC) -- die jüngsten Werte zuerst aktualisieren
UNION ALL
(SELECT ...
WHERE t.txid = $1 AND e.op_id = 1 -- INSERT
ORDER BY r.audit_id DESC -- die jüngsten Werte zuerst löschen
) txid_content
ORDER BY op_id DESC -- erst DELETEs, dann UPDATEs, dann INSERTs verarbeiten
Revertieren bisher etwas zu simpel
Filter bei Restore (nach Zeit, BBox etc.)
Kein Tracking von DDL-Befehlen
Kein Merging zwischen Schemata
Wenig Benchmarking
Mehr Restriktionen und Sicherheit der Logs
Keine GUI, reines SQL-Coding
Für schreiblastige Datenbanken wahrscheinlich ungeeignet, da hoher Speicherverbrauch (obwohl nur Deltas gespeichert werden)
Log-Tabellen sind leicht angreifbar und kein Ersatz für Backups und Recovery Werkzeuge
https://github.com/pgMemento/pgMemento
Kontakt:
Felix Kunde
fkunde@virtualcitysystems.de
+49(0)30 890 48 71 - 42