Transaktionen, die mehr als einen Ressource-Manager umfassen
Typische Ressource-Manager sind Datenbank- oder Messaging-Systeme
ACID muss wie bei nicht verteilten Transaktionen gewahrt werden
Alle RM müssen
Zwei-Phasen-Commit-Protokoll (2PC)
Distributed-Transaction-Processing-Modell (DTP)
TX-Spezifikation
XA-Spezifikation
Verschiedene Unternehmenseinheiten für Giro- und Sparkonten
Jeweils eigene IT-Infrastruktur
executing
prepared
aborting/aborted
commiting/committed
executing
prepared
aborting, commiting
aborted/committed
RM hat bei prepare mit yes geantwortet
TM stürzt ab
Pragmatische Lösung für diesen Fall: RM können von sich aus, d. h. heuristisch, eine Entscheidung zum Ausgang der Transaktion treffen, entweder commit oder abort
Überweisungs-Szenarien, in dem zwei Postgres-Datenbanken in einer Transaktion zusammenspielen.
Szenario 1
Szenario 2
Informationen dazu in der psycopg-Dokumentation.
Wichtig, um Konflikte mit dem ersten Server zu vermeiden:
networks:
adbkt:
external: true
services:
pg2:
container_name: pg2
image: postgres:latest
ports:
- 5433:5432
environment:
POSTGRES_PASSWORD: htw-bln-pg
networks:
- adbkt
In pg und pg2 durchführen
su postgres
psql postgres
ALTER SYSTEM SET max_prepared_transactions = 100;
Container stoppen und starten (restart)
SHOW max_prepared_transactions;
select relation::regclass, mode from pg_locks;
def create_kto(conninfo):
sql1 = "drop table if exists kto"
sql2 = """
create table kto (
kid integer not null,
val integer not null
)
"""
with psycopg.connect(conninfo) as conn:
conn.execute(sql1)
conn.execute(sql2)
def reset_kto(conninfo, kid, val):
sql1 = "delete from kto"
sql2 = f"insert into kto values ({kid}, {val})"
with psycopg.connect(conninfo) as conn:
conn.execute(sql1)
conn.execute(sql2)
def show_kto(conninfo):
sql = "select * from kto"
with psycopg.connect(conninfo) as conn:
rs = conn.execute(sql).fetchall()
return rs
def print_all():
print(f"pg1: {show_kto(conninfo1)}")
print(f"pg2: {show_kto(conninfo2)}")
def prepare(db, conninfo, xid, kid, mode, updval):
sql = f"update kto set val = val {mode} {updval} where kid={kid}"
print(f"{db}: {sql}")
conn = psycopg.connect(conninfo)
conn.tpc_begin(xid)
conn.execute(sql)
conn.tpc_prepare()
conn.close()
def commit(conninfo, db, xid):
print(f"{db}: commit")
with psycopg.connect(conninfo) as conn:
conn.tpc_commit(xid)
def rollback(conninfo, db, xid):
print(f"{db}: rollback")
with psycopg.connect(conninfo) as conn:
conn.tpc_rollback(xid)
import psycopg
conninfo1 = " ".join([
"user='postgres'",
"password='htw-bln-pg'",
"host='pg'",
"port=5432",
"dbname='postgres'"])
print(conninfo1)
conninfo2 = " ".join([
"user='postgres'",
"password='htw-bln-pg'",
"host='pg2'",
"port=5432",
"dbname='postgres'"])
print(conninfo2)
xid = "txn1"
create_kto(conninfo1)
create_kto(conninfo2)
reset_kto(conninfo1, 1001, 200)
reset_kto(conninfo2, 1002, 500)
print_all()
user='postgres' password='htw-bln-pg' host='pg' port=5432 dbname='postgres'
user='postgres' password='htw-bln-pg' host='pg2' port=5432 dbname='postgres'
pg1: [(1001, 200)]
pg2: [(1002, 500)]
reset_kto(conninfo1, 1001, 200)
reset_kto(conninfo2, 1002, 500)
prepare("pg1", conninfo1, xid, 1001, "-", 100)
prepare("pg2", conninfo2, xid, 1002, "+", 100)
print_all()
pg1: update kto set val = val - 100 where kid=1001
pg2: update kto set val = val + 100 where kid=1002
pg1: [(1001, 200)]
pg2: [(1002, 500)]
Durch das "prepare" sind beide Datenbanken bereit für "commit" oder "rollback". Die Daten sind aber noch nicht verändert, da die Transaktion noch nicht betätigt wurde.
Das print_all() öffnet eine neue Verbindung und sieht daher die unveränderten Daten (Snapshot).
commit(conninfo1, "pg1", xid)
commit(conninfo2, "pg2", xid)
print_all()
pg1: commit
pg2: commit
pg1: [(1001, 100)]
pg2: [(1002, 600)]
Das "commit" kann auch dann noch erfolgreich durchgeführt werden, wenn der Server zwischenzeitlich abstürzt.
reset_kto(conninfo1, 1001, 200)
reset_kto(conninfo2, 1002, 500)
prepare("pg1", conninfo1, xid, 1001, "-", 100)
prepare("pg2", conninfo2, xid, 1002, "+", 100)
print_all()
pg1: update kto set val = val - 100 where kid=1001
pg2: update kto set val = val + 100 where kid=1002
pg1: [(1001, 200)]
pg2: [(1002, 500)]
rollback(conninfo1, "pg1", xid)
rollback(conninfo2, "pg2", xid)
print_all()
pg1: rollback
pg2: rollback
pg1: [(1001, 200)]
pg2: [(1002, 500)]