Wo ist denn der Stift?
Ah, Stift.
So,
Vorlesung.
Zunächst möchte ich mich entschuldigen, dass ich gestern kurzfristig
verhindert war, aber die Vorlesung war ja bei Frau Müller in den
besten Händen aufgehoben.
Wir hatten nur so vereinbart, dass sie den Teil nimmt, der ein
bisschen einfacher ist und dass ich mich entgegen der Reihenfolge
jetzt nochmal mit den unteren Schichten beschäftige.
Wir sind also an folgender Stelle.
Das war ja meine Referenzarchitektur, die wir entwickelt hatten.
Hier ist schon angedeutet, dass den Teil, mit dem wir uns sehr
ausführlich beschäftigt, insbesondere als wir mit den Datenmodellen
gearbeitet haben, mit dem Schedule hatten wir uns im Wesentlichen
beschäftigt, als wir uns mit den Transaktionen beschäftigt hatten.
Und Frau Müller hat im Wesentlichen diesen Teil hier behandelt.
Und mir bleibt deshalb zunächst einmal jetzt dieser Teil hier übrig.
Der wird häufig auch einfach als Speichermaschine bezeichnet.
Das heißt, all die Dinge werden zusammengefasst unter dem Begriff der
Speichermaschine.
Und es wird auch davon her, weil, wenn Sie sich erinnern, als wir die
Architektur entwickelt haben, da gab es so einen Schnitt.
Hier hatten wir von oben her die Architektur entwickelt und hier von
unten her.
Also das ist eigentlich auch ein ganz natürlicher Schnitt, an dem wir
den Übergang von einer reinen Betrachtung, die im Wesentlichen von den
Medien her bestimmt ist, wechseln, überwechseln zu dem Teil, der sich
stärker an den Inhalten orientiert.
So, ich würde also die im Wesentlichen Speichermaschine behandeln und
im Wesentlichen sogar mich mit der Recovery- und Segmentverwaltung
beschäftigen.
Aber wir sollten kurz etwas noch darüber aussagen, wie die
Dateiverwaltung, was wir da für Annahmen machen.
Die physische Dateiverwaltung, die geht also von den
Speicherressourcen aus und die zwei wesentlichen Speicherressourcen,
die uns zur Verfügung stehen, sind der nicht-flüchtige
Hintergrundspeicher, ein sehr langsames Medium, wie wir wissen, und
der flüchtige Hauptspeicher, ein vergleichsweise schnelles Medium,
wenn wir uns noch erinnern, wir haben hier eine Größenordnung von 10
auf 5 zu überwinden.
Und die Speichermaschine hat also die Aufgabe, unabhängig von den
Inhalten erstmal zu sehen, wie es ihr gelingt, diese Speicherlücke von
10 auf 5, oder Zugriffslücke von 10 auf 5, wenigstens um zwei, wenn
nicht drei Größenordnungen zu reduzieren.
Da sie an der Physik nicht drehen kann, kann sie das nur mit
organisatorischen Mitteln tun.
Eines, was wir in der Dateiverwaltung immer vorhaben, ist, dass wir
den darüberliegenden Schichten ein völlig einheitliches Bild der
Hintergrundspeicher geben.
Das heißt, wir versuchen zunächst einmal in der Dateiverwaltung zu
verdecken, dass die Hintergrundspeicher selbst sehr unterschiedliche
Eigenschaften haben.
Platte ist auch nicht gleich Platte, ein nicht-flüchtiges
Speichermedium kann eine Diskette sein, kann eine CD-ROM sein, kann
ein großer Magnetplattenspeicher sein, ein kleiner IMPC.
Die haben alle etwas unterschiedliche Eigenschaften.
Diese Eigenschaften sollen tunlichst verdeckt werden, sodass wir ein
einheitliches Bild haben.
Es geht sogar so weit, dass wir gelegentlich noch auf Magnetbänder,
obwohl sie ja reichlich ungebräuchlich sind, noch ein einheitliches
Bild werfen wollen.
Und die einheitliche Sicht besteht darin, dass wir zunächst einmal
eine Einheit haben, in der wir die Daten zwischen dem
Hintergrundspeicher und dem Hauptspeicher übertragen.
Das ist der physikalische Block.
Und wir formatieren unsere Hintergrundspeicher so, dass sie eben diese
Blockgröße oder diese Blockorganisation widerspiegeln.
Und ein solcher physikalischer Block hat auch eine bestimmte Länge.
Die ist durch die feste Formatierung, die wir heute auf den
Speichermedien vorsehen, eigentlich auch fest vorgegeben.
Was uns dann für die Zeiten selbst interessiert, ist die Latenzzeit.
Das heißt, wie lange dauert es, bis ich überhaupt an die Daten
rankomme.
Dann die Übertragungsbandbreite, die noch besagt, wie lange dauert es
denn, wenn ich mal den Block, den physikalischen Block, lokalisiert
habe, wie lange dauert es denn, bis ich den im Hauptspeicher habe.
Das ist auch, trotz der hohen Übertragungsbandbreite, die wir heute
haben, bei längeren physikalischen Blöcken durchaus auch noch ein
Faktor, der eine Rolle spielt.
Nichtsdestotrotz, die Latenzzeit ist eigentlich das, was uns erheblich
mehr Sorgen bereitet.
Und die Zukunftszeit besteht halt dann aus beiden zusammen.
So, ich spreche jetzt nicht nur darum, dass wir sagen, wie sieht am
Ende auf der Dateiverwaltung jetzt unser Modell aus.
Vielleicht gehe ich doch nochmal eine Folie zurück.
Wenn wir so eine einheitliche Sicht haben, dann haben wir die
Speichergeräte, die sind verdeckt, wir haben jetzt hier diese
Vereinheitlichung.
Und mit dem Wesentlichen darin, dass wir eine Menge physikalischer
Blöcke haben, die alle in ihrer Länge übereinstimmen und die sich auch
selbst im Laufe der Zeit in ihrer Länge nicht verändern.
Und das, was wir dann an Diensten anbieten, sind blockorientierte
Dateien.
Jetzt ist es aber so, dass wir aus Anwendungsgründen, wir haben das
etwa gesehen, das hat Ihnen ja Frau Müller gestern gezeigt, es ist ja
sehr wesentlich, dass wir geschickt die Größe unserer Seiten wählen,
auf denen wir unsere physikalischen Strukturen aufbauen.
Und diese Seitenlängen sind häufig sinnvoll, sehr viel größer zu
wählen, als die physikalischen Blöcke.
Was wir dann Spoonische Blöcke nennen.
Nämlich eine anwendungsgerechte Länge.
Und infolgedessen, die blockorientierten Dateien, die wir jetzt
einführen, die an sich ansonsten alles verdecken sollen, die kennen
jetzt nicht mehr den physikalischen Block, sondern den logischen.
Und der logische Block besteht aus am Gehörigen, auch als
Zusammengehörigen markierten Menge physikalischer Blöcke.
Und man kann übrigens auch schon geschickte Maßnahmen die
Zukunftslücke etwas überwinden, wenn wir nämlich Sorge dafür tragen,
dass die Latenzzeiten minimiert werden oder nahezu nicht mehr fühlbar
sind.
Und das kann man zum Beispiel durch ganz geschickte, verschränkte
Anordnung der physikalischen Blöcke auf der Platte erreichen.
Wenn man sie hintereinander schreibt, dann ist das unter Umständen
sehr schlecht, weil man jedes Mal wieder erneut die Latenzzeit
abzuwarten hat, nämlich einmal die Umdrehung abzuwarten hat.
Wenn man es aber ganz geschickt und verschränkt unterbringt, dann kann
man zum Beispiel sofort auf den nächsten Block überspringen, selbst
wenn inzwischen einiges an Verarbeitung erfolgt ist.
Wir haben also den logischen Block und die physische Datei besteht
dann darin, dass wir unsere Hintergrundspeicher, die ganze Menge der
Hintergrundspeicher, geschickt unterteilen in sogenannte logische
Geräte, die eben aus solchen logischen Blöcken bestehen.
Wir haben noch etwas weiteres.
Wir wissen ja, dass unsere Mengen dynamisch sind.
Die Idee aller Datenbanksysteme ist ja immer, diese Skalierbarkeit
durchzusetzen über das Mengenkonzept.
Nun, dieses Mengenkonzept heißt also, dass unsere Mengen insbesondere
schrumpfen können.
Das tun sie aber sehr selten, aber meistens tendieren sie dazu zu
wachsen.
Dieses Wachstum, das wollen wir tunlichst auch schon möglichst nah an
der Geräteebene leisten.
Und das erfolgt dadurch, dass wir die Dateien so anordnen, dass sie
einmal logische Blöcke liefern, aber dass sie zum zweiten auch eine
dynamische Größenanpassung leisten.
Das erfolgt auch wieder üblicherweise dadurch, dass wir immer in
größeren Einheiten, also in Zylinder, mehrere Zylinder oder eine
bestimmte Anzahl logischer Blöcke, gemeinsam betrachten und wir immer
dann, wenn Bedarf ist an Wachstum, einfach wieder eine Einheit,
mehrere Zylinder oder eine große Zahl logischer Blöcke, zusätzlich
allozieren.
Auf die Art erhalten wir ein dynamisches Wachstum und die Schichten
oben drüber, die brauchen sich dann um dieses dynamische Wachstum
nicht mehr zu kümmern.
Die sagen einfach, ich brauche mehr und dann kriegen sie halt mehr.
Die sagen sogar nicht mal, ich brauche so und so viel mehr, sondern
die sagen üblicherweise überhaupt, holen sich nur noch den nächsten
logischen Block und sollte sich herausstellen, dass keiner mehr
vorhanden ist, dann wird unsichtbar für die höheren Schichten, wird
einfach die Datei erweitert.
Das leisten wir also alles auf der Ebene der blockorientierten Dateien
und was sie eben anbieten an Funktionalität ist das Erzeugen und
Zerstören von Dateien, indem sie eben dann das Wachstum durch Zuordnen
von Speicherbereichen, dann werden Dateien geöffnet und geschlossen.
Das ist einfach um gewisse Verwaltungsmaßnahmen durchzusetzen.
Und dann gibt es eben Operationen und zwar sowohl zum wahlfreien
Zugriff, synchron oder asynchron.
Da gibt es eine ganze Menge Varianten und das zeigt schon, dass wir im
Grunde genommen, wenn wir oben drüber aufbauen, eigentlich eine Menge
an Instrumentarium haben, alles sehr primitiv, aber doch hinreichend
umfangreich, sodass wir ganz unterschiedliche Strategien oben
implementieren können.
Wir verlangen also, und das erfüllt die moderne Dateiverwaltung sehr
gut, wir verlangen, dass wir möglichst viel Spielraum haben, um weiter
oben ganz spezifische Strategien fahren zu können.
Ein ganz wesentlicher Punkt ist noch der folgende.
Wir müssen ja Atomicität durchsetzen.
Also Transienz und Persistenz.
Und dazu müssen wir verlangen, dass irgendwo uns eine Mindestgarantie
gegeben wird, was die Korrektheit der Transportvorgänge auf den
Hintergrundspeicher oder vom Hintergrundspeicher in den Hauptspeicher
angeht.
Und das ist die sogenannte Speicherkonsistenz.
Wir haben keine Chance, irgendeine Robustheit durchzusetzen, wenn wir
nicht unten auf der Geräteebene ein Minimum an Robustheit durchsetzen
können.
Und dieses Minimum an Robustheit ist die Speicherkonsistenz, die
besagt einfach folgendes, es wird uns garantiert, dass wenn wir einen
Transportvorgang einleiten, sagen wir zunächst mal vom Hauptspeicher
in den Hintergrundspeicher, dass dann der Transportvorgang vollständig
oder überhaupt nicht stattfindet.
Dass der also selbst auch atomar ist.
Und zum Zweiten, was das Lesen angeht, was das Lesen angeht, es gibt
ja Alterungserscheinungen.
Da fällt halt mal irgendein Bit aus oder kippt um.
Dann müssen wir sicher gehen können, dass wenn immer gelesen wird und
es ist in der Zwischenzeit zu Beschädigungen gekommen, dass wir dann
eine zuverlässige Meldung darüber erhalten, dass es zu einem Fehler
gekommen ist.
Noch schöner wäre es übrigens, wenn wir fehlerkorrigierende Maßnahmen
hätten, dass der Fehler erst gar nicht sichtbar wird.
Dass er im Gerät bzw.
in den Steuereinheiten korrigiert wird, aber im Mindestfall verlangen
wir, dass wir eine Meldung erhalten, dass inzwischen ein Fehler
vorliegt.
Wenn ein Fehler vorliegt, dann haben wir einen Medienfehler und den
müssen wir wieder irgendwie über die Transaktionsverwaltung dann
abfangen.
So, das sind die Voraussetzungen, die wir machen, um jetzt mit unserer
Speichermaschine mit der Segmentverwaltung aufzusetzen.
Die Segmentverwaltung ist jetzt der Teil, der ganz spezifisch auf den
Datenbankbereich zugeschnitten ist.
Die Grundlage aller Implementierungen, insbesondere auch der
Implementierung, die Frau Müller gestern vorgeführt hat.
Die Segmentverwaltung, wissen wir ja schon, umfasst mehrere Teile.
Die umfasst, wie wir ja wissen, insbesondere einmal den Scheduler, den
wir schon erledigt haben, aber sie beinhaltet den Recovery-Verwalter.
Das ist nämlich der Teil, der jetzt für die Atomicität zu sorgen hat.
Ohne die funktioniert auch unsere Transaktionsverwaltung ja nicht.
Wir haben bisher nur Annahmen gemacht, dass der Datenverwalter das
schon für uns leisten wird, diese Atomicität herzustellen.
Und wir haben die Segment- und Pufferverwaltung, die dazu dient, die
Zukunftslücke noch weiter zu überwinden.
Also zwei sehr unterschiedliche Aufgaben.
Das wird auch sich herausstellen, das ist ein Problem.
Jeder der beiden hat nämlich seinen eigenen Verantwortungsbereich und
jeder dieser Teile, dieser Komponenten, versucht auch seinem
Verantwortungsbereich auf seine Weise gerecht zu werden.
Und lässt ihn natürlich am besten nicht von anderen reinreden, wie er
das zu tun hat oder irgendwie ausbremsen, denn dann kann er die
Verantwortung nicht mehr voll übernehmen.
Also haben wir jetzt zunächst mal mit zwei völlig unterschiedlichen
Aufgaben zu tun, die aber dummerweise eben doch etwas zusammenhängen.
Also werden wir, obwohl die beiden autonom sind, muss es am Schluss
doch zwischen den beiden noch irgendeine Art von Kooperation geben,
damit sie sich nicht gegenseitig stören, sondern damit sie sich bei
ihren gemeinsamen Belangen auch gegenseitig unterstützen.
Das wird ein gewisses Problem sein.
Wir werden sehen, wie das dann üblicherweise gelöst wird.
Dazu können wir gleich mal die Architektur ansehen.
Was wir schon kennen, ist der Teil hier, unser Scheduler.
Den haben wir ja kennengelernt.
Der soll am besten rigorose Schedules erzeugen und zudem soll er auch
noch Wiederanlaufbarkeit garantieren, also sowohl CSR als auch CRRC
garantieren.
Und wir haben ja gesehen, das macht er am besten, indem er dann ein
strenges Zwei-Phasen-Sperrprotokoll fährt.
Also wird auch wieder ein eigener Sperrenverwalter übertragen.
Es gibt einen Sperrenverwalter, der jetzt diese Warteschlangen
verwaltet, von denen, wenn Sie sich erinnern, wir ja auch eine gewisse
Strategie fahren können, wie wir einen möglichst optimalen,
kostenoptimalen Schedule erzeugen können.
So, dann gibt es den Segmentverwalter selbst.
Da werden wir sehen, dessen Aufgabe ist im Wesentlichen einfach die
Anweisungen, die er vom Scheduler erhält.
Er erhält ja so eine globale Historie, einen globalen Schedule in
unserem Fall.
Mit den entsprechenden Anweisungen, nur einfach umgeordnet.
Und der Segmentverwalter entscheidet dann, was er zu tun hat.
Er muss also insbesondere auch zu unserem Backup-Recovery-Verwalter
gehen, um gewisse Atomicitätseigenschaften durchzusetzen.
Er gibt ansonsten die Anweisungen weiter an den Pufferverwalter.
Denn der Pufferverwalter ist derjenige, der eben jetzt diese
Zugriffsstücke überwinden muss.
Der soll also dafür Sorge tragen, dass möglichst alles, was gebraucht
wird, auch schon im Hauptspeicher vorhanden ist.
Wir sehen, dass der Pufferverwalter mit zwei Dateien arbeitet.
Erstmal mit der Datenbasis selbst, an dieser Stelle.
Und dann hat er noch etwas anderes, nämlich eine Protokolldatei.
Wir werden noch sehen, diese Protokolldatei spielt jetzt für die
Atomicität eine entscheidende Rolle.
Dort führen wir einfach Zusatzinformationen, meistens redundant, um im
Fehlerfall einfach wieder auf einen definierten Zustand aufsetzen zu
können, sprich die Datenbasis wieder herzustellen.
Und da das auch ein Hintergrundspeicher ist, meist eine spezialisierte
Platte, hilft nichts, auch da müssen wir eben durch den Puffer
durchgehen.
Und wenn das System abgestürzt wird, dann haben wir eben eine Restart
-Operation.
Die geht jetzt zu diesem Recovery-Verwalter, der dann die
Protokolldatei nutzt, aber auch durch den Puffer durchläuft.
Das ist eine relativ komplizierte Architektur, insbesondere wenn man
sich an den Details macht, stecken dahinter ausgekochte Algorithmen.
Und dort entscheidet sich ja häufig die Wettbewerbsfähigkeit,
zumindest was die Leistungsfähigkeit von Systemen angeht, zwischen den
verschiedenen Anbietern.
Es ist also ganz interessant sich anzusehen, soweit man das überhaupt
in Erfahrung bringen kann, was Oracle an dieser Stelle tut, das sind
monströse Systeme, was IBM mit DB2 tut an dieser Stelle oder was
Microsoft mit SQL Server tut oder was meinetwegen auch die Software AG
mit Tamino tut, wo da ist eine spezielle XML-Datenbasis.
Die haben alle irgendwo so ungefähr diese Struktur in ihrer
Speichermaschine, darunter liegt dann unsere Dateiverwaltung, und
hinter diesen einzelnen Komponenten stecken häufig ganz ausgekochte
und sehr komplizierte Algorithmen.
Ich werde nur die einfachsten zeigen.
So, jetzt ist noch die Frage, was kriegt denn eigentlich unser
Scheduler zu sehen?
Der kriegt ja die Anweisungen von oben runtergereicht, ist also die
Schnittstelle eigentlich zu den anwendungsorientierten Schichten,
beginnend mit den physischen Datenstrukturen, und jetzt muss man sich
einigen, was reichen wir denn da, wie sehen denn unsere Dienste, die
wir nach oben geben, aus?
Und das ist sehr einfach.
Wir haben also ganz offensichtlich Read, Write, Abort und Commit zu
geben, aber mit welchen Einheiten haben wir es denn zu tun?
Und da wir noch nichts über Inhalte wissen, werden wir, erinnert sich,
als Literaturabgeleiter habe ich das schon argumentiert, nehmen wir
jetzt unsere logischen Blöcke und reichen die nach oben hin und
versuchen aber diese logischen Blöcke jetzt zusammenzufassen in
sogenannten Segmenten.
Das heißt, alles was wir als irgendwie sinnvollerweise zusammengehörig
ansehen, das wird in Segmenten zusammengefasst oder bei anderen heißt
es DB Area, was immer die Bezeichnungen sind.
Es sind jedenfalls Einheiten, die auch der physischen Steuerung und
der Verteilung auf die Hintergrundspeicher dienen.
Von denen wir sagen, die wollen wir aber gemeinsam benutzen.
Jetzt sprechen wir aber nicht mehr von logischen Blöcken, sondern von
Seiten.
Und das liegt daran, dass wir unsichtbar machen wollen, wenn also von
oben so eine Anforderung kommt, Read, wo sich die Seite befindet.
Wir hoffen ja, dass sie sich im Hauptspeicher befindet.
Geben aber unseren Anwendungen weiter oben immer das Bild eines
homogenen Speichers, von dem wir gar nicht weiter sagen, wo sich im
Augenblick die einzelnen Blöcke befinden.
Und um auszudrücken, dass wir ein homogenes Bild geben, das völlig
verdeckt, wo sich die Seite im Augenblick befindet.
Das heißt, wie groß im Augenblick die Zugriffszeit ist, sprechen wir
nicht mehr von logischen Blöcken, sondern wir sprechen von Seiten.
Also wir haben die Seite, die nichts anderes ist als eine Abfolge von
Bytes.
Wir wissen überhaupt nicht, was da drin steht.
Einfach eine Abfolge von Bytes einer vorgegebenen Größe, nämlich der
Länge der logischen Blöcke.
Wir definieren uns also unsere Seitenlänge, das ist da, wo wir unseren
Einfluss nehmen können.
Auch da gilt heute, dass die Datenbanksysteme mit einer einheitlichen
Seitengröße arbeiten, weil wir sehen, dass die Pufferverwaltung
einfach so viel einfacher wird.
So, und wir fassen eben dann eine solche Menge von Seiten geordnet,
die werden einfach durchnummeriert, dann wird eine Ordnung aufgeprägt
in einem Segment zusammen.
Und das ist alles, was wir von außen sehen.
Wo das liegt, auf welchem Hintergrundspeicher, ob wir überhaupt auf
den Hintergrundspeicher zugreifen müssen, oder ob wir auf den
Hauptspeicher zugreifen müssen, all das verdecken wir völlig.
Das Einzige, was wir noch anbieten sind hier, wenn eine Seite
angefordert wird, dann liefern wir sie zurück.
Wenn ein Segment geöffnet wird, dann öffnen wir das Segment.
So, wir haben also jetzt folgende Operaturen, die wir anbieten.
Zunächst einmal Segmente, muss man euch definieren.
Ich muss sagen, ich richte ein neues Segment ein.
Ich kann es auch wieder mal freigeben.
Zum Beispiel, wenn wir etwa einen SQL-Ausdruck abarbeiten, dann läuft
er ja, wie wir wissen, über eine Vielzahl von algebraischen
Operationen.
Jede dieser algebraischen Operationen produziert eine neue Relation.
Und beispielsweise wird man da die Relationen auch, diese
Zwischenergebnisse, sehr umfangreich werden können, müssen die zum
Teil auch wieder auf den Hintergrundspeicher gebracht werden.
Werden also auch in so ein Segment eingepackt und haben dann eben
Seitenformat.
Und dann solche Segmente sind euch völlig uninteressant, wenn der
Ausdruck mal abgearbeitet ist.
Also kommt es durchaus vor, dass wir auch Segmente haben, die
transient sind und die wir freigeben können.
Dann müssen wir öffnen und schließen.
Das ist einfach üblich, weil wir einiges in Verwaltungsarbeit zu
leisten haben, bevor wir loslegen können bei gewissen
Sicherungsmaßnahmen.
Wir müssen die lokalisieren und so weiter.
Das ist alles beim Öffnen und Schließen dabei.
Dann haben wir ein Read.
Kennen wir ja schon aus unserer Transaktionsbetrachtung, unserem
Transaktionsmodell.
Und Read und Write heißen jetzt einfach, dass wir eine Seite X lesen
oder eine Seite X schreiben.
Und im Falle des Lesens erhalten wir zurück eine Hauptspeicheradresse.
Das heißt, wir transportieren üblicherweise nicht, zumindest bei den
Protokollen, die ich vorführen werde, transportieren wir nicht die
Seite in einen Arbeitsbereich der Transaktion, sondern der bleibt
einfach im Puffer liegen und wir geben nur die Adresse, wo sich die
Seite im Puffer befindet, zurück.
Da sieht man auch, da könnte ein schlechter Missbrauch getrieben
werden, da könnte eine zweite Transaktion kommen und natürlich
versuchen an die Seite heranzukommen.
Wenn wir Schutzmaßnahmen ergreifen wollen, dann müssen wir es ein
bisschen aufwendiger gestalten.
Aber im einfachsten Fall geben wir einfach die Hauptspeicheradresse
zurück und dann arbeitet die Transaktion eben unmittelbar im Puffer.
Hoffentlich eben so, dass sie dann auch die Seitengrenzen einhält.
Wenn wir schreiben, wenn wir sagen Write, dann wird am Ende diese
Anweisung allerdings im Puffer ausgeführt und der Pufferverwalter muss
die Seite auf den Hintergrundspeicher schreiben.
Und der Pufferverwalter arbeitet einfach dem Motto, ich bin
verantwortlich, die Zugriffslücke zu überwinden, das überlässt aber
gefälligst mir, wenn ich die Seite rausschreibe.
Das heißt, wenn wir als Transaktion, wenn die Transaktion sagt Write,
dann kann der Pufferverwalter sagen, schön, nehme ich zur Kenntnis,
aber lasst ihr mal Zeit und belässt einfach die Seite in den Puffer
und irgendwann kommt er mal dahin und sagt, jetzt ist ein guter
Zeitpunkt, um die auszuschreiben.
Das ist natürlich sehr lästig, dann überlegen sie sich mal folgendes,
die Transaktion glaubt natürlich, dass es geschrieben ist, schließt
auch schön ab mit Commit, anschließend bricht das System zusammen und
die Effekte sind überhaupt nicht mehr auf den Hintergrundspeicher
geraten.
Dann kann ich natürlich kommen und sagen, lieber Pufferverwalter, das
hättest du aber doch rausschreiben müssen, vor dem
Systemzusammenbruch, aber hat er eben nicht getan.
Wir sehen also, da ist ein Problem.
Wir müssen also jetzt sozusagen eingreifen und dem Pufferverwalter
erklären, so einfach geht das nicht, so viel Freiheit lasse ich dir
nicht als Segmentverwalter zum Beispiel, sondern ich werde versuchen,
da einzugreifen und dich zu zwingen, irgendwas zu tun, so dass die
Persistenz der Transaktion tatsächlich gesichert ist.
Also wir haben auf der einen Seite die Autonomie des
Segmentverwalters, andererseits auch die Autonomie des Schedulers und
dann noch die Autonomie des Pufferverwalters, also entsetzlich viele
Komponenten, die eigentlich jeweils ihren Eigenverantwortungsbereich
tragen.
Jetzt will ich erstmal den Scheduler loswerden an der Stelle und den
entkoppeln.
Das geht einfach dadurch, dass der Scheduler die Transaktion noch zu
sagen hat, wann sie eine Seite nicht mehr benötigt.
Und sie gibt dann ein sogenanntes Anfix.
Wenn sie ein Anfix gibt, dann ist das das Signal für den
Segmentverwalter, dass er von sich aus jetzt veranlassen kann, dass
die Seite in den Hintergrundspeicher verbracht wird.
Ob das der Pufferverwalter tut, andere Frage.
Aber der Segmentverwalter, der Scheduler hat an der Stelle die
Erklärung abgegeben, dass er die Seite nicht mehr anfassen wird.
Das hat nichts mit Sperren zu tun.
Auch wenn der Scheduler Anfix gibt und sagt, ich fasse die Seite nicht
mehr an, so kann er deshalb trotzdem, wie wir wissen, die Sperre nicht
freigeben, denn er fährt ja ein strenges Zwei-Phasen-Sperrprotokoll.
Und wir verlassen uns auch drauf, wie wir sehen werden, dass die
Sperre nicht freigegeben wird.
Aber er sagt zumindest, aus meiner Sicht, er könnte die Seite aus dem
Puffer rausentfernen und auf den Hintergrundspeicher verbringen.
So, jetzt geht es also darum, Abbildungen zu schaffen.
Das erste Problem, das wir tragen, ist folgendes.
Auf dem Hintergrundspeicher liegen Dateien und logische Blöcke.
Und wir gehen um mit Segmenten und mit Seiten.
Da gibt sich schon mal die erste Abbildungsfrage.
Nämlich, wie mache ich denn das, wie führe ich die Zuordnung um?
Was entspricht einer Seite auf der Dateiebene?
Da haben wir aber gesagt, wir führen ja logische Datenblöcke gerade
dazu ein, dass wir uns möglichst an der Anwendung schon orientieren.
Also ist es eigentlich eine ganz natürliche Folge, dass wir sagen,
eine Seite entspricht exakt einem logischen Dateiblock.
Das nächste ist jetzt das folgende.
Wir wissen ja noch relativ wenig über die Zugriffsprofile, aber
irgendwo sollten wir eine gewisse Kontrolle ausüben können.
Also werden wir folgendes sagen, was heißt es denn, wenn wir
aufsteigende Seitennummern haben?
Also Seitennummer 1, 2, 3, 4, 5.
Wenn wir nun mal annehmen, dass ein nicht unwesentlicher Teil unserer
Zugriffe sequentiell ist, dann werden wir die Forderung stellen, dass
wenn zwei Seiten von ihrer Nummerierung her benachbart sind, dann soll
der Zugriff besonders schnell sein.
Also von N zu N plus 1, der Schritt soll besonders schnell erfolgen.
So, und daraus kann ich jetzt ableiten, wie meine Dateiverwaltung
jetzt die logischen Datenblöcke anzuordnen hat oder wie die
Segmentverwaltung die Zuordnung einer Seite zu einem logischen
Datenblock vorzunehmen hat.
Es kann also durchaus sein, dass da die Entscheidung getroffen wird,
dass das, was scheinbar benachbart ist, in Wirklichkeit physisch
durchaus sehr weit auseinanderliegt.
Ich brauche nur Folgendes zu überlegen, es ist viel schneller, wenn
Sie zwei benachbarte Blöcke auf unterschiedlichen Spuren eines
Zylinders unterbringen.
Zylinder ist ja ein senkrechter Schnitt durch die Platte, da liegen ja
die Spuren untereinander.
Und es ist viel schneller, wenn Sie den Arm einmal auf dem Zylinder
haben, wenn dann Nachbarschaft so definiert ist, dass Sie im Zylinder
bleiben.
Da Sie aber die Rotation irgendwie mit kontrollieren wollen, ist es
also häufig sinnvoll, sogar benachbarte logische Blöcke auf
unterschiedlichen Spuren innerhalb eines Zylinders unterzubringen.
Also solche Maßnahmen werden hier vorgesehen.
Wir haben hier einen ordentlichen Abbildungsmechanismus, mit der wir
durchsetzen wollen, dass wir eine minimale Verzögerung beim Übergang
von einer Seite zur benachbarten Seite haben wollen.
Dann ist die zweite Frage, wie ordnen wir denn nun Segmente zu
physischen Dateien zu?
Wenn Sie Systemadministrator sind, dann kriegen Sie eine Menge
Werkzeuge an die Hand geliefert von den Anbietern dieser Systeme und
sie werden auch durch Schulungen gejagt, damit sie lernen, wie man
Geschickdateien definiert für diese Segmente.
Es ist also keineswegs so, dass wir unbedingt sagen, wir ordnen ein
Segment genau einer Datei zu, sondern es mag sehr wohl Gründe geben,
warum wir beispielsweise mehrere Segmente einer einzigen Datei
zuordnen.
Was hingegen vermieden werden soll, ist, dass wir ein Segment über
mehrere Dateien verteilen.
Davon wird also abgeraten und infolgedessen gibt es eine 1 zu 1 oder N
zu 1 Zuordnung.
Da steckt also eine Menge Arbeit und Systemkenntnis dahinter, um zu
wissen, wie man das exakt macht.
Und da gibt es sogar Simulationspakete, mit denen man ausprobieren
kann, um festzustellen, ob man inklusive der Parametereinstellungen
wirklich zu einer performanten Lösung kommt.
So, die Seitenadressierung, die uns dann interessiert, ist eben, dass
wir eine Zuordnung eines logischen Blocks zu einer Seite vornehmen.
So, jetzt ist aber die Frage, wie rechnet man denn jetzt um?
Ich hatte ja schon gesagt, das hängt ja etwas davon ab, wie wir die
Blocknummern verteilen.
Es kommt aber noch etwas weiteres hinzu.
Unsere Segmente, die sind ja dynamisch, die wachsen und schrumpfen und
außerdem werden mal Seiten zugeordnet und dann auch wieder gelöscht.
Und selbst, wenn ich also sehr klug die Seitennummern so anordne, dass
ich diese minimalen Verzögerungen habe, und wenn ich auch noch
versuche, zusätzlich dann die Zuordnung zu den logischen Blocknummern
geschickt vorzunehmen, so tritt eine Alterungserscheinung ein.
Also Dateien und Segmente altern wirklich im Laufe der Zeit.
Und zwar deshalb, weil wir immer wieder neue Seiten einfügen oder
vorhandene Seiten löschen.
Das heißt, irgendwann wird so allermählich unsere Datei eine Art
Schweizer Käse.
Und infolgedessen ist die Frage, wie adressieren wir das denn am
besten?
Und eine Adressierung sieht folgendermaßen aus.
Nehmen wir mal an, ich habe zwei Segmente innerhalb derselben Datei.
Dann haben wir uns also vorzustellen, dass hier im Segment 1 gibt es
die Seiten P1,1 bis P1,n und dann gibt es ein zweites Segment P2,1 bis
P2,2 usw.
Und dann können wir die Zuordnung so machen, wir nehmen hier erstmal
die ersten n Blöcke in unserer Datei und das ist das erste Segment 1.
Und dann kommen die nächsten Blöcke, die gehören zum Segment 2.
Das ist eigentlich ein sehr einfaches Verfahren, vor allem kann man es
sehr leicht umrechnen.
Keine indirekte Adressierung, sondern man nimmt die Nummer und rechnet
sie direkt in die logische Blocknummer um.
Aber das Verfahren hat einen gravierenden Nachteil.
Wenn wir tatsächlich dynamische Mengen haben, dann haben wir hier eine
feste Grenze eingebaut und wenn jetzt das Segment 1 wächst und wächst,
dann müssen wir eigentlich erstmal hinten die zweite Datei
wegschieben.
Natürlich können wir jetzt irgendeinen schlauen Algorithmus
entwickeln, der sozusagen nicht verschiebt, sondern dann immer wieder
neue Teile aus der Datei rausnimmt und die dann zuordnet, das heißt er
wieder einzelne Speicherbereiche nimmt.
Aber das haben wir ja schon bei unserem dynamischen Wachstum der
Dateien.
Warum soll man diesen Prozess nochmal wiederholen?
Also der Nachteil dieses Verfahrens ist, wenn wir Dateien haben, die
sehr stark in ihrem Umfang schwanken, dann ist das keine gute
Strategie.
Und dann die zweite Strategie ist, wir machen indirekte
Seitenadressierung, die sieht so aus.
Wir haben hier mal wieder unsere Segmente 1 und 2 und wir haben wieder
eine Datei, aber diese Datei, da werden wir die Blöcke jetzt ganz frei
zuordnen.
First come, first serve, wer immer eine Seite braucht, der kriegt
einen logischen Block aus dieser Datei zugeordnet.
Und wenn da zwei Segmente sind, mal kommt der Segment 1, mal kommt der
Segment 2, dann kriegt mal der Segment 1 irgendeinen Block und dann
mal der Segment 2.
Vor allem können wir sogar Folgendes machen, wenn ein Block irgendwie
frei wird, weil die entsprechende Seite geleert wird, also gelöscht
wird, dann können wir sogar den Platz wiederverwenden.
Das kostet mich aber eine zusätzliche Seitentabelle, nämlich ich muss
jetzt indirekt adressieren.
Während ich vorher direkt umgerechnet habe, von Seitenadresse auf
Blockadresse, muss ich jetzt über eine Tabelle die Überabbildung
durchführen.
Also führen wir hier jetzt für die indirekte Adressierung die
Seitentabelle ein.
So und jetzt sehen wir hier beispielsweise die Seite 1,1.
Hier, die liegt im Block 2.
Und da haben wir eben den Block 2 hier.
Und nebenbei gesagt, da ist aber noch eine Bittabelle, die sagt, im
Augenblick ist auch dieser Block belegt.
Und genauso, wenn wir uns das hier mal ansehen, hier die Seite P1,3,
die liegt jetzt hier in einem Block J.
Der Block J liegt hier.
Und natürlich ist auch hier wieder vermerkt, dass der Block belegt
ist.
Und dann haben wir natürlich auch das Segment 2.
Zum Beispiel, dass hier die Seite P2,1 ist dem Block 1, die war
überhaupt die erste, die kam.
Infolgedessen haben wir hier diese Seite belegt.
Und auch hier haben wir wieder die Belegung markiert.
Also, das ist ein einfaches Verfahren, wie wir jetzt wirklich die
Dynamik, die in den Segmenten steckt, auch abfangen können.
Und das ist auch so die gängige Vorgehensweise.
Aber, Sie sehen, wir fangen an, schon Leistung zu verlieren durch
diese ständige Umrechnerei.
In jeder Schicht, die wir nach oben wandern, muss irgendwie eine
Adressumrechnung erfolgen.
Und wenn Sie sich vorstellen, dass wir oben fünf Schichten haben und
bei jedem Übergang von einer Schicht zur nächsten geht es erstmal los
mit Adressumrechnungen, dann ist schon verständlich, dass das auf dem
Weg von oben von der Anwendung bis runter zum Hintergrundspeicher,
dass da allein schon durch diese ständige Umrechnerei eine ganze Menge
an Leistung verloren geht.
Insbesondere, weil die Seitentabelle ja auch persistent gespeichert
werden muss, auch auf dem Hintergrundspeicher liegt, auch erstmal
reingeschafft werden muss.
Wenn die sehr groß ist, liegt die möglicherweise auch nicht im
Hauptspeicher, wenn wir sie brauchen, sondern auf dem
Hintergrundspeicher.
Das heißt, wir produzieren auch noch zusätzliche Hintergrundspeicher
-Zugriffe und die sind ja teuer.
Also da geht eine ganze Menge Leistung verloren und jetzt können Sie
sich vorstellen, dass die Wettbewerbsfähigkeit eines
Datenbankanbieters durchaus auch davon abhängt, wie geschickt er das
jetzt macht.
Oder ob er einfach dem Anwender, also der anwendenden Einrichtung
empfiehlt, sie möge doch bitte nochmal zehn Gigabyte zuschaffen,
Hauptspeicher, dann würden die Probleme auch weitgehend verschwinden.
So, das nächste, was wir jetzt haben, ist folgendes.
Jetzt wissen wir, wo unsere Seiten liegen.
Wir haben aber noch lange nicht gesagt, wo die Seiten zu liegen
kommen, wenn ich eine neue Seite erzeuge oder wenn ich eine der
vorhandenen Seiten, deren Inhalt, ändere.
Da gibt es jetzt zwei Strategien.
Die erste Strategie ist die direkte Einbringstrategie, die besagt
folgendes.
Wenn ich eine Seite erstmals benötige, dann beschaffe ich sie mir.
Das ist eine reine Allozierung.
Wenn ich jetzt den Inhalt einer Seite ändere, und das ist eigentlich
das häufigste, was uns im Allgemeinen bei unseren Schreibvorgängen
geschieht, dann heißt direkte Einspringstrategie, dass ich die Seite
an die gleiche Stelle, das heißt in den gleichen Block,
zurückschreibe, in der sie sich ursprünglich befand.
Das ist das sogenannte Update in Place.
Und das können wir nebenbei gesagt mit der direkten und indirekten
Seitenadressierung kombinieren, hat also damit gar nichts zu tun.
Wir entscheiden uns bei Änderungen, schreiben wir exakt an die gleiche
Stelle zurück, an der sich die ursprüngliche Seite befand.
Und das wie gesagt nennt man Update in Place, das heißt einmal, wenn
wir die Seite haben, das ist schön fixiert an der Stelle.
Und dann ist die indirekte Einbringstrategie natürlich genau das
umgekehrte.
Da sage ich, was geändert, woanders hin.
Und das heißt, dann allocieren wir einfach einen neuen freien Block,
schreiben die neue Seite dorthin, müssen natürlich dann die
Seitentabelle entsprechend anpassen.
Und nebenbei gesagt, anpassen dürfen wir natürlich erst bei Commit.
Denn erst wenn wir tatsächlich unsere Transaktion festschreiben, dann
gilt überhaupt erst der neue Platz der Seite und bis dahin gilt noch
die alte Seite, nämlich noch der alte Zustand.
Da sieht man schon, die indirekte Einbringstrategie ist eigentlich
viel schöner aus der Sicht der Transaktion.
Weil die Transienz so schön gewährleistet bleibt während der Update in
Place.
Stellen Sie sich vor, mittendrin wird die Seite rausgeschrieben,
überschreibt also den alten Inhalt.
Transaktion bricht ab, alter Inhalt weg.
Das heißt, obwohl Sie auf den Anfang zurückkommen wollen, können Sie
es nicht mehr, weil der alte Inhalt schon verschwunden ist.
Also wird uns die direkte Einbringstrategie höchstwahrscheinlich mehr
Ärger bereiten, als die indirekte Einbringstrategie, die man übrigens
auch nur mit der indirekten Seitenadressierung verwenden kann.
Ganz klar, wir ändern ja die Adressen.
Da gibt es ein Konzept, den sogenannten Schattenspeicher, der sehr
anschaulich sagt, wie so die mit der indirekten Einbringstrategie
umgegangen wird.
Und zwar sehen wir hier wieder das Bild, das wir schon hatten.
Nämlich hier haben wir wieder unser Segment.
Also das ist jetzt gedacht natürlich.
Das liegt ja irgendwo physisch in unseren Dateien.
Und wir haben unsere Seitentabelle.
Aber jetzt haben wir zweimal die Seitentabelle.
Dann haben wir unsere Datei mit unseren Blöcken.
Und dann geht natürlich folgendes.
Wir haben irgendeinen Zustand, da müssen wir die Freispeichertabelle
führen.
Und die führen wir auch zweimal.
Das heißt, so wie wir hier zweimal die Seitentabellen führen, so
führen wir auch hier zweimal die Bittabellen, um den Freispeicher zu
verwalten.
Jetzt können Sie sich erst einmal fragen, warum zweimal?
Wir machen das folgendermaßen.
Wir denken ja immer daran, dass wir Robustheit brauchen.
Und hier wird ein Robustheitsprinzip eingebaut, das folgendermaßen
funktioniert.
Wir nehmen irgendeinen bestimmten Zeitpunkt und frieren zu diesem
Zeitpunkt für einen Augenblick die Segmente ein.
Wir sagen also, das ist jetzt ein bestimmter Zustand, von dem wissen
wir, dass der, wie auch immer definiert, gültig ist.
Und dann lassen wir das System wieder ordentlich arbeiten.
Und wenn irgendwas schief geht, dann gehen wir einfach zurück zu
unserem eingefrorenen Zustand und sagen, von dem wissen wir, dass der
in Ordnung ist.
Da können wir wieder anfangen.
Wir haben also hier einen festgefrorenen, einen eingefrorenen Zustand.
Und hier haben wir einen aktuellen.
Hier wird wirklich gearbeitet.
Wir arbeiten also jetzt nur im aktuellen Zustand, halten unseren
Schattenzustand hinten und sagen, wenn was schief geht, kann ich immer
zurück zu diesem eingefrorenen Zustand.
Und entsprechend haben wir eben jetzt auch die aktuelle Tabelle und
die Schattentabelle, das ist auch wieder gehört zum eingefrorenen
Zustand.
Und hier wird aktuell gearbeitet.
So und in der Datei selbst, da liegen jetzt bunt gemischt alte
Zustände und aktuelle Zustände.
Wir gehen ja immer mit neuen Zuständen, gehen wir ja immer wegen der
indirekten Einbringstrategie zu neuen Blöcken über.
Also liegen jetzt bunt gemischt einfach die alten und die neuen da
drin.
Und wir sehen auch genau, was passiert jetzt, wenn wir zurück müssen,
weil wir Transienz haben und Atomicität verfolgen wollen, dann setzen
wir wieder auf den Schattenzustand auf, dann haben wir den wieder zur
Verfügung und können wieder loslegen.
Und wenn alles fehlerfrei abläuft, dann entscheiden wir uns zu
irgendeinem Zeitpunkt und sagen, jetzt glaube ich, sollte ich mal
wieder den Zustand einfrieren, damit ich einen neuen Zustand habe, auf
den ich wieder zurückgehen kann, wenn danach was passiert.
Also Sie können so sagen, so eine Art Safe-Lauf, der nur darin
besteht, dass ich einfach die aktuellen Zustände, also aktuelle
Seitentabelle und aktuelle Bittabelle, zur Schattenseite, zu
Schattentabellen mache.
Das ist alles, was ich zu tun habe.
Dummerweise hängt der Schattenspeicher, ist der orthogonal zur
Transaktionsverwaltung und das ist der Krux daran.
Aber wir können hier mal kurz vorführen, was passiert denn, wenn ich
jetzt der Meinung bin, jetzt muss ich mal wieder den aktuellen Zustand
zum Schattenzustand machen.
Da sehen wir folgendes.
Hier inzwischen, das war vorher 2 und ist jetzt 4, also ganz
offensichtlich ist die Seite P11 geändert worden.
Und damit ich das auch wirklich sehen kann, dass die geändert worden
ist, ohne vergleichen zu müssen, wird da ein sogenanntes Schattenbit
gesetzt, durch dieses Dreieck angedeutet.
Dann sehen wir zum Beispiel hier, bei denen ist nichts passiert, aber
hier ist J durch K ersetzt worden, also haben wir hier auch ersetzt.
Und hier war beispielsweise frei, an der Stelle, und da ist jetzt
inzwischen eine Zuordnung erfolgt, also haben wir auch hier ein
Schattenbit gesetzt.
Übrigens das Umgekehrte gilt auch, wenn ich eine Seite dort habe, und
ich lösche sie, dann setze ich auch ein Schattenbit.
So, und mit diesem Schattenbit kann ich jetzt losgehen und sagen, ich
schaue mir mal alle Stellen an, an denen ein Schattenbit ist und weiß
dann folgendes, ich vergleiche und stelle fest, etwa wenn ich diesen
Fall hier ansehe, dass jetzt die Seite 2, also der Block 2, offenbar
nicht mehr benötigt wird, weil ich jetzt ja als neuen eingefrorenen
Zustand den Block 4 nehme.
Also gebe ich die Seite 2, jetzt beim Herstellen des neuen
Schattenzustands, gebe ich den Block 2 frei und muss natürlich auch
die entsprechende Anpassung in der Bittabelle vornehmen.
Und so gehe ich durch und anhand der Schattenbits entscheide ich
zunächst mal, was kann ich denn jetzt in der Datei, in der nun
physisch meine Daten liegen, also meine Seiten liegen, was kann ich
davon freigeben und was muss ich neu führen und was muss ich außerdem
dann anschließend transferieren an Information von der aktuellen
Tabelle in die Schattentabelle und wie muss ich umgekehrt auch meine
Bittabellen anordnen.
Also ein sehr schöner, einfacher Mechanismus, der schon sehr
frühzeitig entwickelt worden ist, der aber von der Leistung her sich
als reichlich schlecht erwiesen hat, insbesondere als die
Transaktionsverwaltung aufgekommen ist, auch als orthogonal erwiesen
hat.
Er hängt mit den Transaktionen nicht recht zusammen und man muss
infolgedessen noch die Transaktionsverwaltung noch mal obendrauf
aufprägen und das macht das Schattenspeicherkonzept doch reichlich
kompliziert.
Es ist deshalb fast völlig in der Versenkung verschwunden, aber in
neuerer Zeit wird es doch wieder angesehen, angesichts der sehr großen
Speicher, die wir haben, ob es nicht durchaus auch seine Vorzüge hat.
So, das ist der Endzustand, jetzt haben wir alles umgebaut und da wird
ein neuer Schattenzustand erstellt und von dem aus wird jetzt wieder
neu gearbeitet.
So, jetzt haben wir also gesagt, ein Segmentverwalter, der hat
eigentlich nur eine Aufgabe, der schaut sich den Hintergrundspeicher
an und sagt, wie organisiere ich den Hintergrundspeicher möglichst
geschickt und wir gesehen haben, kann er zunächst einmal zwei
verschiedene Strategien fahren, er kann eine direkte Einbringstrategie
fahren, überschreibt, Update in Place, oder er kann eine indirekte
fahren, dann kommt er auf irgend so etwas wie das
Schattenspeicherkonzept.
So, er sagt aber nur dahinterher dem Pufferverwalter, wo der dann die
Seite, wenn er sie aus dem Puffer auf den Hintergrundspeicher
schreibt, wo er sie hinzuschreiben hat und er sagt umgekehrt natürlich
dann auch, wo er sie zu finden hat.
Der Pufferverwalter selbst entscheidet aber eigenmächtig und
eigenständig, wann die Seite rausgeschrieben wird.
Beim Reinholen, da ist weniger, da kann er nicht viel machen, wenn ich
mit Ruinen ankomme, dann muss er die halt erstmal einlagern.
Aber wenn er eine veränderte Seite rausschreibt, der Segmentverwalter
teilt nur mit wohin, aber nicht wann.
Wann ist eine Sache, die der Pufferverwalter zu entscheiden hat.
Also Puffverwalter, wir können nochmal zurückgehen, wir können sagen,
Segmentverwalter sagt wohin und der Pufferverwalter sagt wann
geschrieben werden soll.
So, jetzt ist der Pufferverwalter versucht natürlich jetzt folgendes.
Der würde natürlich gerne eine Strategie fahren nach dem Motto, wenn
immer eine Seite benötigt wird, dann ist sie schon im Puffer.
Wenn wir das schaffen, dann haben wir oben im Hintergrund mal, das
läuft ja heute alles schön parallel, dann haben wir während unser
Datenbanksystem ordentlich gearbeitet hat oder unsere Transaktion
gearbeitet hat, hat in der Zwischenzeit unser Pufferverwalter einfach
die nächsten Seiten reingeholt.
Und wenn immer wir mit unserer Read-Anweisung anrücken, dann ist die
Seite schon im Puffer.
Dann haben wir nämlich nur noch die Zugriffszeit im Hauptspeicher und
die ist ja 10 hoch 5 schneller als die auf den Hintergrundspeicher.
Wenn wir das also hinreichend häufig erreichen, nämlich nur in 1
Promille der Fälle beispielsweise, die tatsächlich auf den
Hintergrundspeicher zurückgegriffen werden muss, dann machen wir aus
den 10 hoch 5 schon eine Lücke, die nur 10 hoch 2 ist.
Also wenn wir das schaffen, dann haben wir wirklich gute Arbeit
geleistet.
Also wir wollen, dass die Daten noch im Puffer vorliegen, damit
möglichst nicht auf den Hintergrundspeicher zurückgegriffen wird.
So, jetzt ist natürlich interessant, ob wir dabei die einzelne
Transaktion betrachten oder ob wir die Menge aller Transaktionen
betrachten wollen.
Man kann sich sehr schnell überlegen, dass Transaktionen ja durchaus,
nehmen wir Buchungen, ja sehr häufig auf die gleichen Seiten zugreifen
werden.
Also wenn wir 1000 Transaktionen pro Minute durchjagen, dann wird
insbesondere bei relativ kurzfristigen Buchungen ist die
Wahrscheinlichkeit, dass wir sehr schnell immer wieder auf die
gleichen Flüge zugreifen, ist recht groß.
Also ist es doch sinnvoller, wir behalten den Puffer so vor, dass er
alle Transaktionen gemeinsam bedient und nicht eine einzelne
Transaktion für sich.
Wir brauchen also infolgedessen, versuchen wir nicht einen privaten
Arbeitsbereich, der ist ungünstig, sondern wir werden eine
gemeinschaftliche Zugänglichkeit aller Datenelemente für die
Gesamtheit aller Transaktionen anstreben.
Ich muss dazu sagen, das ist ein Ziel, wenn man sich etwas mehr mit
den Details beschäftigt, dann gibt es gelegentlich Gründe, warum man
doch davon abweicht, aber muss sich dann im Klaren sein, dass man dann
auch zum Teil die vorteilhaften Eigenschaften des Puffers wieder
verliert.
Also wir werden jetzt davon ausgehen, dass wir hier versuchen, den
Effekt, nämlich die gewünschten Seiten schon in einem Puffer
vorzufinden, dadurch erreichen, dass wir den Puffer allen
Transaktionen gemeinsam zugänglich machen, in der Hoffnung, dass es
sehr viele Seiten gibt, die von mehreren Transaktionen
gemeinschaftlich benutzt werden und die infolgedessen schon mal von
einer früheren Transaktion reingeholt worden sind und der
Pufferverwalter hoffentlich hat die inzwischen auch nicht wieder
rausgeworfen, sondern die befinden sich immer noch im Puffer.
So, wir bauen so einen Puffer auf, wenn Sie sich das jetzt vorstellen,
die werden heute liegen, die auch sehr schnell im Bereich von
Hunderten von Megabyte, wenn es nicht im Bereich Gigabyte geht.
Sie haben natürlich Konkurrenten.
Aber wir können uns also vorstellen, dass wir so einen Arbeitsbereich
haben und der wird jetzt in den Rahmen.
Und diese Pufferrahmen, die haben nur die Größe einer Seite.
Und jetzt sehen Sie, warum es so vorteilhaft ist, wenn Seitengrößen
identisch sind, dann sind natürlich auch die Pufferrahmen alle
identisch und dann ist so ein Puffer verhältnismäßig einfach zu
verwalten.
Und wir müssen ja den Verwaltungsaufwand runterbringen, so weit als
möglich, weil überall, in jeder Schicht wird ja Leistung und Zweck
gefressen.
Alles Overhead, alles Blindleistung, die nicht unmittelbar der
Anwendung zugute kommt.
Also haben wir einen Pufferrahmen, in den wir zwischenspeichern.
Und nur dort können wir zugreifen, wir hoffen eben, dass sich die
Seiten, wenn wir sie brauchen, auch schon in einem Pufferrahmen
befinden.
Da muss ich noch einen Pufferkontrollblock natürlich zufügen, für
jeden Pufferrahmen, um dann eben Aussagen zu machen, welche Seite ist
es im Augenblick, ist die geändert worden oder nicht, sogenannte Dirty
Bit, also einige Informationen, die etwas Aussagen darüber führen, wie
dann dieser Puffer zu verwalten ist.
So, und was uns jetzt natürlich besonders interessiert, ist das M.
Wie viele Seiten sollen wir vorhalten?
Natürlich, es ist kein Kunststück, wir kaufen uns nochmal etliche
Speichermodule dazu und jagen einfach die Hauptspeichergröße hoch,
aber das ist ein wirtschaftlicher Faktor, wir können nicht beliebig
hochgehen.
Und insbesondere sollte man sich sein Profil genauer ansehen, es
könnte ja sein, dass man höchst Unnütz ausgibt, weil das
Zugriffsprofil so ist, dass ich eigentlich gar nicht sonderlich viele
Seiten brauche.
Also muss ich noch etwas anderes tun, damit ich entscheiden kann, was
ist denn eine vernünftige Größe für N und über welche Strategie
erfahre ich dann anschließend, wenn es darum geht, diesen Puffer
möglichst gut auszunutzen, brauche ich die Zugriffscharakteristika der
Transaktionen.
Die aber, wissen wir ja, sind immer nur Read-Write-Anweisungen, über
Read -Seite oder Write-Seite.
Also ist das eine ganz einfache Abfolge von An- oder Anverforderungen
an den Puffer und die werden durch eine solche Abfolge, die bezeichnen
wir als einen logischen Seitenreferenztring.
Jetzt können Sie sich vorstellen, am Anfang wissen Sie vielleicht noch
nicht so genau, wie Ihr Profil aussieht, Sie messen jetzt einfach auf
einem Puffer und ermitteln diesen logischen Seitenreferenztring.
Und dann lassen Sie irgendwelche Analyseverfahren drüber laufen, die
Ihnen dann Kennzahlen ermitteln, wie ein solches Profil aus der
Puffersicht aussieht und daraus können Sie dann Pufferstrategien
entwickeln.
Also es ist durchaus sinnvoll, so eine Zeit lang im ersten Betrieb
erstmal so eine Warmlaufphase laufen zu lassen, bei der man sich
überhaupt erstmal mit der Anwendung vertraut macht und mit den
Profilen, bevor man endgültig seine Parameter setzt.
Also wir ermitteln so einen logischen Seitenreferenztring, das sind
einfach die Anforderungen, die an den Puffer vom Segmentverwalter
gereicht werden.
Und was macht der Segmentverwalter?
Der nutzt jetzt die Funktionalität des Pufferverwalters.
Der hat an Einweisungen ein Fetch.
Fetch heißt, holen wir die Seite X und ich gehe zurück, erhalte ich
eine Adresse X, die die Hauptspeicheradresse ist, nämlich die Adresse
des Pufferrahmens.
Mark X sagt einfach, bitte schön markiere diese Pufferrahmen als eine
Seite, an der ich geändert habe.
Also wenn wir Write sagen, dann wird nicht etwa geschrieben, sondern
dann wird markiert.
Also mit Write ist noch lange nichts draußen auf dem
Hintergrundspeicher.
Wenn wir es auf den Hintergrundspeicher bringen wollen, dann schreiben
wir Flush, dann geben wir die Flush-Anweisung.
Also häufig macht der Pufferverwalter von sich aus Flush, aber wir
können natürlich auch von außen herankommen und sagen, die Seite muss
aber raus, jetzt schreibst du gefälligst raus und wir geben ein Flush.
Dann greifen wir also in die Autonomie des Pufferverwalters ein und
erklären ihm, er hätte keine freie Wahl mehr, sondern wir schreiben
ihm vor, was er zu tun hat.
Pin heißt, wir nageln die Seite im Puffer fest.
Und das müssen wir tun, weil wir ja die Pufferadresse an unsere
Transaktion geben.
Und die verlässt sich jetzt darauf, dass an der Adresse auch die Seite
tatsächlich zu finden ist.
Das heißt, wir müssen dem Pufferverwalter, der muss jetzt eine
Garantie abgeben, dass solange die Seite nicht wieder frei entnagelt
wird per Unpin, dass sie sich immer an der gleichen Stelle befindet.
Und nebenbei gesagt, im Gegensatz zu den virtuellen
speichertechnischen Betriebssystemen, weiß unser Pufferverwalter
nichts darüber, wann und in welcher Form die Transaktion auf die Seite
zugreift.
Die weiß nur einmal, dass sie geholt wird, dass ein Pin draufgegeben
wird und sie weiß am Ende, dass wieder mit Unpin freigegeben wird.
Sie weiß nicht, wie häufig zwischendrin die Transaktion tatsächlich
mit der Seite arbeitet.
Während in der klassischen virtuellen Speicherverwaltung und
Betriebssystemen ja jeder einzelne Zugriff als solcher erkannt wird.
Also das wissen wir hier nicht.
Hier wissen wir nur erstmaliger Zugriff und dann wieder Freigabe.
Unpin habe ich schon gesagt, gibt also wieder frei, wenn wir es
erklären, braucht man nicht mehr.
Und wir können uns jetzt vorstellen, wenn wir ein Read geben, in
unserem Segmentverwalter, wenn wir ein Read erhalten, dann setzt er
einen Fetch um.
Und außerdem gibt er gleich noch einen Pin hinten nach, sagt nagel in
die Fest.
Wenn er ein Write erhält, dann setzt er einen Mark ab an unseren
Pufferverwalter.
Wenn er einen Unfix kriegt, dann setzt er einen Unpin um.
So, was passiert jetzt mit unserem Pufferverwalter?
Wir wünschen uns ja alle, dass wenn da ein Fetch einläuft mit der
Seitennummer X, dass dann sich die gewünschte Seite im Puffer
befindet.
Dann braucht unser Puffverwalter nichts anderes zu machen, also setzt
er einen Pin drauf, nagelt ihn also wieder fest im Puffer und gibt die
Adresse nach außen ab.
Das geht ziemlich schnell.
Kostet zwar auch ein bisschen Verwaltungsaufwand, aber geht doch
vergleichsweise schnell.
Aber natürlich kann auch etwas Schlimmeres passieren, nämlich die
gewünschte Seite ist nicht im Puffer.
Das ist eine sogenannte Fehlseitenbedingung.
Dann muss unser Pufferverwalter die Seite hereinholen.
Da geht er raus zum Hintergrundspeicher, besorgt an die entsprechende
Adresse des logischen Blocks, geht also möglicherweise noch durch die
Seitentabelle durch, holt sich die Seite und schafft sie rein in den
Puffer.
Aber wohin?
Der Puffer ist ja voll.
Er kann also nichts anderes machen, als eine Seite, die im Augenblick
da drin ist, ersetzen.
Kann überschreiben.
Die kann er aber nur überschreiben, wenn die nicht geändert ist.
Wenn sie geändert worden ist, dann muss er sie überhaupt erstmal
rausschreiben, die andere.
Wenn er sie rausgeschrieben hat, dann überschreibt er sie also und
dann kann er die Adresse herausgeben.
So, welche sucht er sich denn raus?
Wenn er sich hier recht doof anstellt, dann überschreibt er genau die
Seite, die gleich im nächsten Anlauf gebraucht wird.
Dann muss man sofort wieder raus zur Peripherie und der ganze schöne
Effekt, den wir uns wünschen, ist weg.
Also sollte unser Pufferverwalter eine hinreichende Intelligenz
besitzen, sodass er sagt, naja, ich weiß schon ungefähr, welche Seite
lange nicht mehr benötigt wird, in Zukunft nicht mehr benötigt wird.
Das ist hellseherisch, muss er jetzt sein, unser Pufferverwalter.
Wenn er immer sagt, was wird in längerer Zeit nicht mehr benötigt, das
kann ich am besten überschreiben.
Also wir brauchen einen hellseherischen Pufferverwalter.
Und er muss also zwei Dinge machen.
Er muss sehr schnell die Seite im Puffer finden oder herauskriegen,
dass da die Seite nicht vorhanden ist.
Das ist die Suchstrategie.
Und dann muss er sich überlegen, welche der Seiten ersetze ich denn?
Und dazu benutzt er seine hellseherischen Fähigkeiten.
So, und das ist mit dem Hellsehen ja so eine Sache.
Es gibt ja den alten Spruch, dass nichts schwieriger vorherzusagen ist
als die Zukunft.
Also Vorhersagen ist einfach, wenn es in die Vergangenheit geht, aber
schwierig, wenn es in die Zukunft geht.
Und das gilt halt auch für unseren Pufferverwalter.
Die Suchstrategie braucht man nicht groß darauf einzugehen.
Das geht heute üblicherweise mit Hash-Verfahren, weil die besonders
schnell sind.
Aber bei der Ersetzungsstrategie.
Unser Hellseher muss also herauskriegen, er darf nicht solche Seiten
ersetzen, die in Kürze wieder referenziert werden oder im
Umkehrschluss.
Er darf nur solche Seiten ersetzen, die lange nicht mehr benötigt
werden.
Nun, was macht ein Hellseher in dieser Situation?
Er kennt, man weiß nicht, welche Transaktionen, er hat ja keine
Ahnung, in welcher Form die Transaktionen, welche Transaktionen
einlaufen, in welcher Abfolge, zu welchem Zeitpunkt.
Er weiß überhaupt nichts über die Zukunft.
Das Einzige, was er machen kann, ist, dass er sagt, das zukünftige
Verhalten wird sich wahrscheinlich nicht sehr stark vom Verhalten der
Vergangenheit unterscheiden.
Und jetzt sehen Sie, das ist eine absolut kritische Annahme.
Ich brauche also ein hinreichend stabiles Zugriffsprofil, wenn ich
diese hellseherischen Fähigkeiten wirklich entwickeln will.
Um das zu tun, oder dazu müssen wir also wissen, wie stabil ist unsere
Anwendung.
Und in den klassischen Transaktionssystemen, also Buchungssystemen,
Banker, Kontenführung, auch vieles, was etwa Einkaufen im Web angeht,
da kann man diese Voraussetzung machen.
Es gibt andere Anwendungen, da können Sie diese Voraussetzung nicht
machen und dann sehen Sie, dass Sie dann mit völlig anders gearteten
Pufferstrategien arbeiten müssen.
Die, die ich Ihnen jetzt vorführe, geht also davon aus, dass wir
wirklich ein stabiles Verhalten haben, als uns die Vergangenheit
ansehen und dann sagen, dann wird es in der Zukunft wohl ähnlich
weitergehen.
Wir haben noch ein weiteres Problem.
Wir können auch nicht mal den gesamten Puffer verwenden, sondern
alles, was im Augenblick fixiert ist, also mit PIN versehen worden
ist, das ist überhaupt tabu.
Das dürfen wir überhaupt nicht anfassen.
Und Sie sehen Folgendes, wenn wir also eine hohe Transaktionslast
fahren und ein erheblicher Teil der Seiten im Puffer ist inzwischen
mit einem PIN versehen, dann bleibt uns nur noch ein kleiner Rest
übrig und dann geht die Performance natürlich auch im Keller runter,
weil jetzt beginnt so ein sogenanntes Seitenflattern, das heißt die
paar Rahmen, die wir noch haben, durch die wird alles durchgeschleust.
Also wir brauchen in Folge dessen noch außen irgendwo den
übergeordneten Strategen, der sagt, jetzt ist aber genug, jetzt nehmen
wir mal keine Transaktionen mehr an, bis wir die anderen losgeworden
sind, weil wir einfach nicht mehr die Ressourcen haben, um so viele
Transaktionen gleichzeitig durchzuführen.
Das hängt also damit zusammen, dass wir alles, was fixiert ist, eben
auch nicht ersetzen dürfen.
So, jetzt gibt es noch verschiedene Strategien.
Diejenige, die ist sogenannten, das heißt wir schaffen nicht auf
Vorrat, weil wenn ich eine Seite reinhole, dann brauche ich auch
gleich noch die und die und die Seiten.
Dann arbeitet man also, indem man gleich mehrere Seiten in einem Zug
einlagert.
Aber das Demandpaging, das also insbesondere auf die Asset
-Transaktionen zugeschnitten ist, das sagt immer, naja, mal abrichtig
fragen, welche Seiten als nächste benötigt werden.
Also warte ich ab und reagiere immer nur auf eine Anforderung.
Also das Demandpaging kriegt eine Seitennummer genannt und ersetzt
dann genau, oder bringt diese Seite in den Puffer oder lokalisiert
diese Seite, wenn sie schon dort ist.
So, und jetzt muss ich also ersetzen und das Ersetzen heißt, ich
berechne jetzt aus der Vergangenheit einen Erwartungswert für alle
Seiten, die sich im Puffer befinden, den Erwartungswert der
Wiederbenutzung und diejenige Seite, die den geringsten Erwartungswert
hat, die ersetze ich.
Die wird rausgeworfen.
Kann ich vielleicht noch ein bisschen ändern, nachschauen, ist sie
geändert worden oder nicht und vielleicht bevorzuge ich erstmal
diejenigen, die nicht geändert worden sind, um den Aufwand zu
reduzieren.
Aber im Grunde genommen schaue ich mir einfach an, ich projiziere aus
der Vergangenheit einen Erwartungswert und versuche dann
vorherzusagen.
So, das kann man natürlich ein bisschen hochgradig intelligent machen.
Das können Sie so machen, dass Sie ununterbrochen Statistiken sammeln
und nachher endlos brauchen, bis Sie Ihre ganzen Erwartungswerte
berechnet haben und dann sagen, die Seite ist dran.
Inzwischen hätten Sie natürlich auch mit einem Zufallsverfahren
arbeiten können und die gleiche Leistung auch erreicht.
Wir haben also das Problem, dass wir die Erwartungswerte gar nicht so
genau berechnen können, weil das so aufwendig ist.
Wir müssen ja schnell sein.
Und um schnell zu sein, versucht man also, relativ einfache Modelle zu
entwickeln, die so ungefähr diesen Erwartungswert ermitteln, wenn auch
mit einer gewissen Unschärfe und die sehr einfach handzuhaben sind.
Und jetzt muss man sich fragen, was kann ich denn, um so ein einfaches
Modell zu verwenden, was kann ich denn da an Größen und Einflussgrößen
identifizieren?
Und im Wesentlichen sind es zwei.
Sie sagen einfach mal das Alter, wie lange ist denn das Ding schon
drin?
Und zweitens, wie häufig ist denn die Seite schon referenziert worden
in der Vergangenheit?
Eine Seite, die sehr häufig referenziert worden ist, von der würde man
sagen, das wird in Zukunft auch so sein.
Aber, das reicht nicht ganz, denn es kann mal sein, dass die lange
zurück in der Vergangenheit sehr häufig referenziert worden ist, aber
in der jüngsten Zeit nicht mehr.
Und im Vorgängersen dürfen wir also nicht nur ansehen, wie häufig
referenziert wird, sondern das Alter spielt eine zusätzliche Rolle.
Wir kombinieren also im Wesentlichen Alter und Zahl der Referenzen, um
die Entscheidung zu treffen.
Da gibt es eine sogenannte Least-Reasonly-Used- oder auch Second
-Chance -Technik, die gilt auch als Clock-Algorithmus, die sieht
folgendermaßen aus.
Sie sehen, dass wir einen Zeiger haben, der zeigt also auf eine Seite,
die gerade entweder eingelagert worden ist, oder zumindest
referenziert worden ist.
Und wir haben hier noch Bit, also das sind jetzt nicht die Pin-Bits,
sondern das ist ein zusätzliches Ein-Bit, das wird auf 0 oder 1
gesetzt.
Wenn wir eine Seite referenzieren, dann wird dieses Bit auf 1 gesetzt.
Und wenn jetzt eine neue Seite benötigt wird, dann fängt unser
Uhrzeiger an jetzt zu laufen und der sucht sich dann die nächste
Seite, den nächsten Rahmen, bei dem das Bit auf 0 gesetzt ist.
Jetzt muss man ja fragen, wo steckt da Alter und Referenzhäufigkeit
drin?
Während unser Zeiger jetzt fixiert ist auf diese eine Seite, wie hier
dargezeigt ist, kann es natürlich weitere Referenzen geben auf Seiten,
die bereits vorhanden sind.
Und dann wird nicht mehr der Zeiger weitergeführt, sondern wird nur
dieses Bit von 0 auf 1 gewechselt.
Und jetzt, wenn unser Zeiger nachher wegen des Ersetzen-Müssens
umläuft, dann stößt er nicht mehr auf eine 0 an der Stelle, sondern
auf eine 1 und dann ignoriert er und läuft weiter, bis er die nächste
0 gefunden hat.
Auf der anderen Seite müssen die 1 irgendwie mal auf 0 gesetzt werden,
um das Alter wieder zu spiegeln.
Und das machen wir dadurch, dass wir umlaufen und jedes Mal, wenn wir
überstreichen und da ist eine 1, dann verwandeln wir die einzelnen 0.
Also können wir sagen,
zwischenzeitliches Setzen auf 1, da sieht man es über die Referenz
aus, und beim Überstreichen, Setzen auf 0,
das hat was mit dem Alter zu tun.
So, jetzt schauen wir es an.
Seht ihr, jetzt sind wir da umgelaufen und das war unsere bisherige
Position, da standen Einsen, die sind jetzt auf 0 verwandelt worden
und hier finden wir die nächste 0.
Und jetzt wird an der Stelle eingelagert und jetzt sind wir zum
Beispiel noch ein bisschen weiter gelaufen, inzwischen ist diese Seite
wieder angesprochen worden, da ist jetzt wieder auf 1 gesetzt worden.
Also das ist ein ganz einfacher Algorithmus, sehr einfach zu
implementieren und ist eigentlich heute der gängige Algorithmus für
die PUFA-Verwaltung.
Aber wie gesagt, man kann auch sehr viele intelligentere Algorithmen
entwickeln, für den Zweck muss erheblich mehr Informationen sammeln.
Man will zum Beispiel, was eigentlich das beste Verfahren ist, die
sogenannte Referenzdichte zu ermitteln.
Man geht also über Zeitintervalle und misst dann, wie in einem
gegebenen Zeitintervall, wie häufig Referenzen aufgetaucht sind.
Da kriegen wir also nicht nur diese einfache 0-1-Regelung, sondern da
wird echt gemessen, wie häufig die Referenzen erfolgen.
Aber da gibt es ungeheuer viel Spielräume, ungeheuer viele
Algorithmen.
Ich führe Ihnen nur so ganz einfache Einführung in die Materie vor.
So, jetzt kommt unsere Atomicität.
Wir müssen nämlich auch noch zu unserem Recovery-Verwalter gehen.
Und der hat ja jetzt das Problem, der die Atomicität einer Transaktion
durchsetzen muss.
Er weiß ja auch, Abort oder Commit läuft ein.
Und die erste Anweisung ist unser Beginn of Transaction.
Das ist ganz einfach.
Aber er muss jetzt entscheiden, wie er durchsetzen kann die Zienz und
die Persistenz einer Transaktion, wenn er da so einen Puffer-Verwalter
hat, der alles auf eigene Faust macht.
Und zwar im Überfluss, wenn er auch noch eine direkte
Einbringstrategie, also ein Update in Place fährt, bei der er auch
noch dafür Sorge trägt, dass Änderungen überschrieben werden.
Also irgendwo muss der Segment-Verwalter und der Puffer-Verwalter doch
zusammenspielen.
Aber ich muss noch mal sagen, eine wichtige Voraussetzung ist immer
dafür, dass wir sicher sind, dass wenn etwas auf den
Hintergrundspeicher geschrieben wird, dass es fehlerfrei geschrieben
wird.
Dass der Teil atomar ist.
So, da wollen wir ansehen, wie wir hier vorgehen können.
Jetzt wollen wir uns erstmal überlegen, wie die Einflussnahme unserer
Segment -Verwalters auf den Puffer-Verwalter aussieht.
Da ist die erste Frage die Verdrängung.
Jetzt haben wir ja gesagt, unser Puffer-Verwalter läuft da rum und
sucht sich so ein Opfer aus, das er jetzt rauswerfen kann.
Das hat zunächst mal nichts mit Transaktionen zu tun.
Aber dummerweise ist denkbar, dass der Puffer-Verwalter, als er schon
losläuft, während die Transaktion läuft und ausgerechnet sich eine
Seite aussucht im Puffer, die eine Transaktion bereits freigegeben
hat, per Unpin, aber auf der noch eine Sperre sitzt.
Die dürfte also noch gar nicht rausgeschrieben werden.
Aber der Puffer-Verwalter sagt, die muss raus.
Die wird ersetzt.
Und dann haben wir vielleicht auch noch ein Update in Place, so dass
der alte Inhalt auch noch vernichtet wird.
Alles, obwohl die Transaktion noch gar nicht zu Ende gekommen ist.
Nun kann man sagen, ich will mal den Puffer-Verwalter daran hindern,
das zu tun.
Die normale Strategie, die ich gerade genannt habe, heißt Steal.
Das heißt in der Tat, der Puffer-Verwalter kann die Seite stehlen, die
Transaktion wegnehmen und auf den Hintergrundspeicher schreiben.
Aber unser Segment-Verwalter kann nicht kommen und sagen, gibt es
nicht.
Du hältst diese Seite solange im Puffer, bis das Commit einläuft.
Dann sprechen wir von einem No-Steal.
Das kann zum Beispiel der Segment-Verwalter ganz einfach machen, indem
er einfach das Unpin nicht gibt.
Also er kann da Einfluss nehmen, ohne dass der Puffer-Verwalter es
richtig merkt.
Das Unpin wird länger gehalten.
Sie sehen sofort, was passiert.
Es stehen noch weniger Seiten für das Ersetzen zur Verfügung.
Also die Gefahr, dass Ihnen die Ressourcen ausgehen, sind noch größer.
Das ist meistens ein so gravierendes Argument, dass fast alle unserer
Puffer -Verwalter nicht beeinflusst werden, sondern eine Steal
-Strategie gefahren wird.
Und an dem Bild sehen Sie einfach mal, hier ist unsere Transaktion.
Die gibt da also ihre Write und Unfix.
Das wird übersetzt in ein entsprechendes Unpin.
Und wenn wir eine Steal-Strategie haben, wird dann anschließend nach
dem Unpin irgendwann ausgeschrieben.
Wir wissen gar nicht mehr weiter, was passiert.
Während wenn eine No-Steal gefahren wird, dann wird das Unpin halt in
einem Zug beim Komitee gegeben.
Also so können wir Einfluss nehmen.
Das zweite ist die Auslagerungsstrategie.
Jetzt ist unsere Transaktion erfolgreich zu Ende gekommen, gibt
Komitee.
Und unser Puffer-Verwalter sagt, ich entscheide, wann ich die Seite
rausschreibe, nämlich wenn es mir passt, beziehungsweise wenn es in
meine Strategie passt.
Das heißt, er lässt die einfach sitzen im Puffer.
Die kommt gar nicht raus.
Und wir nennen das dann ein No-Force.
Also unser Puffer-Verwalter, dessen natürliche Strategie ist No-Force.
Der sagt, ich entscheide, wann die Seite rauskommt und nicht der
Segment -Verwalter.
Wenn der Segment-Verwalter der Meinung ist, er muss Einfluss nehmen,
er muss dafür Sorge tragen, dass das rausgeschrieben wird bei Komitee,
dann muss er eine Force-Strategie fahren.
Und die Force-Strategie macht er dadurch, er kann auch den Einfluss
direkt nehmen, indem er einfach Flasch gibt.
Er geht hin zum Puffer-Verwalter und sagt Flasch alle Seiten, die noch
nicht rausgeschrieben sind.
Wenn er das Flasch nicht gibt, dann führt eigentlich das Flasch gar
nicht der Segment-Verwalter durch, sondern dann führt der Puffer
-Verwalter das Flasch durch.
Wann immer ihm das passt.
So sehen wir das Problem natürlich auch an der Stelle.
Bei der Force-Strategie, da sind wir sicher, dass die Transaktion
tatsächlich persistent ist bei ihrem Abschluss.
Und bei der No-Force, da wissen wir gar nichts.
Da können wir überhaupt keine Garantien abgeben.
Aber anders als der Segment-Verwalter gibt er im Schedulung gegenüber
Garantien ab.
Der sagt, wenn du Kommitten gibst, dann ist die Transaktion
persistent.
Also muss ganz offensichtlich jetzt unser Segment-Verwalter ran und
der muss an der Stelle dann auf irgendeine Weise, obwohl der Puffer
-Verwalter No-Force fährt, muss er trotzdem erzwingen, dass ein
persistenter Zustand nicht flüchtig infolgedessen geführt wird.
Und das kann er im Grunde genommen nur machen, indem er auf eigene
Faust seinen eigenen Hintergrundspeicher hält, auf dem er jetzt
einfach mal alles sicherheitshalber unterbringt.
Und dieser eigene Hintergrundspeicher unseres Segment-Verwalters, das
ist jetzt die Protokoll-Datei oder Log-Datei.
Man kann die beiden natürlich kombinieren, das sehen Sie noch hier.
Ich kann also Steal und No-Force, Steal und Force, No-Steal-No-Force,
No -Steal-No-Force, nein, am schönsten, da kann fast gar nichts
passieren.
Aber man muss sagen, heute unsere Puffer-Verwalter fahren alle Steal
und No-Force.
Wir sehen auch noch, dass wir jetzt unsere Einbringstrategie auch noch
eine Einbringstrategie, ist bei Steal wirklich die alte Information
verloren, während bei der indirekten Einbringstrategie ist Steal noch
nicht so gefährlich, denn wir verbringen ja die geänderte Seite an
eine neue Stelle.
Also da ist noch gar nicht verloren.
Aber dummerweise ist heute die Kombination fast durchgängig Update-in
-Place, also Verlust der alten Information, Steal, der Puffer
-Verwalter kann verdrängen, und No-Force, niemand kann ihn zwingen zum
Commit auszuschreiben, weil er die Transaktionsverwaltung gar nicht
wahrnimmt.
Und infolgedessen sind wir also gezwungen, diese Log-Datei für den
Segment -Verwalter vorzunehmen.
So, ich lasse mal das andere weg, denn in anderen Fällen sind wir
immer etwas einfacher dran und beschränke mich mal nur hier auf den
Fall der Voll-Me, also Steal, No-Force und Direkt.
So, jetzt können Sie als erstes mal fragen, was ist denn überhaupt
gewonnen, wenn jetzt der Segment-Verwalter da sich so den eigenen
Hintergrundspeicher hält.
Da ist aber in der Tat viel gewonnen, weil Sie diesen
Hintergrundspeicher, das ist eine Spezialplatte, weil Sie die so
anlegen können, dass die Transienz, dass die Latenzzeiten und auch die
Übertragung, vor allem die Latenzzeiten sehr kurz sind.
Sie sind also im Allgemeinen viel schneller, die Daten nichtflüchtig
zu speichern, als wenn Sie in die normale Datenbasis gehen.
Infolgedessen können Sie tatsächlich, zwar fährt dann der Segment
-Verwalter für sich eine Force-Strategie, er schreibt alles aus, aber
zunächst mal sicherheitshalber auf seinen eigenen Speicher und das
geht viel, viel schneller, als das Verbringen durch den Puffer
-Verwalter in die Datenbasis.
Und dann muss man halt zu irgendeinem geeigneten Zeitpunkt später,
wenn irgendwas schief geht, kopieren von dieser Protokolldatei in die
eigentliche Datenbasis.
Aber auf jeden Fall muss der Segment-Verwalter dabei was beachten.
Er kriegt nämlich zwei Regeln an die Hand.
Und zwar die sogenannte Undo-Regel und die Redo-Regel.
Und die haben mit der Atomicität zu tun.
Die Undo-Regel sagt, was ist denn los, wenn ich noch transient bin.
Sie sagt, wenn ich mir nicht sicher bin, ob die Transaktion
erfolgreich zu Ende kommt, also ich bin bei der aktiven Transaktion
nie, dann muss ich in jedem Fall sicherstellen, dass der alte Zustand
noch irgendwo existiert, bevor ich den neuen Zustand überhaupt in
place überschreibe.
Also muss jetzt an der Stelle offensichtlich irgendwie der Segment
-Verwalter entscheiden, wann er auf seine Protokolldatei den alten
Zustand, den sogenannten Before-Image, wann er den schreibt.
Spätestens muss er schreiben, bevor der neue Inhalt in die Datenbasis
gebracht wird.
Inzwischen hat er Spielraum und wir sehen sofort wieder, er kann
unterschiedliche Strategien fahren mit unterschiedlichem Aufwand, wann
er jetzt wirklich vor dem Ersetzen in der Datenbasis den alten Zustand
sichert in seiner Protokolldatei.
Und die Redo-Regel besagt umgekehrt, das Kommit darf der Segment
-Verwalter überhaupt erst nach außen weitergeben, wenn er sicher ist,
dass der neue Zustand, der Endzustand der Transaktion, auch
tatsächlich nicht flüchtig gesichert ist.
In seiner Protokolldatei zum Beispiel.
Und das heißt, er muss jetzt irgendwie Sorge dafür tragen, dass auf
jeden Fall diese Daten stabil und gesichert sind, bevor er dieses
Kommit nach außen gibt.
Das sind die beiden Regeln, die er einhalten muss.
Das ist ein Protokoll.
Das Protokoll beschreibt Spielräume innerhalb der, die ich zu beachten
habe, wenn ich jetzt meine Algorithmen und Strategien festlege.
Und das ist eine Menge Spielraum.
Da kann er schon einiges tun.
Er kann sich entscheiden, wann er das tut, wohin er das tut, welche
Art von der Protokolldatei er sich anlegt, wie er auf die
Protokolldatei schreibt.
Da gibt es eine Menge Spielraum.
Aber diese Regel muss er einhalten.
So, wir haben, das hatte ich schon gesagt, wir haben ja gesehen, dass
wir hier jetzt Bevor- und Afterimages schreiben müssen, dass wir auf
jeden Fall aus Effizienzgründen eine sequentielle und chronologische
geschriebene Datei verwenden.
Und das heißt, wir verzichten auch darauf, nicht benötigte Einträge
sofort zu löschen.
So, jetzt geht das folgendermaßen los.
Die gängige Strategie sieht also so aus.
Erste Riederanweisung läuft ein.
Ich schreibe in mein Protokoll, als Segmentverwalter, ein Begin-of
-Transaction der Transaktion TI, um die es hier geht.
Wenn wir jetzt Deal fahren, was wir ja gesagt haben, üblicherweise der
Fall ist, dann schreiben wir eine Write-Operation.
Dann nehmen wir eine Write-Operation und wenn immer die erscheint,
dann müssen wir zunächst einmal ein Undo schreiben.
Nämlich, jetzt soll ja der alte Zustand durch den neuen ersetzt werden
und wir dürfen laut Ando-Regel das erst tun, wenn der alte Zustand
gesichert ist.
Also spätestens zu diesem Zeitpunkt, vielleicht auch etwas früher,
wird ein Ando-Anweisung in unser Protokoll geschrieben.
Das hat also Transaktionserkennung TI, sagt, welche Seite es ist und
dann wird ein alter Seiteninhalt einfach kopiert in die
Protokolldatei.
An sich kann unser Segmentverwalter sogar noch warten, denn er muss es
spätestens tun, bevor das Flash erfolgt.
Entweder er kontrolliert das Flash selbst oder er muss sich irgendwie
mit dem Pufferverwalter absprechen und herauskriegen, wann der das
Flash durchführen wird und zuvor muss er in jedem Fall noch dieses
Ando auf sein Protokoll geschrieben haben.
Man nennt sowas ein Writer-Headlock.
Ich muss also auf jeden Fall erst den alten Zustand gesichert haben,
bevor ich den neuen nehme.
So, jetzt kommt das Commit.
Beim Commit, wenn wir mal annehmen, dass wir No Force fahren, dann
schreibe ich in mein Protokoll, als Segmentverwalter, ein Redo und das
Redo hat wieder die Transaktionskennung, die Seitennummer X und den
neuen Wert V.
Und wenn ich jetzt alles gesichert habe, wenn ich jetzt sicher bin,
dass jede Ride-Operation, dass deren Zustand jetzt gesichert ist,
entweder in der Datenbasis selbst oder in meinem Protokoll, dann setze
ich in das Protokoll einen EOT-Eintrag.
Und der ist kritisch, denn wenn wir später durch den Lock durch
müssen, um beispielsweise bei Fehlern wieder zu reparieren, dann ist
entscheidend, ist dieser EOT-Eintrag da oder nicht.
Wenn er da ist, dann wissen wir, Transaktion erfolgreich
abgeschlossen.
Ist er nicht da, wissen wir, Transaktion ist nicht erfolgreich
abgeschlossen worden.
Ich zeige das mal hier an einem Beispiel.
Wir sehen mal hier folgendes.
Wir fangen mit dem ersten W1 an, blau, und dann wird geschrieben, das
ist das BOT für diese Transaktion.
Dann schreibt man Undo rein, weil jetzt irgendwann, bevor
ausgeschrieben wird, bevor Image geführt werden muss.
Dann schreiben wir, da kommt das zweite W, es kommt ein Undo, jetzt
kommt ein Unpin und nochmal ein Unpin, und jetzt kommt das Commit.
Und bei dem Commit müssen wir spätestens die Redos ausgeschrieben
haben, und dann schreiben wir das EOT.
So, jetzt kommt die Transaktion 2.
Genau dasselbe, wir schreiben das BOT für T2, dann kommt das Undo,
jetzt kommt nochmal ein W2, kommt nochmal ein Undo für den alten
Inhalt von Y, dann kommt unser Unfix und nochmal ein Unfix, und jetzt
sind wir fertig.
Und es steht kein EOT da.
An der Stelle ist also unsere Transaktion T1 eigentlich gesichert, das
steht auf jeden Fall auf unserem Protokoll.
Aber die Transaktion T2, die ja auch noch gar nicht zu Ende gekommen
ist, da ist kein EOT da, wenn jetzt also irgendwas passiert, wenn wir
mal das Undo fahren müssen.
So, Sie sehen auch, dass da etwas Redundanz drin ist, wenn man ein
Undo hat, ein Redo, und dann die nächste Transaktion betrachtet auf
dem gleichen Element, dann führt man durch die Information etwas
Redundant, das macht man aber sehr bewusst, weil eben auf die Art die
Protokolle sehr viel schneller abzuarbeiten sind.
So, ganz entscheidend ist folgendes, und dafür sorgt unser
Sperrprotokoll, da wir in einer bestimmten Reihenfolge schreiben und
die Ando- und Redo-Regel beachten, garantieren wir gleichzeitig, dass
das, was in der Protokolldatei steht, die abgeschlossene Projektion
unseres Schedules enthält.
Das ist ganz wichtig.
Wir sichern also gleichzeitig, dass wir immer die abgeschlossene
Projektion unseres Schedules rekonstruieren können.
Anhand entweder der Datenbasis und oder der Protokolldatei.
Und das ist deshalb der Fall, weil wir mit den Sperren sicherstellen
und mit den beiden Regeln, dass wir immer vorlösend der Sperre
entsprechend ausschreiben.
Das heißt, wir können nicht plötzlich einen anderen, wir haben also
sogar noch mehr, wir können den äquivalenten seriellen Schedule für
dieses CP von H sogar konstruieren, weil wir alles schön sequenziell
schreiben.
Es ist sogar so, wenn wir den Commit-Zeitpunkt, wie wir ja gesagt
haben, als den Äquivalenzzeitpunkt nehmen, gibt die Reihenfolge der
EOT -Einträge sogar exakt wieder, was die äquivalente serielle Abfolge
der Transaktion ist.
Also hier spielen Sperrenverwaltung, aber nur die Annahmen, die wir
darüber machen, aber im Endeffekt die Sperrenverwaltung und dann
unsere Ando und Rido-Regel, die spielen so zusammen, dass wir jetzt
wirklich sichern können, dass wir, obwohl wir die Transaktion selbst
gar nicht kennen, in unserem Segmentverwalter oder Recovery-Verwalter
sogar das CP von S garantieren können.
So, jetzt nehmen wir mal an, was muss denn jetzt passieren.
Wenn wir uns genauer ansehen, was mit so einem Commit, wenn so ein
Commit einläuft, dann wird tatsächlich dieses Commit in zwei Phasen
zerlegt.
Die Phase 1, die stellt zunächst mal sicher, dass wir tatsächlich
wiederholen können.
Dass wir, wenn wir einen bestimmten Zustand erreicht haben, das ist
ein Endzustand, diese Phase, dann ist unsere Transaktion gesichert.
Es kann nichts mehr passieren, die ist persistent.
Wir wissen, dass das dann der Fall ist, wenn wir das EOT geschrieben
haben, in unserem Protokoll.
So, und jetzt machen wir eben folgendes, wenn wir jetzt hier so ein
geändertes Datenelement X antreffen, das jetzt gesichert werden muss,
dann können wir, wenn wir Force fahren, über das gleiche Flasch geben,
das haben wir ja vorhin gesehen, und bei Nino Force, da schreiben wir
eben das Redo.
Spätestens.
Und wenn in dieser Phase jetzt noch eine Störung einläuft, auch das
Commit dauert ja seine Zeit jetzt, ist also auch anfällig gegen
Störungen.
Und jetzt gilt das Gesetz der Idempotenz, beziehungsweise an dieser
Stelle zunächst einmal gilt sogar, dass wenn es uns an dieser Phase,
haben wir das EOT noch nicht gegeben.
Also wenn jetzt eine Störung einläuft, obwohl der Benutzer eigentlich
schon meinte, er ist fertig, wird auf Abort entschieden.
Wenn ein EOT fehlt, dann werden nämlich die Undo-Einträge betrachtet
und nicht die Redo-Einträge.
Also es ist sichergestellt, dass solange das EOT nicht da ist, wird
die Transaktion, wenn irgendwas passiert, immer als eine Transaktion,
die abgebrochen werden muss, betrachtet.
Wenn das EOT geschrieben ist, von da ab, was immer passiert, gilt
jetzt, dass die Transaktion erfolgreich zu Ende gekommen ist.
Das heißt, wenn wir jetzt gezwungen sind, ein Redo durchzuführen, dann
wird die Transaktion einfach anhand der Redo-Einträge rekonstruiert.
So, die zweite Phase, die schließt halt dann die Transaktion ab.
Das heißt, das Kommit, wenn es erfolgreich war, meldet sich unsere
Transaktion, unser Segment-Verwalter beim Scheduler.
Und der Scheduler sagt, Moment, ich muss auch noch was tun, ich muss
nämlich noch die Sperren freigeben.
Aber das ist unabhängig aus der Sicht unserer Segment-Verwaltung, ist
die Transaktion jetzt persistent geworden.
So, schauen wir mal an, das Rücksetzen bei Abort.
Leser brauchen wir nicht anzusehen, sind ganz einfach.
Nehmen wir mal jetzt ein Abort an und wir haben also eine
Schreibtransaktion, so wie die letzte, die wir da hatten.
Und dann gehen wir folgendermaßen vor.
Wir wissen ja sowieso im Augenblick, dass gar kein EOT geschrieben
werden konnte.
Es wird ja auch ganz regulär ein Abort gegeben.
Wir nehmen jetzt jeden Eintrag an, und wir lesen jetzt das Alte, wir
lesen jetzt den Inhalt, wir lesen zunächst mal die Seite in den Puffer
ein, oder möglicherweise ist es noch dort.
Dann ersetzen wir den Inhalt von X und setzen ihn wieder auf den alten
Wert, den wir in unserem Undo-Eintrag haben.
Entweder, möglicherweise noch gar nicht geändert worden, dann ist es
trivial.
Andernfalls wird er halt ersetzt durch sich selbst.
Und wenn doch per Stil schon der neue Inhalt dort ist, wird halt
wieder alter Inhalt dahingesetzt.
Und jetzt hängt wieder davon ab, wenn jetzt wir eine Force-Strategie
fahren, dann sagt unser Recovery-Verwalter Flush, wenn No Force ist,
dann dreht er den Spieß um.
Dann sagt er, ich mache jetzt aus dem Undo sozusagen ein Redo, nämlich
ich stelle jetzt, ich schreibe rein, ich gebärde mich so, als ob das
Zurücksetzen ein Kompensieren war, und ich eine erfolgreiche
Kompensationstransaktion durchgeführt habe und schreibe infolgedessen
am Ende wieder ein EOT.
Und dann gebe ich wieder die Sperren frei.
So, darauf müssen wir uns verlassen, denn wenn jetzt der
Systemzusammenbruch kommt, dann müssen wir anschließend, jetzt nach
dem Systemzusammenbruch, den alten Zustand wiederherstellen, und zwar
das CP von S, wie es vor dem Systemzusammenbruch existierte.
Und das heißt, die da abgebrochenen Transaktionen, sind bereits aus
dem System entfernt.
Haben wir gerade gesehen.
Stehen also möglicherweise per Redo jetzt auch in unserer
Protokolldatei.
Die abgeschlossenen Transaktionen sind per Assistent gemacht.
Es kann allerdings sein, dass die Einträge zum Teil noch als Redo
-Einträge im Protokoll stehen.
Und dann gibt es Transaktionen, die waren aktiv in der Zeit des
Systemzusammenbruchs.
Und da ist nichts zu machen.
Für diese Transaktion entscheide ich mich jetzt, dass sie abgebrochen
werden müssen.
Also haben wir im Wesentlichen aus der Sicht unseres
Transaktionsverwalters, abgeschlossene Transaktionen, einschließlich
der, die durch Kompensation abgeschlossen worden sind, und aktive
Transaktionen.
Die aktiven zeichnen sich dadurch aus, dass kein EOT-Eintrag da ist.
Also werden sie mit Abort behandelt.
Und die anderen werden als abgeschlossene angesehen und werden
notfalls einfach per Redo in die Datenbasis verbracht.
Ich werde das jetzt einfach mal an einem Beispiel noch vorführen,
abschließend.
Hier sehen Sie, das ist unsere ursprüngliche Datei.
Da sehen wir da oben unsere Folge.
Das ist die von vorhin.
Und wir haben also hier jetzt unsere verschiedenen Anweisungen.
Und jetzt gehen wir...
Und jetzt wird die auch wieder nach dem Abort-Algorithmus behandelt.
Wir holen uns jetzt die Seite in den Puffer, ersetzen den Inhalt, und
protokollen ein Redo an der Stelle des Undo.
Oder vielmehr eigentlich zusätzlich zu dem Undo.
So, dann machen wir das gleiche nochmal.
Also kommt auch da noch ein Redo dazu.
Und jetzt treffen wir auf das BOT und sagen, Aha, in Folge dessen
können wir jetzt ein EOT geben.
Und jetzt sehen Sie, jetzt haben wir aus der alten, abgebrochenen, der
Transaktion, die wir abbrechen mussten, eine gemacht, die
wiederherstellbar ist, aber wiederhergestellt, wir haben sozusagen
eine künstliche Transaktion erzeugt, die jetzt das Wiederherstellen
besorgt.
Und die können wir jetzt lassen.
Wenn jetzt zum Beispiel ein...
Dann stoßen wir jetzt auf ein EOT und führen sozusagen diese
Kompensations...
in eine erfolgreiche Transaktion, aber eine, die kompensierende
Wirkung hat.
Und jetzt ist immer entscheidend, diese ganzen Vorgänge, diese
Protokolle müssen so angelegt sein, dass, wenn eine neuerliche Störung
während des Wiederherstellens erfolgt, dass kein Schaden entsteht.
Das heißt, wir müssen immer sicherstellen, dass die Datenbasis immer
konsistent bleibt.
Auch wenn wir gerade wiederherstellen, und es kommt zu einer neuen
Störung, müssen wir in der Lage sein, die Datenbasis
wiederherzustellen.
Und das ist auch deutlich zu sehen.
Wir sehen immer, wenn kein EOT-Satz da ist, dann fahren wir auf diesen
Transaktionen das Abort-Protokoll und das nimmt immer die Undo
-Einträge.
Und die sind ja doch da, wie wir gerade gesehen haben.
Wir schreiben ja nur zusätzliche Re-Do-Einträge beim Abort.
Und wenn ein EOT-Satz da ist, dann ignorieren wir die Undo-Einträge
und nehmen die Re-Do-Einträge.
Also wir sagen immer, EOT-Satz da, nur Re-Do-Einträge beachten.
Kein EOT-Eintrag da.
Wir betrachten nur die Undo-Einträge.
So, jetzt kann man noch einige Tricks spielen.
Man kann das alles kombinieren.
Das dürfen Sie sich auf den Folien ansehen, in denen man irgendwelche
Watermarks setzt.
Und diese Verfahren kann man nun modifizieren, etwas erweitern.
Vielleicht machen wir es nach einem Bild noch zum Abschluss.
Auch wenn jetzt nicht mehr der Inhalt des Hauptspeichers verloren
geht, wie beim Systemzusammenbruch, sondern wenn jetzt Fehler auf dem
Hintergrundspeicher auftreten.
Wenn plötzlich Ihre Datenbasis selbst in Gefahr gerät.
Und wenn man das macht, dann kann sich die Datenbasis auf sogenannte
Archivbänder.
Also das wird dort archiviert.
Und jetzt können wir uns mal ansehen, was passiert.
Und dann kommt hier der Verlust.
Hier ist plötzlich unser Datenträger verloren gegangen.
Hier haben wir einen EOT-Satz, der ist sogar vor diesem Check, das wir
hier setzen, da ist sogar schon die Transaktion vollständig in dieser
Kopie, in der Archivkopie enthalten.
Gar kein Problem.
Die da war noch aktiv während des Archivierens und ist aber
inzwischen, wie wir sehen, an dieser Stelle war ja ein Abort, die ist
aber inzwischen ja als Kompensationstransaktion sehr wohl mit einem
EOT -Satz verbunden.
Das heißt, wir fahren die erneut, diese Transaktion, per ReDU, und
dabei stellt sie aber den alten Zustand her.
T3, das ist unschön, T3 hat keinen EOT-Satz, muss also zurückgesetzt
werden.
Das ist sehr kompliziert, weil wir nämlich hier auch noch über die
Grenze des Archivierens rausfahren.
Und hier ist auch kein EOT-Satz, der ist etwas einfacher, weil wir da
die Spuren erst gar nicht in der Kopie haben.
Das heißt, da muss ich nur feststellen, dass ich die ignorieren kann,
diese Transaktion.
So, das war jetzt ein Eildurchgang, sehr schnell, durch den, was man
als Speicherverwalter ansieht, und was also heute einen wesentlichen
Teil unserer kommerziellen Systeme liefert, extrem komplizierte
Gebilde sind, dort entscheidet im Wesentlichen auch die Performance,
aber auch die gesamte Robustheit und Transaktionssicherheit.
So, ich muss nochmal erklären, es gibt da so ein paar Dinge, die ich
mit der Frau Zitterbart abklären muss, ob ich noch den Rest mache,
oder ob ich hier das Feld überlasse.
Möglicherweise sehen Sie mich am Donnerstag nicht mehr, dann
verabschiede ich mich von Ihnen, möglicherweise sehen Sie mich doch
nochmal kurz, dann kann ich mich da noch verabschieden.
Wir werden aber jetzt anschließend noch die Beurteilungsbögen
ausgeben, wie ist das, der Herr Abek, sind die auch dabei?
Wir machen eine getrennte Auswertung für Herrn Abek, und dann für
meinen Teil, also ich habe ordentlich zu arbeiten, und das ist
natürlich alles, die Frau Zitterbart wird dann auch nochmal für ihren
Teil das ausgeben, aber wir wollen Ihre Meinung wissen, und ich möchte
nochmal daran erinnern, was uns auch interessiert, aber wahrscheinlich
nicht im Rahmen des Fragebogens, sondern erst später, bei der
Prüfungsvorbereitung, welche Medien setzen Sie wie ein, welche
schätzen Sie als besonders erfolgreich für die Prüfungsvorbereitung?
Ich verabschiede mich.