Qualitative und quantitative Evaluation von Varianten zur

Werbung
Universität Ulm
Fakultät für Ingenieurwissenschaften und Informatik
Institut für Datenbanken und Informationssysteme
Qualitative und quantitative Evaluation von Varianten zur
performanten Verwaltung von Prozessdaten in einem
Hochleistungs-PMS
Diplomarbeit
vorgelegt von
Bastian Philipp Glöckle
am 23. Dezember 2009
1. Gutachter: Prof. Dr. Manfred Reichert
2. Gutachter: PD Dr. Stefanie Rinderle-Ma
Betreuer: Dipl.-Inf. (U) Ulrich Kreher
Kurzfassung
Der stetig anwachsende Grad der Globalisierung ließ Prozess-Management-Systeme (PMS)
in den letzten Jahren stark an Bedeutung gewinnen. Diese bilden Prozesse ab, welche
dann computergestützt durchgeführt werden können. Hierzu müssen die Prozesse dem
PMS bekannt sein. Ein Modell, welches dies ermöglicht ist das ADEPT2-Metamodell
[Rei00]. Dieses muss in einer geeigneten Speicherrepräsentation dem PMS zur Verfügung
gestellt werden. Ziel dieser Arbeit ist, durch qualitative und quantitative Untersuchungen
eine optimale Repräsentation zu ermitteln.
Das ADEPT2-Metamodell gliedert die Daten in Schema- und Instanzdaten. Schemata
bilden hierbei den Bauplan eines Prozesses, Instanzen dessen Ausführung. Neben der
Modellierung und der Ausführung sieht das Metamodell auch die flexible Änderung von
Prozessen vor. Hierbei sind Änderungen sowohl auf Instanz- als auch auf Schemaebene
möglich. Dies muss als zentrales Merkmal in die Repräsentation der Daten einfließen.
In einem ersten Schritt werden verschiedene Konzepte vorgestellt. Dies umfasst die Partitionierung von Schemata und die konzeptionelle Umsetzung dieser. Es stellt sich heraus,
dass neben verschiedenen Repräsentationen für Instanzen auch unterschiedliche Repräsentationen für Schemata existieren. Zusätzlich wird die grobe Clusterung vorgestellt,
welche eine Einschätzung des Laufzeitzustands von Instanzen ermöglicht. Hierdurch lässt
sich die Laufzeit der so genannten Schemaevolution [Rin04] verbessern.
Diese Konzepte werden anschließend umgesetzt und implementiert. Hierbei werden sowohl Repräsentationen im Primär- als auch im Sekundärspeicher vorgestellt. Es kommen
verschiedene Sekundärspeichervarianten zum Einsatz, wie zum Beispiel eine Speicherung
in XML-Dateien oder ausgewählten relationalen Datenbanksystemen. Mithilfe dieser Implementierung werden auf Basis von Anwendungsszenarien Messungen der verschiedenen
Repräsentationsvarianten durchgeführt. Hierbei werden Laufzeiten von Anfragen an die
Primärpseicherrepräsentation untersucht, ebenso wie das Ein- und Auslagern der verschiedenen Sekundärspeicherrepräsentationen und Anfragen an Kollektionen. Die Messergebnisse werden anschließend interpretiert und verglichen. Ein abschließendes Fazit
stellt als Folgerung aller Untersuchungen die optimale Repräsentation der Daten vor.
Die Vorstellung alternativer Konzepte zur Modellierung von Prozessen, welche anstelle
des ADEPT2-Metamodells verwendet werden können, rundet diese Arbeit ab.
Inhaltsverzeichnis
1 Einleitung
1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Aufgabenstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3 Aufbau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2 Grundlagen
2.1 Das ADEPT2-Metamodell . . . . . . .
2.1.1 Grundlegende Begrifflichkeiten
2.1.2 Schemata . . . . . . . . . . . .
2.1.3 Instanzen . . . . . . . . . . . .
2.1.4 Abstraktion . . . . . . . . . . .
2.1.5 Zusammenfassung . . . . . . .
2.2 Repräsentation . . . . . . . . . . . . .
2.2.1 Schemata . . . . . . . . . . . .
2.2.2 Instanzen . . . . . . . . . . . .
2.2.3 Instanzbasierte Änderungen . .
2.2.4 Zusammenfassung . . . . . . .
2.3 ADEPT2-Architektur . . . . . . . . .
2.4 Anwendungsszenarien . . . . . . . . .
2.4.1 Weiterschalten . . . . . . . . .
2.4.2 Schemaevolution . . . . . . . .
2.4.3 Anfragen auf Kollektionen . . .
2.4.4 Zusammenfassung . . . . . . .
2.5 Allgemeine Speicherbetrachtungen . .
2.6 Zusammenfassung . . . . . . . . . . .
1
1
1
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
3
3
4
6
7
8
8
8
9
10
13
13
15
15
15
18
19
19
20
3 Konzepte
3.1 Ansätze zur Partitionierung von Schemata . . . . . . .
3.1.1 Partitionierung anhand Bearbeiterzuordnungen
3.1.2 Blockstruktur . . . . . . . . . . . . . . . . . . .
3.1.3 Fazit . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Schemata . . . . . . . . . . . . . . . . . . . . . . . . .
3.2.1 Speicherbetrachtungen . . . . . . . . . . . . . .
3.2.2 Relation zwischen Knoten . . . . . . . . . . . .
3.2.3 Blockrepräsentation . . . . . . . . . . . . . . .
3.3 Grobe Clusterung . . . . . . . . . . . . . . . . . . . . .
3.3.1 Schemaevolution . . . . . . . . . . . . . . . . .
3.3.2 Fazit . . . . . . . . . . . . . . . . . . . . . . . .
3.4 Instanzbasierte Änderungen . . . . . . . . . . . . . . .
3.4.1 Erweiterung des Deltaschicht-Modells . . . . .
3.4.2 Speicherbetrachtungen . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
21
21
23
24
25
25
25
26
28
33
34
40
41
41
42
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
i
3.5
3.4.3 Repräsentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.4.4 Clusterung und Schemaevolution . . . . . . . . . . . . . . . . . . . 46
Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4 Umsetzung
4.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.1.1 Relevante Aspekte von Java™ . . . . . . . . . . . . . .
4.1.2 Architektur . . . . . . . . . . . . . . . . . . . . . . . .
4.2 Primärspeicher . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2.1 Standard-Datenstrukturen von Java™ . . . . . . . . . .
4.2.2 Schemata . . . . . . . . . . . . . . . . . . . . . . . . .
4.2.3 Deltaschicht . . . . . . . . . . . . . . . . . . . . . . . .
4.2.4 Instanzen . . . . . . . . . . . . . . . . . . . . . . . . .
4.2.5 Cluster . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2.6 Zusammenfassung . . . . . . . . . . . . . . . . . . . .
4.3 Sekundärspeicher . . . . . . . . . . . . . . . . . . . . . . . . .
4.3.1 Realisierungsalternativen . . . . . . . . . . . . . . . .
4.3.2 Schnittstellen zwischen Primär- und Sekundärspeicher
4.3.3 Schemata . . . . . . . . . . . . . . . . . . . . . . . . .
4.3.4 Instanzen . . . . . . . . . . . . . . . . . . . . . . . . .
4.3.5 Zwischenspeicher . . . . . . . . . . . . . . . . . . . . .
4.3.6 Flüchtiger Speicher . . . . . . . . . . . . . . . . . . . .
4.3.7 Zusammenfassung . . . . . . . . . . . . . . . . . . . .
4.4 Anfragen auf Kollektionen . . . . . . . . . . . . . . . . . . . .
4.4.1 Instanzen eines Schemas . . . . . . . . . . . . . . . . .
4.4.2 Instanzen mit bestimmten Knotenzustand . . . . . . .
4.4.3 Zusammenfassung . . . . . . . . . . . . . . . . . . . .
4.5 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . .
5 Messungen
5.1 Rahmenbedingungen . . . . . .
5.2 Sekundärspeichermessungen . .
5.2.1 Einführung . . . . . . .
5.2.2 XML . . . . . . . . . . .
5.2.3 PostgreSQL . . . . . . .
5.2.4 Oracle . . . . . . . . . .
5.2.5 DB2 . . . . . . . . . . .
5.2.6 JPA . . . . . . . . . . .
5.2.7 Vergleich . . . . . . . .
5.2.8 Zusammenfassung . . .
5.3 Primärspeichermessungen . . .
5.3.1 Schemata und Instanzen
5.3.2 Clusterung . . . . . . .
5.4 Fazit . . . . . . . . . . . . . . .
ii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
49
49
49
50
52
52
53
56
57
58
59
59
60
60
61
68
72
73
73
73
74
74
75
76
.
.
.
.
.
.
.
.
.
.
.
.
.
.
77
77
79
80
81
86
91
95
100
101
107
107
108
111
114
6 Verwandte Konzepte
117
7 Zusammenfassung und Ausblick
119
7.1 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
7.2 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
A Abbildungsverzeichnis
vii
B Tabellenverzeichnis
xi
C Literaturverzeichnis
xiii
D Algorithmen
xix
D.1 getNodeRelation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix
D.2 findPositions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix
D.3 preEvolveSelection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiv
iii
1 Einleitung
1.1 Motivation
Die Entwicklung des Menschen zum dominanten Wesen auf diesem Planeten liegt auch
in der Fähigkeit begründet, prozessorientiert zu denken. Um ein Ziel zu erreichen, müssen zuerst andere Tätigkeiten als Vorbereitung ausgeführt werden. Zum Beispiel war es
nötig, ein geeignetes Tier zu finden und zu erlegen, bevor man dessen Haut zu einem Kleidungsstück verarbeiten konnte. Im Laufe der Zeit wurde diese Prozessorientierung weiter
verfeinert. So führte Henry Fords Erfolg des Fließbands am Anfang des 20. Jahrhunderts
zu einer Art zu arbeiten, bei der jeder Arbeiter für einen Arbeitsschritt spezialisiert ist
und die Fertigung eines Erzeugnisses nicht mehr von Anfang bis Ende begleitet. Die einsetzende Globalisierung ist bislang einer der letzten Schritte in dieser Entwicklung. Diese
erfordert eine Unterstützung der Prozesse, welche in weltumspannenden Firmen ablaufen. Hierbei helfen Prozess-Management-Systeme (PMS), welche sich die elektronische
Datenverarbeitung hierfür zu Nutze machen. So wird ein anstehender Prozessschritt zur
richtigen Zeit dem richtigen Mitarbeiter vorgelegt, egal wo sich dieser auf der Welt befindet. Nachdem die anstehende Aufgabe bearbeitet wurde, wird der Prozess automatisch
dem für den nächsten Prozessschritt verantwortlichen Mitarbeiter vorgelegt, welcher die
Bearbeitung des Prozesses fortsetzt.
PMS müssen die Prozesse kennen, welche mit ihrer Hilfe durchgeführt werden sollen. Hierfür ist ein Modell dieser nötig, welches in Form einer geeigneten Repräsentation dem PMS
zur Verfügung gestellt werden kann. Es bietet sich ein Prozessgraph an, mithilfe dessen
ein Prozess vollständig beschrieben wird. Dieser Graph besteht hierbei aus einer Menge
durch Kanten aneinander gereihten Prozessschritten, welche je einen Arbeitsschritt abbilden. Dieser Prozessgraph muss im PMS durch geeignete Strukturen beschrieben werden.
Hierbei existieren verschiedene Varianten. Zu beachten ist dabei, dass für die Akzeptanz
eines PMS die performante Verarbeitung von Anfragen unabdingbar ist. So ist es beispielsweise denkbar, das 105 Prozesse gleichzeitig ablaufen. Für diese ist eine effiziente
Verwaltung und Repräsentation nötig.
1.2 Aufgabenstellung
In dieser Arbeit werden verschiedene Repräsentationen von Prozessen verglichen. Dies
wird am Beispiel des ADEPT2-Metamodells [Rei00] untersucht, welches an der Universität Ulm entwickelt wurde. Die angestellten Untersuchungen sind allerdings allgemein
gültig und auf andere PMS [vv04] übertragbar.
Die Prozessdaten gliedern sich in Schemata und Instanzen. Schemata werden einmalig
modelliert und bilden die Vorlage, den „Bauplan“ eines Prozesses. Die Durchführung von
diesem findet mithilfe von Instanzen statt. Diese basieren auf einem Schema und stellen
die Abarbeitung der dort definierten Prozessschritte dar. ADEPT2 bietet hierbei große
Flexibilität in Form von instanzbasierten Änderungen [RD98]. Dies bedeutet, dass der
1
1 Einleitung
Prozess-Bauplan einer Instanz geändert werden kann, ohne dass andere Instanzen, welche
auf dem selben Schema basieren, von der Änderung betroffen sind. Ein weiteres Merkmal
der Flexibilität ist die Schemaevolution [Rin04]. Mithilfe dieser kann ein Schema geändert
und die darauf basierenden Instanzen zu Laufzeit automatisch auf das neue Schema, den
neuen Bauplan des Prozesses, übertragen werden, sofern deren Zustand dies erlaubt.
Die Herausforderung hierbei ist, auch instanzbasiert geänderte Instanzen automatisch zu
übertragen.
Diese Flexibilität muss als ein Merkmal bei der Erarbeitung von Repräsentationen von
Prozessdaten beachtet werden. Diese Arbeit stellt verschiedene Varianten vor, Schemata
und Instanzen zu repräsentieren. Diese werden nach einer qualitativen Untersuchung des
Speicherverbrauchs und der zu erwartenden Laufzeiten realisiert. Mithilfe dieser Implementierung werden anschließend Messungen durchgeführt, um die Repräsentationen auch
quantitativ vergleichen zu können. Bei diesen Untersuchungen wird einerseits der Speicherplatzverbrauch der verschiedenen Varianten ermittelt und verglichen. Andererseits
wird die Laufzeit von verschiedenen Anfragen, wie sie in PMS typisch sind, auf Basis
der verschiedenen Repräsentationen untersucht. Das Ziel besteht darin, eine optimale
Repräsentationen bezüglich der Anforderungen zu ermitteln.
1.3 Aufbau
Die Arbeit gliedert sich wie folgt: Die Grundlagen werden in Kapitel 2 vorgestellt. Hier
werden neben dem ADEPT2-Metamodell und der ADEPT2-Architektur auch vorhandene Untersuchungen zur Repräsentation der Prozessdaten betrachtet. Ebenso werden
typische Anfragen an das PMS beschrieben. Die hierauf aufbauenden Konzepte werden
in Kapitel 3 entwickelt. Diese betreffen Repräsentationen von Schemata und die Verwaltung von Instanzen. Kapitel 4 stellt die Umsetzung der Grundlagen und Konzepte
vor. Nach der Wahl einer Programmierumgebung wird die hierfür spezifische Umsetzung
beschrieben. Aufbauend darauf werden Messungen durchgeführt, welche in Kapitel 5 betrachtet werden. Hierbei werden nach der Entwicklung von Testszenarien die Ergebnisse
präsentiert und interpretiert und ein Fazit aus diesen gezogen. Kapitel 6 stellt verwandte Konzepte zur Repräsentation von Prozessen vor. Abschließend wird diese Arbeit in
Kapitel 7 zusammengefasst und ein Ausblick auf Verbesserungen und Erweiterungen gegeben.
2
2 Grundlagen
Dieses Kapitel erläutert die Grundlagen auf denen diese Arbeit beruht. Nach der Vorstellung des ADEPT2-Metamodells werden aktuelle Forschungsergebnisse zur Repräsentation der Prozessdaten betrachtet. Es folgt eine Vorstellung der ADEPT2-Architektur,
in welche sich die in dieser Arbeit entwickelten Konzepte einbetten. Die für Laufzeitmessungen notwendigen Anwendungsszenarien werden danach im Detail untersucht. Um den
Speicherbedarf betrachten zu können, werden abschließend grundlegende Speicherarten
rekapituliert.
2.1 Das ADEPT2-Metamodell
Das ADEPT2-Metamodell definiert Strukturen zur Darstellung von Prozessgraphen. Für
das Verständnis ist zuerst eine Begriffsbildung nötig. Danach werden grundlegende Strukturen des ADEPT2-Metamodells vorgestellt. Da im Rahmen dieser Arbeit nicht das vollständige Metamodell benötigt wird, wird dieses abschließend vereinfacht.
2.1.1 Grundlegende Begrifflichkeiten
Ein Prozess-Management-System (PMS) verwaltet Prozesse, wobei diese auf Schemata
abgebildet werden. Ein Schema ist dabei die Vorlage, der „Bauplan“ eines Prozesses. Hierauf basieren Instanzen, welche die Durchführung eines Prozesses abbilden. Diese Trennung zwischen Modellierung und Ausführung ist sinnvoll, um einen häufig ablaufenden
Prozess nicht mehrmals definieren zu müssen. Dieser kann stattdessen einmal modelliert und häufig durchgeführt werden. In Abbildung 1 ist ein Schema mit zwei darauf
basierenden Instanzen dargestellt.
Instanz 2
Instanz 1
Schema
3
Start
2
5
4
RUNNING
COMPLETED
3
3
End
Start
2
5
End
Start
4
2
5
End
4
SKIPPED
NOT_ACTIVATED
Abbildung 1: Ein Schema mit zwei Instanzen
Das ADEPT2-Metamodell [Rei00] definiert ein Schema als Graph [bpm09, Ley01, LA94,
MWW+ 98]. Dieser ist azyklisch und gerichtet [DD90, CLRS09], wobei jeder Knoten einen
Arbeitsschritt, einen so genannten Prozessschritt abbildet. Zusätzlich beinhaltet es einen
Start- und einen End-Knoten, welche den Start- bzw. Endpunkt des Prozesses darstellen.
3
2 Grundlagen
Durch die Verknüpfung der Knoten mit gerichteten Kanten entsteht eine Abfolge von
Prozessschritten, welche in der durch die Kanten angegebenen Reihenfolge durchlaufen
werden. Diese Kanten heißen Kontrollkanten und bilden den so genannten Kontrollfluss.
Jeder Knoten besitzt eine Aktivität, welche bei der Durchführung dieses Prozessschrittes
ausgeführt wird. Hierbei kann es sich zum Beispiel um die Anzeige oder Abfrage von
Daten mithilfe von Formularen [Mic09] handeln. Da ADEPT2 als Mehrbenutzersystem
konzipiert ist, muss für jede Aktivität eine Bearbeiterzuordnung definiert werden. Diese legt fest, welcher Mitarbeiter den Prozessschritt zur Ausführung vorgelegt bekommt.
Hierfür existiert für jeden Benutzer eine Arbeitsliste, in welcher die anstehenden Prozessschritte aufgeführt sind.
Bei der Durchführung der Aktivitäten fallen Daten an, welche erzeugt, geändert, gelesen
[Rei00] oder konsumiert [For09] werden können. Dies wird durch den Datenfluss [RD98]
abgebildet, welcher implizit Teil des Schemas ist. Er besteht ebenso aus Knoten, den
so genannten Datenelementen, welche über Datenkanten mit den Prozessschritten verbunden sind. Die Datenkanten sind hierbei ebenfalls gerichtet und bestimmen, ob der
Prozessschritt lesend oder schreibend auf das Datenelement zugreift.
2.1.2 Schemata
Schemata sind Prozessgraphen, welche die Vorlage von Prozessen bilden. Sie werden in
dieser Arbeit mithilfe des ADEPT2-Metamodells dargestellt, welches verschiedene Strukturen hierfür vorsieht. So existieren neben verschiedenen Kantenarten auch verschiedene
Typen von Knoten. Normale Knoten besitzen eine Aktivität, Verzweigungs- und Schleifenknoten dienen zur Steuerung des Kontrollflusses und Start- und End-Knoten bilden
Einstiegs- bzw. Endpunkt des Prozesses. Für diese Strukturen existiert eine Notation,
welche in Abbildung 2 dargestellt ist.
Daten
1
Daten
2
5
Start
2
3
11
6
9
4
7
8
13
10
14
15
16
End
12
Abbildung 2: Ein ADEPT2-Schema
In Abbildung 2 bilden die Knoten 2 und 3 eine Sequenz. Der nachfolgende sogenannte
AND-Split-Knoten 4 leitet eine Parallelverzweigung ein. Es folgen verschiedene Zweige, welche alle parallel abgearbeitet werden. Die Zusammenführung dieser bewerkstelligt
Knoten 9, welcher ein AND-Join-Knoten ist. Knoten 10 ist ein XOR-Split -Knoten, welcher eine Entweder-Oder-Verzweigung einleitet. Diesem folgen wiederum mehrere Zweige,
4
2.1 Das ADEPT2-Metamodell
wobei hier allerdings zur Laufzeit nur einer der Zweige ausgeführt wird und die anderen
ignoriert werden. Die Entscheidung, welcher Zweig ausgeführt wird, wird durch Auswerten des Datenelements 2 getroffen. Die Zusammenführung der Zweige erfolgt durch
Knoten 13, einem so genannten XOR-Join-Knoten. Der Knoten 14 ist ein Loop-StartKnoten. Die Schleife wird durch den zugehörigen Loop-End-Knoten 16 begrenzt. Die
Kante von Knoten 16 zu Knoten 14 ist hierbei eine spezielle Schleifenkante. Diese wird
im ADEPT2-Metamodell gesondert behandelt, um die azyklische Eigenschaft des Prozessgraphen zu erhalten. Diese Eigenschaft gilt bezüglich der Kontrollkanten (nicht der
Schleifen- und Datenkanten) und ist für Korrektheitsuntersuchungen notwendig.
Die Kanten eines Schemas können attributiert sein. Hierbei ist zum Beispiel der Kantentyp zu nennen. Es gibt Kontroll-, Schleifen-, Daten- und so genannte Synchronisationskanten. Letztere dienen dazu, die Parallelausführung von Knoten zeitlich aufeinander
abzustimmen, so dass ein Prozessschritt zwingend nach dem anderen ausgeführt wird.
Dies ist zum Beispiel sinnvoll, wenn ein Prozessschritt eines parallelen Zweigs Daten ändert, die von einem anderen Prozessschritt benötigt werden, welcher auf einem anderen
Zweig der selben parallelen Verzweigung liegt. Solch eine Kante ist in Abbildung 2 zwischen Knoten 6 und 7 zu sehen. Hierbei kann der Prozessschritt 6 erst gestartet werden,
wenn Knoten 7 abgeschlossen ist. Dies wird benötigt, da die Daten von Prozessschritt 7
zuerst in das Datenelement 2 geschrieben werden müssen, bevor Prozessschritt 6 diese
lesen kann.
Betrachtet man die Knoten des Schemas ausschließlich unter Beachtung der Kontrollkanten, so entsteht eine streng hierarchische Blockstruktur [Rei00] wie die Rechtecke
verschiedener Grautöne in Abbildung 3 verdeutlichen.
Daten
1
Daten
2
6
Start
2
3
4
5
10
8
7
12
9
13
14
15
End
11
Abbildung 3: Die Blockstruktur eines ADEPT2-Schemas
Diese Blockstruktur zeigt, dass Kontrollstrukturen wie Sequenzen, Schleifen, Parallelund Entweder-Oder-Verzweigungen eindeutige Start- und Endknoten besitzen. Hierdurch
ist es zum Beispiel möglich einem Split-Knoten einen Join-Knoten eindeutig zuzuordnen.
Diese Blöcke dürfen sich nicht überlagern, können aber beliebig ineinander geschachtelt
werden. Durch diese strenge Struktur werden Prozesse übersichtlicher und viele Fehler,
welche beim Modellieren unterlaufen könnten (zum Beispiel falsches Zusammenführen
von Zweigen), sind dadurch ausgeschlossen. Eine korrekte Blockstruktur ist Teil der De-
5
2 Grundlagen
skip
activate
NOT_ACTIVATED
ACTIVATED
reset
deselect
select
skip
SELECTED
start
SKIPPED
start
suspend
SUSPENDED
resume
fail
FAILED
RUNNING
complete
COMPLETED
Abbildung 4: Knotenzustände und -übergänge (in Anlehnung an [Rei00])
finition von Korrektheit des ADEPT2-Metamodells. Hierzu zählt auch der Ausschluss von
Verklemmungen, wie sie durch Synchronisationskanten zustande kommen können [Jur06].
Ebenso beachtet werden Datenelemente. Es ist sichergestellt, dass ein Datenelement nicht
gelesen werden kann, bevor es geschrieben wurde und dass kein geschriebener Wert ungelesen überschrieben wird („lost update“). Hinzu kommt, dass Synchronisationskanten in
oder aus Schleifenkonstrukten verboten sind. Durch die Bereitstellung von Änderungsoperationen [Jur06] ist diese Korrektheit sichergestellt. Diese dienen zum Ändern von
Schemata. Dabei gelten für die Anwendung einer Änderungsoperation Vor- und Nachbedingungen, mithilfe derer eine Änderung hin zu einem inkorrekten Schema festgestellt
und unterbunden wird.
Bei der Erzeugung eines Schemas wird initial ein Prozessgraph angelegt, welcher ausschließlich aus Start- und Endknoten besteht. Dieser kann im Folgenden vom Modellierer mithilfe der Änderungsoperationen so geändert werden, dass der gewünschte Prozess
abgebildet wird. Somit ist das Korrektheitskriterium für Schemata immer erfüllt.
2.1.3 Instanzen
Aufbauend auf einem Schema, kann der hierin modellierte Prozess mithilfe einer Instanz
ausgeführt werden. Diese repräsentiert die Ausführung des Prozesses und reichert das
Schema hierfür mit einer Instanzmarkierung an, welche jedem Knoten einen der aus
Abbildung 4 zu entnehmenden Zustände zuweist. Außerdem beinhaltet die Instanz auf
logischer Ebene die Werte der Datenelemente des Schemas (vergleiche Abbildung 5).
Zu Beginn besitzen alle Knoten den Zustand NOT_ACTIVATED. Als Erstes wird der
Start-Knoten gestartet und somit in den Zustand RUNNING versetzt. Nach der Aus-
6
2.1 Das ADEPT2-Metamodell
führung dessen geht der folgende Knoten in den Zustand ACTIVATED über, welcher
dem zugeordneten Benutzer dann in dessen Arbeitsliste angezeigt wird. Markiert der Benutzer den Prozessschritt zur Bearbeitung, bekommt der Knoten den Zustand SELECTED. Sobald dieser ausgeführt wird, folgt die Markierung des Knotens als RUNNING:
Die beim Knoten hinterlegte Aktivität wird gestartet. Der Prozessschritt kann während
seiner Ausführung pausiert werden, was durch einen Übergang in den Zustand SUSPENDED widergespiegelt wird. Hierbei wird die laufende Aktivität ebenfalls unterbrochen.
Die Durchführung des Prozessschritts kann dann später fortgesetzt werden. Schlägt die
Durchführung der Aktivität fehl, so wird der Knoten in den Zustand FAILED versetzt.
Bei einer erfolgreichen Abarbeitung der Aktivität bekommt der Knoten den Zustand
COMPLETED. Der Zustand SKIPPED markiert Knoten, welche auf dem Zweig eines
XOR-Split-Knotens liegen, der nicht zur Bearbeitung ausgewählt wurde.
2.1.4 Abstraktion
In dieser Arbeit wird von gewissen Aspekten des ADEPT2-Metamodells abstrahiert.
So werden Datenelemente inklusive ihrer Datenkanten nicht beachtet, da die Betrachtung dieser den Rahmen dieser Arbeit sprengen würde. Die gesonderte Behandlung von
Schleifen ist hier überflüssig, da der Schleifenkörper als Sequenz angesehen werden kann.
Hieraus folgt, dass auch Schleifenkanten nicht betrachtet werden. Auch Synchronisationskanten sind nicht relevant für diese Arbeit, somit werden nur Kontrollkanten betrachtet.
Diese werden im Folgenden einfach Kanten genannt.
Auch die Menge an Knotenzuständen lässt sich verringern. Im Rahmen dieser Arbeit sind
nur die Zustände NOT_ACTIVATED, RUNNING, COMPLETED und SKIPPED interessant. ACTIVATED ist überflüssig, da sich dieser Zustand für einen Knoten berechnen
lässt, in dem man überprüft, ob der Vorgängerknoten im Zustand COMPLETED ist.
SELECTED ist ebenfalls vernachlässigbar, da dies nur das „Reservieren“ eines Prozessschrittes für einen Benutzer des Systems ist und für die folgenden Betrachtungen keine
Auswirkungen hat. Diese würden sich allerdings auch leicht anpassen lassen. SUSPENDED und FAILED werden im Rahmen dieser Arbeit äquivalent zu RUNNING angesehen.
SUSPENDED ist ein temporärer Zustand eines Knotens, welcher RUNNING folgt und
Daten 1
"Inhalt"
Daten 2
42
RUNNING
COMPLETED
6
Start
2
3
4
5
10
8
7
SKIPPED
NOT_ACTIVATED
12
9
13
14
15
End
11
Abbildung 5: Eine ADEPT2-Instanz während Knoten 10 ausgeführt wird
7
2 Grundlagen
auch von RUNNING gefolgt wird. FAILED kann in diesem Zusammenhang ebenfalls wie
RUNNING angesehen werden, da Knoten den Zustand FAILED nur über RUNNING
erreichen können und im Rahmen dieser Arbeit keine Differenzierung zwischen FAILED
und RUNNING notwendig ist.
2.1.5 Zusammenfassung
In diesem Abschnitt wurde das ADEPT2-Metamodell vorgestellt. Hierzu wurden nach
einer grundlegenden Begriffsbildung Schemata und Instanzen betrachtet. Da nicht alle
Aspekte des ADEPT2-Metamodells in dieser Arbeit betrachtet werden müssen, wurde
abschließend eine Abstraktion dessen durchgeführt.
2.2 Repräsentation
Das vorgestellte ADEPT2-Metamodell modelliert Schemata als gerichtete, azyklische
Graphen. Diese lassen sich in verschiedenen Varianten im Speicher repräsentieren
[KRRD09, KR10], welche in diesem Abschnitt vorgestellt werden. Ebenso betrachtet
werden verschiedene Realisierungsmöglichkeiten von Instanzen und, aufbauend darauf,
instanzbasierte Änderungen. Diese sind, wie sich herausstellen wird, für die geforderte
Flexibilität des ADEPT2-Metamodells nötig.
2.2.1 Schemata
Schemata, welche aus Knoten und Kanten bestehen, lassen sich in verschiedenen Varianten im Speicher repräsentieren [KRRD09]. Da Knoten eine große Anzahl an Attributen
besitzen, empfiehlt sich die Realisierung durch eigenständige Knoten-Objekte. Diese besitzen jeweils eine ID, die ID des direkt übergeordneten Split-Knotens und des Zweigs
auf dem sich der Knoten befindet, sowie eine topologische ID [JMSW04]. Die IDs sind
aus Performanzgründen vorhanden und beschleunigen zum Beispiel Schleifeniterationen,
wenn das vollständige ADEPT2-Metamodell eingesetzt wird. Die topologische ID bildet
4
Start
ID
topologische ID
Split ID
Zweig ID
2
0
0
0
0
ID
topologische ID
Split ID
Zweig ID
2
1
0
0
3
ID
topologische ID
Split ID
Zweig ID
Join ID
ID
topologische ID
Split ID
Zweig ID
3
2
0
0
6
4
3
3
0
5
ID
topologische ID
Split ID
Zweig ID
6
ID
topologische ID
Split ID
Zweig ID
zugeh. Split ID
6
5
0
0
3
End
ID
topologische ID
Split ID
Zweig ID
5
4
3
1
Abbildung 6: Die Knoten eines Schemas und deren Knotenattribute
8
1
6
0
0
2.2 Repräsentation
eine topologische Sortierung [Sch01a] der Knoten innerhalb des Schemas ab, welche aufgrund der Zyklenfreiheit des ADEPT2-Metamodells verfügbar ist. Mithilfe der Zweig-ID
lassen sich Nachfolgerknoten von Split-Knoten zu den Zweigen des Split-Knotens zuordnen. Split-Knoten besitzen zusätzlich einen Verweis auf den zugehörigen Join-Knoten
und Join-Knoten einen Verweis auf den zugehörigen Split-Knoten [JMSW04]. Dies wird
ebenfalls in Form von Knoten-IDs repräsentiert. Abbildung 6 illustriert das.
Für die Realisierung der Kanten zwischen den Knoten-Objekten ergibt sich eine Adjazenzliste [CLRS09] als optimale Alternative [KRRD09]. Dies bedeutet, dass das Schema
eine Menge von Kanten verwaltet, welche aus einem Paar aus Quell-Knoten-ID und ZielKnoten-ID bestehen. Zusätzlich können die Kanten bei ADEPT2 attributiert sein. Auch
dies ist mithilfe einer Adjazenzliste leicht umsetzbar, da hier ein weiterer Wert zu jedem
Quell-/Ziel-Knoten-ID-Paar hinzugefügt wird, mithilfe dessen die Attribute der Kante
verwaltet werden.
2.2.2 Instanzen
Da eine Instanz die Ausführung eines Schemas abbildet, muss diese Beziehung in der
Repräsentation von Instanzen modelliert werden. Hierfür bieten sich verschiedene Möglichkeiten an [KRRD09]. Als beste Variante hat sich die Schemareferenz herausgestellt,
welche eine Referenz auf die Schemadaten besitzt. Im Gegensatz zur Schemakopie werden
Schema
Start
2
3
Instanz mit Schemakopie
E
Start
2
3
4
End
Knoten Zustand
Start
2
3
4
End
Instanz mit Schemareferenz
RUNNING
COMPLETED
SKIPPED
NOT_ACTIVATED
Knoten Zustand
Start
2
3
4
End
Abbildung 7: Schemakopie vs. Schemareferenz
bei dieser Variante die Graphstrukturen nicht von der Instanz redundant gehalten (siehe
Abbildung 7). Im Folgenden werden Instanzen zur besseren Lesbarkeit allerdings aus der
logischen Sicht inklusive der Graphstrukturen wie in Abbildung 5 dargestellt.
Eine Instanz reichert ein Schema mit einer Zustandsmarkierung an, welche für jeden
Knoten einen Knotenzustand liefern können muss. [KRRD09] nennt hier verschiedene
Möglichkeiten, wobei in dieser Arbeit die implizite und die explizite Markierung sowie die Clusterung von Interesse sind. Die implizite Markierung speichert nur Zustände für die Knoten, welche im Zustand RUNNING oder SKIPPED sind. Aufgrund der
9
2 Grundlagen
Schema
Start
2
3
Instanz mit expliziter Knotenmarkierung
4
End
Knoten Zustand
Start
2
3
4
End
Instanz mit impliziter Knotenmarkierung
RUNNING
COMPLETED
SKIPPED
NOT_ACTIVATED
Knoten !" stan RUNNING:
Knoten im Zustand SKIPPED:
{3}
{}
Abbildung 8: Instanz mit impliziter und expliziter Markierung
Vorgänger- und Nachfolgerbeziehungen zwischen den Knoten des Schemas lassen sich die
übrigen Zustände zur Laufzeit errechnen. So befinden sich alle (transitiven) Vorgänger
eines RUNNING-Knotens im Zustand COMPLETED und die (transitiven) Nachfolger
im Zustand NOT_ACTIVATED. Die explizite Markierung speichert hingegen für jeden
Knoten den zugehörigen Zustand. Dies bedeutet mehr Speicheraufwand, aber die Ersparnis von Berechnungen der Knotenzustände NOT_ACTIVATED und COMPLETED zur
Laufzeit. In Abbildung 8 sind implizite und explizite Markierung illustriert.
Die Clusterung von Instanzen [KRRD09] geht hierbei einen anderen Weg: Die Instanzen
werden nicht direkt markiert, sondern in einem Clusterbaum gehalten. Dieser besteht
aus Clustern, welche jeweils genau einen Zustand der Instanz abbilden, wie Abbildung 9
zeigt. Hierbei existiert für jeden Zustand der Instanz genau ein Cluster. Ein Cluster
verweist auf eine Menge an Instanzen, welche in diesem Zustand sind. Da alle Zustände,
die eine Instanz annehmen kann durch das Schema definiert sind, kann der Clusterbaum
zur Modellierzeit des Schemas vorberechnet werden.
Eine weitere Möglichkeit, den Zustand eines Knotens zu bestimmen, ist die Ausführungshistorie zu untersuchen. Diese beinhaltet alle Markierungsänderungen der Instanz
in einer zeitlich sortierten Liste. Die aktuelle Knotenmarkierung ist hierbei die zeitlich
zuletzt eingetragene Markierungsänderung des Knotens.
2.2.3 Instanzbasierte Änderungen
Das ADEPT2-Metamodell unterstützt das Ändern des Prozessgraphen einer bestimmten
Instanz. Dieser wird durch das Schema abgebildet, auf das die Instanz eine Referenz
hält. Da allerdings mehrere Instanzen auf diesem Schema basieren, welche nicht geändert
werden sollen, kann nicht das zugrunde liegende Schema geändert werden.
Die Lösung bietet die Deltaschicht, welche Änderungen gegenüber einem Schema abbildet
[LRR04, JMSW04, Jur06]. Bei einer instanzbasierten Änderung wird eine Deltaschicht
erzeugt, welche von der Instanz referenziert wird.
Es existieren verschiedene Ansätze, die Deltaschicht im Speicher zu repräsentieren
[KR10]. Auf der einen Seite ist dabei die Art des Zugriffs der Instanz auf Schemadaten
10
2.2 Repräsentation
Schema
3
Start
4
7
2
5
End
8
6
Clusterbaum
S
2
3
4
S
2
3
4
5
5
6
7
8
E
S
2
3
4
5
6
S
2
3
4
5
6
7
8
E
S
2
3
4
5
6
7
8
E
S
2
3
4
5
6
7
8
E
7
8
S
6
7
E
2
8
S
3
4
5
6
7
E
2
8
S
3
4
E
2
3
S
2
3
4
5
6
7
8
E
S
2
3
4
5
6
7
8
E
S
2
3
4
5
6
7
8
E
4
5
6
7
8
E
S
2
3
4
5
5
6
7
8
E
6
7
8
E
RUNNING
COMPLETED
Abbildung 9: Clusterung
zu nennen. Hierbei hat sich die Kapselung der Schemadaten als vorteilhaft herausgestellt
[KR10]. Dabei bildet die Deltaschicht die gleichen Zugriffsmöglichkeiten wie ein vollwerInstanz 1
Schema
3
Instanz 2
Instanz 3
Start
2
Knoten Zustand
E
Start
2
3
4
5
End
Instanz 4
Knoten Zustand
Deltaschicht
RUNNING
COMPLETED
SKIPPED
NOT_ACTIVATED
Start
2
3
4
5
6
End
Abbildung 10: Instanzbasierte Änderungen mit Deltaschicht
11
2 Grundlagen
tiges Schema. Wird eine Anfrage an die Deltaschicht gestellt, so wird überprüft, ob diese
einen geänderten Bereich des Schemas betrifft oder nicht. Dementsprechend werden die
überlagerten Daten zurückgeliefert, welche in der Deltaschicht gehalten werden, oder die
Daten vom zugrunde liegenden Schema abgerufen. Hierzu benötigt die Deltaschicht eine
Referenz auf dieses. Durch Einsatz der Kapselung stellt es sich für eine Instanz als transparent dar, ob diese auf einem vollwertigen Schema oder auf einer Deltaschicht beruht.
Auf der anderen Seite ist die Repräsentation der Änderungen von Bedeutung. Diese
können als operationale oder strukturelle Deltas dargestellt werden. Operationale Deltas speichern die bei der Änderung durchgeführten Änderungsoperationen, strukturelle
logische Sicht
physische Sicht
(strukturell)
Deltaschicht
Anfrage
Start
2
3
4
hinzugefügte Knoten
gelöschte Knoten
verschobene Knoten
4
keine
keine
End
hinzugefügte Kanten
2
4
4
3
gelöschte Kanten
2
3
Schema
logische Sicht
Anfrage
wird potentiell
weitergeleitet
Start
2
3
End
Abbildung 11: Kapselung bei Einsatz einer strukturellen Deltaschicht
Instanzdeltas halten hingegen realisierte Graphstrukturen der geänderten Bereiche nach
der Anwendung der Änderungsoperationen. Hierbei stellen sich strukturelle Instanzdeltas als besser geeignet heraus [KR10]. Diese werden durch Mengen repräsentiert, welche
hinzugefügte, gelöschte und verschobene Knoten und hinzugefügte und gelöschte Kanten beinhalten [Jur06, KR10]. Dabei können die gelöschten und verschobenen Knoten
ausschließlich mit ihrer ID vorgehalten, während von hinzugefügten Knoten alle Daten
(vergleiche Abbildung 6) benötigt werden [Jur06]. In Abbildung 11 ist die Überlagerung
der Daten bei einer Anfrage (zum Beispiel von einer Instanz) mithilfe einer gekapsel-
12
2.3 ADEPT2-Architektur
ten, strukturell aufgebauten Deltaschicht dargestellt. Hierbei wird Knoten 4 zwischen
die Knoten 2 und 3 hinzugefügt.
2.2.4 Zusammenfassung
Es wurden die Repräsentationen des ADEPT2-Metamodells vorgestellt. Ein Schema besteht aus mehreren Knotenobjekten und einer Adjazenzliste von Kanten. Für Instanzen,
welche mithilfe von Schemareferenzen dargestellt werden, bieten sich mehrere Alternativen zur Repräsentation der Knotenzustände: Eine implizite Markierung, welche nur
bestimmte Knotenzustände speichert und die restlichen zur Laufzeit errechnet, eine explizite Markierung, die alle Knotenzustände vorhält und als orthogonales Konzept die
Clusterung von Instanzen. Um instanzbasierte Änderungen zu unterstützen, wird eine
Deltaschicht benutzt, welche als gekapseltes Schema die Daten intern als realisierte Graphstrukturen vorhält.
2.3 ADEPT2-Architektur
Das ADEPT2-System implementiert das ADEPT2-Metamodell. Hierbei kommt eine
schichtartige Architektur zum Einsatz, wobei jede Schicht aus verschiedenen Komponenten aufgebaut ist [RDR+ 09]. Jede Schicht versteckt so viel Komplexität wie möglich,
ohne dabei die Möglichkeit zur flexiblen Konfiguration zu verlieren. Diese Arbeit bewegt
sich in dem in Abbildung 12 rot eingefärbten Bereich.
3
Start
2
Start
2
5
End
5
End
ControlCenter
4
3
3
Start
5
End
Start
4
2
5
End
4
OrgModelEditor
ProcessEditor
WorklistManager
3
2
4
Monitor
User interaction layer
Simulation/Test
3
Start
2
5
End
4
ChangeOperations
3
Start
2
ExecutionManager
(
3
5
End
Start
2
5
RuntimeManager
End
Start
4
ActivityRepository
4
ProcessRepository
LogManager
Persistence (DBMS)
ProcessManager
DataManager
Configuration &
Regsitry
Framework
3
2
5
4
End
)
(
Start
OrgModelManager
3
2
5
4
End
Execution layer
)
ResourceManager
Basic services layer
Low-level services layer
Communication
3
Start
2
5
4
End
Buildtime
Runtime
Abbildung 12: Die ADEPT2-Architektur [RDR+ 09]
Die unterste Schicht (Low-level services layer ) stellt die persistente Datenspeicherung
zur Verfügung. Dadurch sind übergeordnete Schichten unabhängig von der eingesetzten
Variante zur Persistierung. Hierbei sind mehrere Möglichkeiten denkbar, wobei relationale Datenbanksysteme oder eine Speicherung im Dateisystem möglich sind. Da diese
Arbeit Schemata und Instanzen persistiert, ist ein Teil der Komponente zur Persistenz
rot eingefärbt. Der Low-level services layer bietet ebenfalls die Möglichkeit, verschiedene
13
2 Grundlagen
Nachrichten aus dem System zu protokollieren (LogManager ) und bietet den Komponenten eine Abstraktion zur Kommunikation untereinander und damit die Möglichkeit zur
Verteilung der Komponenten auf unterschiedlichen Rechnern. Dies wird durch die Kommunikationskomponente bereitgestellt und läuft für die übrigen Komponenten transparent ab. Die unterste Schicht stellt auch Zugriff auf die Konfiguration des Systems bereit.
Der Basic services layer stellt grundlegende Dienste zur Verfügung. Das ActivityRepository verwaltet die Aktivitäten, welche auf Knoten von Schemata platziert sind. Aufgrund
der Flexibilität des ADEPT2-Metamodells und speziell der Unterstützung der Schemaevolution, können Schemata sich mit der Zeit entwickeln. Dies erfordert eine Versionierung
der Schemata, welche das ProcessRepository übernimmt. Die einzelnen Schemata werden
ohne die Versionierungsinformationen im ProcessManager zusammen mit den Instanzen
verwaltet. Dies beinhaltet auch die Wahl der Repräsentation der Prozessdaten. Diese
Komponente ist auch für die Migration der Instanzen bei instanzbasierten Änderungen
und der Schemaevolution zuständig. Diese Arbeit umfasst den Aufgabenbereich des ProcessManager s komplett. Der DataManager versorgt andere Komponenten mit Daten,
welche in Prozessen angefallen sind. Hierzu speichert und verwaltet er die Werte der einzelnen Datenelemente aller Instanzen. Im OrgModelManager ist ein Organisationsmodell
der Benutzer des Systems abgebildet, mithilfe dessen die Bearbeiterzuordnungen der Prozessschritte ausgewertet werden. Der ResourceManager verwaltet zusätzliche Ressourcen,
welche in einem Unternehmen vorhanden sind (zum Beispiel Räume oder Maschinen).
Der Execution layer sorgt für die Ausführung und Änderung der Prozesse. Es wird zurückgegriffen auf die im ProcessManager gespeicherten Prozesse, welche mithilfe des ExecutionManager s und des RuntimeManager s ausgeführt und deren Aktivitäten gestartet
werden. Hierbei umfasst diese Arbeit Teile des ExecutionManager s, da Funktionen auf
Schemata und Instanzen durchgeführt werden sollen. Die Komponente ChangeOperations beinhaltet die Änderungsoperationen, mithilfe denen das Anlegen oder Ändern eines
Schemas möglich ist.
Die oberste Schicht, der User interaction layer sorgt schließlich mithilfe von EndbenutzerSoftware dafür, dass das System korrekt angesteuert und benutzt wird, es bildet die
Schnittstelle zum Benutzer. Hierzu gehört der ProcessEditor, welcher Schemata anlegen
und ändern kann. Der OrgModelEditor bildet die grafische Oberfläche zum Steuern des
OrgModelManager s. Mithilfe des Monitor s können Instanzen untersucht werden und es
existiert die Möglichkeit zur Simulation und zum Test von Prozessen. Hinzu kommt der
WorklistManager welcher die Arbeitslisten der Benutzer verwaltet. Dieser besitzt keine
eigene grafische Oberfläche, wird allerdings von den anderen Komponenten dieser Schicht
benutzt.
14
2.4 Anwendungsszenarien
2.4 Anwendungsszenarien
In diesem Abschnitt werden Funktionen auf Basis von Schemata und Instanzen vorgestellt. Dies sind Funktionen, welche intern von PMS durchgeführt werden und später in
dieser Arbeit zu Messungen herangezogen werden.
In dieser Arbeit werden die Funktionen Weiterschalten einer Instanz, Schemaevolution
und Anfragen auf Kollektionen betrachtet.
2.4.1 Weiterschalten
Das Weiterschalten [Rei00] ist die Änderung der Markierung einer Instanz gemäß den
Regeln des Metamodells. Wird die Bearbeitung einer Aktivität abgeschlossen, muss der
nachfolgende Prozessschritt dem zuständigen Benutzer oder die nachfolgenden Prozessschritte den zuständigen Benutzern vorgelegt werden. Diese zu aktivierenden Prozessschritte müssen zuerst identifiziert und danach die Knotenzustände angepasst werden.
Um Weiterschalten zu können, werden zwei Informationen benötigt: Welcher Knoten
weitergeschaltet und, im Fall einer Entweder-Oder-Verzweigung, welcher Zweig aktiviert
werden soll. Der weiterzuschaltende Knoten muss dabei im Zustand RUNNING sein. Ihm
wird beim Weiterschalten der Zustand COMPLETED zugewiesen.
Ist der weiterzuschaltende Knoten ein normaler Knoten, ein AND-Split-Knoten oder ein
Join-Knoten, so werden alle direkten Nachfolgerknoten ermittelt und diesen der Zustand
RUNNING zugewiesen, wie die ersten vier Beispiele in Abbildung 13 veranschaulichen.
Ist dieser hingegen ein XOR-Split-Knoten, so muss der auf diesem Zweig folgende Knoten
gefunden und auf den Zustand RUNNING gesetzt werden. Zusätzlich müssen alle anderen
Zweige durchlaufen und die Zustände dieser Knoten auf SKIPPED gesetzt werden. Dies
ist im letzten Beispiel der Abbildung 13 dargestellt.
Ist der Knoten, der durch das Weiterschalten auf RUNNING gesetzt werden soll, ein
AND-Join-Knoten, so müssen die anderen Zweige untersucht werden, bevor dies geschieht. Ein AND-Join-Knoten darf erst aktiviert werden, wenn alle Zweige abgearbeitet,
also die jeweils topologisch letzten Knoten auf den Zweigen den Zustand COMPLETED
haben. Sollte dies nicht zutreffen, darf der AND-Join-Knoten nicht auf RUNNING gesetzt
werden, sondern muss im Zustand NOT_ACTIVATED verbleiben. Dies ist im dritten
Beispiel in Abbildung 13 zu sehen: Nachdem Knoten 3 weitergeschaltet wurde, darf Knoten 5 nicht auf RUNNING gesetzt werden, da sich Knoten 4 noch in der Ausführung
befindet.
2.4.2 Schemaevolution
Durch gesetzliche Änderungen oder aufgrund von Anpassungen des Betriebsablaufs, kann
es nötig werden, die im PMS vorhandenen Prozesse zu ändern. Hierbei reicht eine Än-
15
2 Grundlagen
...
3
schalten
...
4
...
Knoten: 3
3
3
...
3
2
...
Weiterschalten
Knoten: 2
...
2
4
2
...
Weiterschalten
Knoten: 3
...
2
...
4
3
3
2
...
Weiterschalten
...
Knoten: 4
2
10
10
12
9
11
...
4
4
...
...
3
4
...
4
3
...
...
4
...
Weiterschalten
Knoten: 9
Zweig: 0
...
12
9
...
11
RUNNING
COMPLETED
SKIPPED
NOT_ACTIVATED
Abbildung 13: Die verschiedenen Fälle des Weiterschaltens
derung des entsprechenden Schemas nicht aus. Es ist davon auszugehen, dass Instanzen
auf dem zu ändernden Schema beruhen, welche sich in der Ausführung befinden. Auf der
einen Seite wäre es möglich, die vorhandenen Instanzen weiterhin auf Basis des „alten“
Schemas durchzuführen und neue Instanzen mit dem neuen Schema zu starten. Dies ist
allerdings nicht immer durchführbar. So besteht die Möglichkeit, dass der Gesetzgeber eine sofortige Umstellung auch der laufenden Prozesse fordert oder sonstige Umstände eine
Migration der Instanzen notwendig machen. Dies ist die Grundlage der Schemaevolution
[Rin04].
Der Schemaevolution stehen neben dem „alten“ Schema und den Instanzen, welche auf
diesem basieren, die durchgeführten Änderungen in Form einer Deltaschicht zur Verfügung [Jur06] (vergleiche Abschnitt 2.2.3). Ziel der Schemaevolution ist die Untersuchung
16
2.4 Anwendungsszenarien
der Instanzen auf ihre Verträglichkeit mit dem neuen, geänderten Schema. Diese Verträglichkeit gliedert sich in eine zustandsbasierte Verträglichkeit und eine strukturelle
Verträglichkeit. Abbildung 14 zeigt eine Schemaevolution und illustriert Verträglichkeitsprobleme.
Schemaevolution
Schema
Start
Start
E
Instanz
migrierbar
Start
E
Start
Instanz mit Änderung
Instanz
Start
Start
E
E
Nicht migrierbar, da die Ändeungen "in der Vergangenheit" sind.
Würde migriert werden, hätten Knoten 6 und 7 ausgeführt werden müssen.
E
E
Nicht migrierbar, da die Blockstrukturen unvereinbar sind.
RUNNING
COMPLETED
SKIPPED
NOT_ACTIVATED
Abbildung 14: Beispiel einer Schemaevolution
Die zustandsbasierte Verträglichkeit überprüft, ob geänderte Stellen bei einer Instanz
bereits abgearbeitet wurden, die Änderungen somit sozusagen „in der Vergangenheit“ der
Instanz liegen. Ist dies der Fall, so ist die Instanz nicht verträglich. Formal wird definiert,
dass eine Instanz zustandsbasiert verträglich ist, wenn ihre Ausführungshistorie auch auf
dem neuen Schema nachvollzogen werden kann [Rin04].
Die Überprüfung der strukturellen Verträglichkeit ist dafür zuständig, Prozesse zu identifizieren, welche dem Korrektheitskriterium nicht entsprechen (vergleiche Abschnitt 2.1.2):
Ist eine zu untersuchende Instanz instanzbasiert geändert, so müssen die instanzbasierten
Änderungen mit den Schemaänderungen vereint werden. Hierbei ist es möglich, dass das
resultierende Schema nicht länger korrekt ist, da z.B. Verklemmungen auftreten können
oder die Blockstruktur nicht länger korrekt ist [Rin04, Jur06]. Ist dies der Fall, ist eine
solche Instanz nicht verträglich. Für Instanzen, welche nicht instanzbasiert geändert wurden, ist die strukturelle Verträglichkeit immer gegeben, da hier keine zwei Änderungsmengen vereint werden müssen und somit die Korrektheit durch die Änderungsoperationen
sichergestellt ist.
17
2 Grundlagen
Migrierbare Instanzen müssen sowohl zustandsbasiert als auch strukturell verträglich sein
und können auf Basis des neuen Schemas fortgesetzt werden. Hierbei sind unter Umständen noch strukturelle Anpassungen oder Modifikationen der Zustandsmenge notwendig
[Rin04, Jur06]. Alle Instanzen, welche nicht zustandsbasiert oder strukturell verträglich
sind, sind nicht kompatibel mit dem neuen Schema und sind deshalb nicht migrierbar.
2.4.3 Anfragen auf Kollektionen
Wie einleitend erläutert, werden mithilfe von PMS Geschäftsprozesse ausgeführt. Das
bedeutet, dass diese in Unternehmen eingesetzt werden, welche eine Organisation ihrer
Mitarbeiter in Abteilungen haben. Eine Abteilung ist hierbei meist an der Durchführung
verschiedener Prozesse beteiligt. In Abbildung 15 sind exemplarisch die Abteilungen Beschaffung, Produktion und Verkauf dargestellt. Mitarbeiter jeder dieser Abteilungen sind
an den Prozessen A, B und C beteiligt.
Mitarbeiter
Prozess C
Verkauf
Mitarbeiter
Prozess B
Produktion
Mitarbeiter
Prozess A
Beschaffung
Abbildung 15: Exemplarische Organisation eines Unternehmens
Um die Auslastung einer Abteilung einschätzen zu können, muss das PMS Instanzen
abfragen können, welche auf einem bestimmten Schema beruhen und sich ein bestimmter
Knoten hierin in einem bestimmten Zustand befindet. Diese Anfrage kann dann für die
verschiedenen Prozesse und für die verschiedenen Knoten durchgeführt werden, welche
von der Abteilung bearbeitet werden. Dadurch kann die Menge der Instanzen ermittelt
werden, welche aktuell in der Abteilung abgearbeitet werden. Hiermit kann die Auslastung der Abteilung abgeschätzt werden.
Ebenfalls interessant ist die Auslastung der einzelnen „Prozesslinien“. Hierfür ist eine
Abfrage im PMS nötig, welche alle Instanzen zu einem gegebenen Schema abrufen kann.
Im Rahmen dieser Arbeit werden diese beiden Anfragen in ihrer Laufzeit genauer untersucht. Diese werden hierbei beispielhaft für andere Anfragen auf Kollektionen herangezogen, welche auf der Datenbasis eines PMS definiert werden können.
18
2.5 Allgemeine Speicherbetrachtungen
2.4.4 Zusammenfassung
In diesem Abschnitt wurden Anwendungsszenarien von PMS und von PMS’ zu unterstützende Anfragen untersucht. Zuerst wurde das häufig benötigte Weiterschalten betrachtet.
Anschließend wurde die Schemaevolution vorgestellt, gefolgt von zusätzlichen Anfragen
auf Kollektionen.
2.5 Allgemeine Speicherbetrachtungen
In dieser Arbeit wird neben der Laufzeit von PMS-typischen Anfragen auch der Speicherverbrauch von verschiedenen Repräsentationen betrachtet und verglichen. In diesem
Abschnitt wird zuerst die Speicherhierarchie beschrieben und anschließend werden Vereinbarungen für diese Arbeit gültige Vereinbarungen zum Speicherverbrauch atomarer
Größen vorgestellt.
Der Speicher eines Rechners ist in eine Speicherhierarchie [Cle06] aufgeteilt, welche in
Abbildung 16 dargestellt ist.
Prozessorregister
Primärspeicher
Hauptspeicher
Einlagern
Auslagern
Prozessorcache
Massenspeicher
Sekundärspeicher
Wechseldatenträger
Abbildung 16: Speicherhierarchie
Hierbei bilden die Register des Prozessors die Spitze der Hierarchie, da sie zwar am
schnellsten zugreifbar, aber aufgrund ihrer Kosten nur in geringem Umfang in einem
Rechner vertreten sind. Es folgen die Prozessorcaches, welche Daten, die vom Prozessor
19
2 Grundlagen
aus dem Hauptspeicher geladen werden, zwischenspeichern. Der Hauptspeicher bildet
die dritte Schicht. Auf Daten, welche in einer dieser Speicherarten gehalten werden, kann
gegenüber den restlichen Speicherarten relativ schnell zugegriffen werden. Sie sind allerdings alle flüchtige Speicher, das bedeutet, dass die Daten nach einem Neustart des
Rechners nicht mehr vorhanden sind. Die darunter liegenden Schichten von Massenspeicher und Wechseldatenträgern halten die Daten dagegen persistent, wobei allerdings eine
wesentlich höhere Zugriffszeit benötigt wird. Wie in Abbildung 16 dargestellt, werden
die Schichten der Prozessorregister, -caches und des Hauptspeichers Primärspeicher genannt, da diese Speicher vorrangig während der Laufzeit des Rechners genutzt werden.
Die beiden unteren Schichten werden Sekundärspeicher genannt, da diese während der
Laufzeit nur zur Persistierung von Daten benutzt werden. Der Transfer von Daten aus
dem Primärspeicher in den Sekundärspeicher nennt man Auslagern, die Gegenrichtung
Einlagern.
Betrachtet man die Daten, welche in diesen Speichern gespeichert werden, so fällt auf,
dass im Rahmen dieser Arbeit Vereinbarungen für atomar anzusehende Speicherstrukturen getroffen werden müssen. Die Größen dieser Daten sind grundlegend abhängig von
der Architektur des eingesetzten Rechners. Um im Rahmen dieser Arbeit keine unveränderlichen, absoluten Werte für diese Größen angeben zu müssen, werden die Variablen
sint , sObjektheader und sRef erenz eingeführt. sint stellt dabei den Speicherverbrauch einer
Variablen dar, welche einen ganzzahligen Wert (Integer ) hält. Bei objektorientierten Programmierumgebungen haben Objekte im Allgemeinen einen Objektheader im Speicher.
Dies ist zusätzlicher Speicherplatz, welcher zur Organisation des Objekts benötigt wird.
Hierin gespeichert ist zum Beispiel die Klasse des Objekts. Die Größe dieses zusätzlichen
Speicherverbrauchs hält die Variable sObjektheader . sRef erenz speichert die Größe, welche
eine Referenz auf ein Objekt im Speicher einnimmt. Hierbei ist grundlegend zwischen
den heute üblichen 32-Bit- und 64-Bit-Rechnern zu unterscheiden. Bei ersteren haben
Referenzen 32 Bit, also 4 Byte, bei letzteren 8 Byte. Um nicht bei jedem Aufkommen
eine Fallunterscheidung machen zu müssen und um zukünftige Entwicklungen auf diesem
Gebiet in diese Arbeit einzugliedern wird die Variable sRef erenz hierfür verwendet.
2.6 Zusammenfassung
Nach der Betrachtung des ADEPT2-Metamodells wurden Repräsentationen der dort definierten Strukturen mithilfe von aktuellen Forschungsergebnissen vorgestellt. Daraufhin
wurde die Architektur des ADEPT2-Systems betrachtet und die Konzepte dieser Arbeit
darin eingegliedert. Es wurden die Funktionen Weiterschalten, Schemaevolution und Anfragen auf Kollektionen vorgestellt. Abgeschlossen wurde dieses Kapitel durch allgemeine
Speicherbetrachtungen, welche dieser Arbeit zugrunde liegen.
20
3 Konzepte
Aufbauend auf den in Kapitel 2 vorgestellten Grundlagen zeigt dieses Kapitel weiterführende Konzepte der Repräsentation und Verwaltung von Prozessdaten auf. Hierbei wird
zuerst eine geeignete Partitionierung von Schemata erarbeitet. Danach wird diese in die
bereits vorgestellte Repräsentation von Schemata eingegliedert. Wie zuvor angemerkt,
ist die Durchführung einer Schemaevolution sehr zeitintensiv. Dies kann durch die in
diesem Kapitel vorgestellte grobe Clusterung vermindert werden. Abschließend werden
instanzbasierte Änderungen in Bezug auf die in diesem Kapitel vorgestellten Konzepte
untersucht.
3.1 Ansätze zur Partitionierung von Schemata
Die in Abschnitt 2.5 vorgestellte Speicherhierarchie teilt den Speicher eines Rechners
in Primär- und Sekundärspeicher. Schemadaten müssen hierbei in beiden repräsentiert
werden: Um Funktionen auf den Daten auszuführen werden diese im Primärspeicher benötigt, es ist aber gleichzeitig eine Persistierung der Daten notwendig. Der Primärspeicher
ist hierbei in geringeren Größen vorhanden als der Sekundärspeicher. Aufgrund dessen
ist es anzustreben, nur benötigte Teile im Primärspeicher zu halten. Es gilt somit eine
geeignete Aufteilung der Daten zu finden. Diese ist einerseits für die Menge der Schemata möglich. Wird ein Schema aktuell nicht von Instanzen im Primärspeicher referenziert
und auch nicht anderweitig für Funktionen des Systems benötigt, ist es nicht sinnvoll,
dieses im Primärspeicher zu halten. Auf der anderen Seite lässt sich eine Aufteilung der
Daten der aktuell benötigten Schemata treffen. Hierbei können Teile abgespaltet werden,
da nicht immer alle Informationen eines Schemas benötigt werden.
Dies kann mit einer geeigneten Partitionierung erreicht werden. So lassen sich die Knotenund Kantenmengen horizontal partitionieren, in dem man nur die aktuell benötigten
Knoten und Kanten einlagert. Eine andere Möglichkeit besteht darin, diese Mengen vollständig im Primärspeicher zu halten, allerdings Daten von den Knoten abzuspalten und
diese bei Bedarf nachzuladen. Dies ist die sogenannte vertikale Partitionierung.
Bei der vertikalen Partitionierung, wie in Abbildung 17 dargestellt, werden alle Knoten
und Kanten im Primärspeicher gehalten. Da Knoten einige Attribute besitzen (in der
Abbildung sind dies die ID, topologische ID, Split ID und Zweig ID) können diese vertikal partitioniert werden, also die genannten Attribute abgespaltet werden. Diese sind
nicht initial im Primärspeicher vorhanden, sondern werden bei Bedarf aus dem Sekundärspeicher nachgeladen. Dies ist sinnvoll, da diese Daten nicht zwangsweise von allen
Knoten des Schemas benötigt werden.
Es muss allerdings beachtet werden, dass zusätzlicher Speicher benötigt wird, um die
Möglichkeit zur Abspaltung zu schaffen. So benötigt man zum Beispiel bei einer objektorientierten Implementierung der Entitäten zusätzlichen Speicherplatz für eine Referenz
auf ein Objekt, welches die eingelagerten Daten hält. Ist dieses Objekt dann instantiiert,
21
3 Konzepte
3
Start
4
2
7
9
8
End
6
5
9
ID
topologische ID
Split ID
Zweig ID
9
8
0
0
Abbildung 17: Vertikale Partitionierung eines Schemas
sprich die Daten eingelagert, so fällt noch einmal zusätzlicher Speicher an, da ein Objekt
einen Objektheader besitzt, in dem unter anderem der Typ des Objekts gespeichert ist.
Dieser Aufwand lohnt sich erst bei einer größeren Menge an abzuspaltenden Daten, welche hier allerdings nicht gegeben ist. Die einzelnen Attribute eines Knoten beschränken
sich auf vier bis fünf ganzzahlige Werte, wie in Abschnitt 2.2.1 angegeben. Weiterhin
ist an eine Verschlechterung der Zugriffszeit auf Daten zu denken, da vor jedem Zugriff
überprüft werden muss, ob diese bereits eingelagert sind. Da der hierfür nötige Zeitaufwand allerdings konstant ist, kann er vernachlässigt werden. Sollten die Daten noch nicht
eingelagert sein, so muss dies vor dem Zugriff geschehen. Vergleicht man den Gesamtaufwand für das Einlagern aller Daten eines vertikal partitionierten Schemas mit dem eines
nicht partitionierten Schemas, stellt sich heraus, dass diese äquivalent sind. Beim nicht
partitionierten Schema fällt der gesamte Aufwand beim initialen Einlagern an, während
er beim vertikal partitionierten zeitlich verteilt auftritt.
3
Start
4
2
7
9
End
6
5
Partition 1
8
Partition 2
Partition 3
Abbildung 18: Horizontale Partitionierung eines Schemas
Im Gegensatz zur vertikalen Partitionierung werden bei der horizontalen Partitionierung
keine Daten von den Knoten und Kanten abgespalten. Es werden verschiedene Knoten
und Kanten zu Partitionen zusammengefasst, welche dann ein- und ausgelagert werden,
22
3.1 Ansätze zur Partitionierung von Schemata
wie in Abbildung 18 illustriert. Dies hat den Vorteil, dass gegenüber einer Realisierung
ohne Partitionierung kein zusätzlicher Speicherplatz benötigt wird, sofern die Partitionszugehörigkeit der Kanten und Knoten ohne zusätzliche Strukturen verfügbar ist. Ebenso
gilt, dass die Umsetzung der horizontalen Partitionierung im Allgemeinen leichter ist als
die der vertikalen: Es muss sichergestellt werden, dass die Partition (als Ganzes) eingelagert ist und nicht, wie bei der vertikalen, vor jedem Zugriff auf Daten des Knoten
geprüft werden muss, ob diese eingelagert sind. Die horizontale Partitionierung benötigt
allerdings zusätzliche Funktionen des Sekundärspeichers: Sind bei der vertikalen Partitionierung alle Knoten und Kanten im Primärspeicher vorhanden und die Menge dieser
somit bekannt, benötigt man bei einer horizontalen Partitionierung eine Abfrage an den
Sekundärspeicher, welche Knoten und Kanten verfügbar sind. Dies stellt aber keine Hürde dar, da die als Sekundärspeicher eingesetzten Systeme wie zum Beispiel relationale
Datenbanksysteme diese Aufgabe ohne zusätzlichen Aufwand erfüllen können.
Im Rahmen dieser Arbeit wird nur die horizontale Partitionierung weiter betrachtet,
da sich die vertikale aufgrund der geringen Datenmengen nicht eignet. Hierbei gibt es
verschiedene Ansätze, welche im Folgenden näher untersucht werden. Zuerst wird die horizontale Partitionierung anhand der Bearbeiterzuordnungen der Aktivitäten der Knoten
untersucht. Anschließend betrachten wir eine horizontale Partitionierung, welche die hierarchische Blockstruktur des ADEPT2-Metamodells ausnutzt.
3.1.1 Partitionierung anhand Bearbeiterzuordnungen
Ein Ansatz, horizontal zu partitionieren, besteht im Auswerten der Bearbeiterzuordnungen der Aktivitäten von Knoten. Dies basiert auf der Untersuchung der Verteilung
ganzer Prozesse auf verschiedenen Workflow-Servern [Bau00]: Sind mehrere WorkflowServer vorhanden, so kann ein ablaufender Prozess auf demjenigen ausgeführt werden,
welcher dem Bearbeiter am nächsten ist. Hierdurch werden unter anderem die Kosten
von anfallendem Datentransfer minimiert.
Überträgt man diese Idee auf die Partitionierung von Schemata, so ist es sinnvoll, direkt aneinander gereihte Knoten als Partition zusammenzufassen, welche vom selben
Mitarbeiter bearbeitet werden. Dies geschieht unter der Annahme, dass der Mitarbeiter
mehrere Prozessschritte der selben Instanz direkt hintereinander bearbeitet, sofern er für
deren Bearbeitung zuständig ist.
Die Umsetzung gestaltet sich allerdings schwierig, da die Partitionierung erst zur Laufzeit
des Prozesses vollzogen werden kann. Dies folgt aus der Möglichkeit, voneinander abhängige Bearbeiterzuordnungen zu definieren, die erst zur Laufzeit auflösbar sind. Beispiele hierfür sind: „diese Aktivität muss vom Bearbeiter des Vorgängerknotens ausgeführt
werden“ oder „ein Mitglied der Organisationseinheit x muss diese Aktivität ausführen“.
Hieraus folgt, dass nicht nur die Partitionierung erst zur Laufzeit bekannt ist, sondern
auch, dass sich die Partitionierungen von Instanzen des gleichen Schemas unterscheiden
können. Dies wird in Abbildung 19 illustriert. Hierbei ist zur Modellierzeit nicht zu ent-
23
3 Konzepte
Mitarbeiter
Organisationseinheit
a
OE 1
Organisationsmodell
Schema
Start
Bearbeiterzuordnung
b
OE 1
c
OE 2
2
3
4
Mitarbeiter
aus OE 1
Mitarbeiter
aus OE 1
Mitarbeiter c
End
Partitionierung
oder
Abbildung 19: Partitionierungsschwierigkeiten bei Bearbeiterzuordnungen
scheiden, ob Knoten 2 und 3 zur selben Partition gehören, da sie jeweils von Mitarbeiter
a und b bearbeitet werden können. Ebenfalls zu beachten ist, dass die Annahme, dass
der Bearbeiter direkt folgende Prozessschritte hintereinander ausführt, nicht zwingend
erfüllt ist und somit die Möglichkeit besteht, dass die Partitionierung nicht benötigte
Knoten einlagert. Dies hängt von weiteren „weichen“ Faktoren wie dem Zeitaufwand für
das Bearbeiten eines Prozessschritts ab.
Aufgrund dieser Nachteile wird die Partitionierung durch Auswerten von Bearbeiterzuordnungen nicht weiter betrachtet.
3.1.2 Blockstruktur
Eine weitere Möglichkeit zur horizontalen Partitionierung besteht im Ausnutzen der
streng hierarchischen Blockstruktur des ADEPT2 Metamodells. Hierbei bildet jeder
Block eine Partition. Die hierfür benötigten Informationen sind bereits zur Modellierzeit
bekannt. Dadurch entsteht ein Vorteil dieser Partitionierung gegenüber dem Auswerten
von Bearbeiterzuordnungen: Bei Ausnutzung der Blockstruktur müssen keine Berechnungen zur Laufzeit durchgeführt werden und die Partitionierung ist für alle Instanzen eines
Schemas gleich.
0
2
10
3
Wurzel
block
11
4
3
12
6
11
5
Start
9
2
7
14
13
10
17
15
8
16
Blockstruktur im Schema
Abbildung 20: Blockstruktur mit Hierarchie
24
Block ab
Knoten 2
Block ab
Knoten 10
Block ab
Knoten 3
Block ab
Knoten 11
End
Hierarchie der Blockstruktur
3.2 Schemata
Die in Abschnitt 2.1.2 vorgestellte Blockstruktur wird leicht vereinfacht, um die Anzahl
der Partitionen zu verringern. Ein Block beginnt vor einem Split-Knoten, welcher den
Block aufspannt. Zusätzlich enthält er vorgestellten Blockstruktur alle Knoten und Kanten der untergeordneten Zweige. Diese waren bisher eigenen Blöcken zugeordnet, wie
Abbildung 3 gezeigt hat. Diese werden hier zusammengefügt um eine übersichtlichere
Anzahl an Partitionen zu erhalten. Der Block wird nach dem Join-Knoten geschlossen,
welcher dem Split-Knoten zugeordnet ist. Hierbei kann ein Block Unterblöcke enthalten,
sofern sich auf den Zweigen des Split-Knoten weitere Split-Knoten befinden. Jeder Knoten gehört zu genau einem Block. Deshalb gehören Knoten aus untergeordneten Blöcken
nicht zu übergeordneten (Knoten 4 in Abbildung 20 gehört nur zu Block 3, nicht zu 2).
Für die Knoten auf der Ebene des Start- und Endknotens des Prozesses, welche sich
ja auf keinem Zweig eines Split-Knotens befinden, wird ein künstlicher Block eingeführt,
der Wurzelblock. Dieser wird benötigt, um sicherzustellen, dass jeder Knoten genau einem
Block zugeordnet ist und somit die Knoten besser gehandhabt werden können.
3.1.3 Fazit
Nach der Betrachtung verschiedener Ansätze zur Partitionierung von Schemata wurde
festgestellt, dass eine vertikale Partitionierung nicht geeignet ist. Das dazu orthogonale
Konzept der horizontalen Partitionierung wurde anhand von zwei Varianten untersucht.
Hierbei wurde die Partitionierung anhand der Bearbeiterzuordnungen von Knoten verworfen, da diese erst zur Laufzeit bestimmt werden kann und somit zusätzlichen Rechenaufwand zur Laufzeit des Prozesses erfordert. Hieraus folgt auch, dass sich die Partitionierungen von Instanzen des selben Schemas voneinander unterscheiden können. Das
Ausnutzen der Blockstruktur für die Partitionierung besitzt hingegen keine Nachteile und
ist somit bei Schemata anwendbar.
3.2 Schemata
Die zuvor erarbeiteten Ergebnisse zur Partitionierbarkeit werden in diesem Abschnitt
auf die Repräsentation von Schemata angewandt. Dabei existieren verschiedene Varianten, welche ausgearbeitet und qualitativ verglichen werden. Hierfür ist ein qualitativer
Vergleich des Speicheraufwands der einzelnen Varianten nötig, wofür zur einheitlichen
Handhabung einleitend Vereinbarungen getroffen werden. Ebenso wird die Erweiterung
des Algorithmus vorgestellt, welcher die Relation zwischen zwei Knoten ermittelt und
häufig benötigt wird [JMSW04].
3.2.1 Speicherbetrachtungen
Dieser Abschnitt leitet in die qualitative Berechnung des Speicherplatzverbrauchs eines
Schemas S ein. Hierzu werden zusätzlich zu den in Abschnitt 2.5 definierten Größen sint ,
sObjektheader und sRef erenz folgende Größen herangezogen:
25
3 Konzepte
•
•
•
•
nKanten (S) Anzahl der Kanten von S
nKnoten (S) Anzahl der Knoten von S
nSplit (S) Anzahl der Split-Knoten von S
Die mit ∆ bezeichneten Größen bezeichnen den Speicherplatz, welcher von einer
Implementierung zusätzlich benötigt wird. Ein Beispiel hierfür ist der zusätzliche
Speicherplatz, der für eine lineare Liste benötigt wird, um eine Menge an Werten
zu halten.
Hieraus folgt die Berechnung des Speicherverbrauchs für die Kanten eines Schemas mit
der Formel
sKanten (S) = nKanten (S) · 2 · sint + sKantenattribute (S) + ∆Kanten (S)
Eine Kante besteht aus zwei ganzzahligen Werten: Der ID des Ursprungknotens und
der des Zielknotens. Da ohne zusätzliche Information nicht einzuschätzen ist, wie viele
Kantenattribute gespeichert werden, wird die Summe der Größen aller Kantenattribute
durch sKantenattribute (S) repräsentiert.
Für die Knoten eines Schemas S ergibt sich:
sKnoten (S) = nKnoten(S) · (4 · sint + sObjektheader ) + 2 · nSplit (S) · sint + ∆Knoten (S)
Ein Knoten hat nach Abschnitt 2.2 vier ganzzahlige Werte: Die ID und den Typ des
Knotens, sowie die Zweig- und Split-Knoten-ID. Split-Knoten besitzen hierbei einen weiteren ganzzahligen Wert: Die ID des zugehörigen Join-Knotens. Für Join-Knoten kommt
ebenfalls ein zusätzlicher ganzzahliger Wert hinzu: Die ID des zugehörigen Split-Knotens.
Da ein Schema gleich viele Split-Knoten wie Join-Knoten besitzt, kann 2 · nSplit(S) · sint
addiert werden. Hinzu kommt die Objektheader der Knoten-Objekte.
Der Speicherverbrauch eines Schemas S kann mit folgender Formel berechnet werden:
sSchema(S) = sint + sKanten (S) + sKnoten (S) + nKnoten (S) · sRef erenz + ∆Schema(S)
Dieses besteht neben der ID des Schemas aus den Kanten und Knoten. Zusätzlich werden
Referenzen auf die einzelnen Knotenobjekte benötigt.
3.2.2 Relation zwischen Knoten
Eine häufig für ein Schema benötigte Information ist die Relation zwischen
zwei Knoten n1 und n2 bezüglich der Kontrollkanten, also die Berechnung der
Vorgänger-/Nachfolgerbeziehung von n1 und n2 . Der Algorithmus isCPredecessorOf
[JMSW04] berechnet für zwei gegebene Knoten, ob der eine Vorgänger des anderen ist.
Dies ist allerdings nicht ausreichend. Es existieren nicht nur die Relationen „Knoten n1
ist Vorgänger von n2 “ und „Knoten n1 ist nicht Vorgänger von n2 “, sondern es existieren
deren drei:
26
3.2 Schemata
• n1 ist Vorgänger von n2
• n1 ist Nachfolger von n2
• n1 und n2 stehen in keiner Vorgänger-/Nachfolgerbeziehung. Dies ist beispielhaft in Abbildung 21 dargestellt. Hierbei stehen die Knoten 3 und 6 in keiner
Vorgänger-/Nachfolgerbeziehung, da sie sich auf parallelen Zweigen befinden.
Der Algorithmus getNodeRelation aus Anhang D.1 berechnet die korrekte Relation
zwischen zwei Knoten eines Schemas auf Basis dieser drei Kategorien.
3
Start
4
7
2
5
End
6
Abbildung 21: Knoten ohne Vorgänger-/Nachfolgerbeziehung
Der vorgestellte Algorithmus arbeitet auf der topologischen Sortierung der Knoten. Diese
Ordnung wird abgebildet durch die topologische ID. Besitzen die beiden Knoten n1 und
n2 die selbe topologische ID, so handelt es sich um den Trivialfall: n1 = n2 . Um die
restlichen Fälle abzudecken, genügt es nicht, die topologischen IDs zu vergleichen.
Es gibt mindestens einen Weg [DD90] vom Start-Knoten zu einem Knoten n des Schemas,
welcher durch Traversieren des Schemagraphen vom Start-Knoten aus errechnet werden
kann. Hierbei können mehrere existieren, was durch vorgelagerte Split-Knoten verursacht
wird (vergleiche Abbildung 22).
Durch Betrachten dieser Wege für die Knoten n1 und n2 kann der Algorithmus die restlichen Fälle abdecken. Sollte der Knoten n1 auf einem der Wege von Knoten n2 liegen, ist
n1 Vorgänger von n2 . Ist n2 auf einem der Wege von n1 , so ist n1 Nachfolger von n2 . Befindet sich keiner der Knoten auf einem der Wege des anderen, so befinden sich die Knoten
auf verschiedenen Zweigen und stehen somit in keiner Vorgänger-/Nachfolgerbeziehung.
erster Weg
zweiter Weg
reduzierter Weg: 6
8
7
3
10
12
9
Start
2
5
4
6
13
11
End
12
Abbildung 22: Verschiedene Wege vom Start-Knoten zu Knoten 12
27
3 Konzepte
Der Algorithmus nutzt aus, dass die Wege ohne Informationsverlust auf die dem Knoten
übergeordneten Split-Knoten reduziert werden können [JMSW04]. Der reduzierte Weg
für Knoten 12 aus Abbildung 22 ist beispielsweise 6 → 12, da der Split-Knoten 6 der
einzige, dem Knoten 12 übergeordnete Split-Knoten ist. Hierbei können die Knoten 2
bis 5 unbeachtet bleiben. Wichtig ist, dass der Split-Knoten, bei welchem „abgezweigt“
werden muss, Knoten 6 ist. Hierdurch existiert für einen Knoten nur noch ein Weg vom
Startknoten aus, welcher reduzierter Weg genannt wird. Diese reduzierten Wege der Knoten werden außerdem rückwärts betrachtet (also ausgehend vom Knoten n, nicht vom
Start-Knoten), da hier eine weitere Optimierung möglich wird: Es muss nicht der komplette Weg betrachtet werden, sondern nur der bis zum ersten gemeinsamen Split-Knoten
[JMSW04]. Im worst-case ist dies der Start-Knoten (welcher hier als Split-Knoten betrachtet wird, da er den äußersten Block aufspannt). Hiernach muss überprüft werden,
von welchem Zweig die einzelnen Wege der Knoten n1 und n2 kommen. Sind diese nicht
identisch, befinden sich die Knoten auf unterschiedlichen Zweigen und stehen somit in
keiner Vorgänger-/Nachfolgerbeziehung. Sind sie identisch, so gibt ein Vergleich der topologische IDs die Vorgänger-/Nachfolgerbeziehung an.
Für ein Schema S besitzt der Algorithmus somit eine Laufzeitkomplexität von
O(nSplit (S)).
3.2.3 Blockrepräsentation
Um die horizontale Partitionierung anhand der Blockstruktur wie in Abschnitt 3.1.2 zu
unterstützen, muss die Blockinformation im Schema vorhanden sein. In diesem Abschnitt
wird erörtert, welche allgemeinen Vorgehensweisen zu empfehlen und welche speziellen
Realisierungen der Blockrepräsentation möglich sind.
Für jeden Block ist ein eindeutiges Identifizierungsmerkmal nötig. Hierfür wäre eine einfach Durchnummerierung der Blöcke denkbar. Dies würde bei einer Umsetzung allerdings
weitere Strukturen erfordern, welche die Zugehörigkeit eines Knotens zu seinem Block
regeln. Besser ist es, die ID des den Block aufspannenden Split-Knotens als Blockidentifikation zu benutzen. Somit sind keine zusätzlichen Strukturen nötig. Es folgt, dass die
Anfrage, zu welchem Block ein Knoten gehört, in konstanter Laufzeit beantwortet werden
kann, da die ID des direkt übergeordneten Split-Knotens bereits im Knoten mitgeführt
wird.
Die Vorgänger-/Nachfolgerbeziehung zwischen zwei Blöcken b1 und b2 kann mithilfe des
in Abschnitt 3.2.2 vorgestellten Algorithmus getNodeRelation berechnet werden. Die
möglichen Relationen zwischen Blöcken sind:
•
•
•
•
•
28
b1
b1
b1
b1
b1
ist Vorgänger von b2
ist Nachfolger von b2
und b2 stehen in keiner Vorgänger-/Nachfolgerrelation
ist b2 untergeordnet
ist b2 übergeordnet
3.2 Schemata
Aufgrund der zwei zusätzlichen Relationen gegenüber denen zwischen Knoten, kann durch
einen Aufruf von getNodeRelation somit kein Ergebnis errechnet werden. Hat man allerdings die letzten beiden Relationen ausgeschlossen, reicht zur Berechnung ein Aufruf
von getNodeRelation mit den beiden Split-Knoten, welche die Blöcke aufspannen. Somit müssen diese beiden Fälle zuerst überprüft werden. Ist b1 untergeordnet zu b2 , so
befindet sich der aufspannende Split-Knoten von b2 auf dem reduzierten Weg (vergleiche
Abschnitt 3.2.2) vom Start-Knoten zu b1 . Dies gilt analog für b1 .
Ein blockorientiertes Schema muss zusätzliche Anfragen zu den in Abschnitt 2.2 definierten beantworten können. So muss neben der angesprochenen Auflösung eines Knotens
zum zugehörigen Block auch die Information verfügbar sein, welche Blöcke im Schema und welche Knoten und Kanten in einem bestimmten Block vorhanden sind. In
den Laufzeiten dieser Anfragen unterscheiden sich die verschiedenen Möglichkeiten, die
Blockstruktur zu repräsentieren.
Es gibt zwei Varianten zur Darstellung der Blockinformation: Zunächst wird die explizite
Blockrepräsentation untersucht. Im Anschluss wird die implizite betrachtet und ein Vergleich der beiden Varianten gezogen. Hierbei werden, neben dem Speicheraufwand auch
die Laufzeiten für die Berechnung der folgenden Mengen untersucht:
•
•
•
•
alle
alle
alle
alle
Knoten eines Blocks
Kanten eines Blocks
Blöcke des Schemas
Knoten und Kanten des Schemas
3.2.3.1 Explizite Blöcke Die Blockinformation kann explizit dargestellt werden. Dies
bedeutet, dass ein Schema aus einer Menge an Blöcken besteht, welche wiederum die
Knoten und Kanten beinhalten. Die blockbasierte Partitionierung ist in dieser Repräsentation ohne Anwendung weiterer Algorithmen verfügbar, wie aus Abbildung 23 ersichtlich
ist.
[KRRD09] schlägt vor, die Hierarchie der Blockstruktur mithilfe leerer Knoten abzubilden. Diese Knoten verweisen auf einen Unterblock, was auf der Behandlung von Subprozessen basiert. Hierbei lässt sich die Hierarchie allerdings nur von oben nach unten,
also vom Wurzelblock zu den untergeordneten Blöcken traversieren. Dies ist zum Beispiel dann problematisch, wenn der Nachfolger eines Knotens bestimmt werden soll. Wird
diese Anfrage für Knoten 6 aus Abbildung 24 gestellt, so muss zuerst der Block des Knotens bestimmt werden. Dieser wird daraufhin untersucht, ob der Nachfolger des Knotens
durch Untersuchen der Kantenmenge bestimmt werden kann. Dies ist für Knoten 6 nicht
möglich, da dies der topologisch letzte Knoten im Block ist. Daraufhin muss der übergeordnete Block und darin der Nachfolger des „leeren Knotens“ bestimmt werden. Dies ist
sehr aufwändig und benötigt zur Speicherung der leeren Knoten unnötig Speicherplatz.
Abhilfe schafft die zweite in Abbildung 24 dargestellte Möglichkeit zur Repräsentation. Es
werden innerhalb von Blöcken keine leeren Knoten zur Abbildung der Hierarchie benutzt.
Vielmehr werden in einem Block auch diejenigen Kanten gespeichert, welche entweder als
29
3 Konzepte
0
2
10
3
11
4
logische Sicht
3
11
6
5
Start
14
13
9
2
7
konzeptionelle Sicht
12
10
17
15
8
End
16
Schema
Block 0
Block 2
Block 3
Block 10
Block 11
Knoten des Blocks
Kanten des Blocks
Knoten des Blocks
Kanten des Blocks
Knoten des Blocks
Kanten des Blocks
Knoten des Blocks
Kanten des Blocks
Knoten des Blocks
Kanten des Blocks
Abbildung 23: Schemarepräsentation mit expliziter Blockstruktur
Quell- oder Ziel-Knoten-ID einen Knoten referenzieren, der außerhalb des Blocks liegt.
So kann der Vorgänger und Nachfolger eines Knotens jeweils direkt bestimmt werden.
Um die Hierarchie abzubilden, wird eine weitere Methode benötigt, welche zu einem gegebenen Schema und einem Knoten von diesem die Block-ID berechnet, zu der dieser
Knoten gehört. Ist also das Ziel einer Kante ein Knoten, welcher nicht im Block gespeichert ist, so kann der nachfolgende Block mithilfe dieser Methode bestimmt werden. Die
Hierarchie der Blockstruktur ergibt sich somit implizit.
Die Menge der Knoten, welche sich in einem Block befinden, lässt sich bei einer expliziten
Blockrepräsentation in konstanter Zeit beantworten, da diese Menge bereits innerhalb des
Blocks vorliegt. Gleiches gilt für die Menge an Kanten eines Blocks.
Die Information, welche Blöcke in einem Schema vorhanden sind, kann man bei der expliziten Blockrepräsentation ebenfalls in konstanter Laufzeit bestimmen, da die Blockmenge
direkt im Schema verfügbar ist.
Um alle Knoten oder Kanten des Schemas zu berechnen, fällt linearer Aufwand auf der
Anzahl der Knoten an, da die einzelnen Blöcke durchlaufen und die jeweiligen Knotenbzw. Kantenmengen vereinigt werden müssen. Hierbei liegt die Anzahl der Kanten ebenfalls in O(nKnoten (S)), deshalb ist auch hierbei der Aufwand linear zur Anzahl der Knoten.
Den Speicherplatzaufwand für ein Schema, welches durch explizite Blöcke dargestellt ist,
erhält man durch die Formel
sexplizit
Schema (S) =
X
[sRef erenz + sObjektheader + sint + sKanten (b) + sKnoten (b)]
Block b
+sint + ∆explizit
Schema (S)
30
3.2 Schemata
0
10
2
3
4
logische Sicht
3
6
5
Start
7
physische Sicht
nach [KRRD04]
Block 3
Knoten
ID Typ
Kanten
von nach
AND-Split
NORMAL
NORMAL
AND-Join
"leerer" Knoten, der auf Block 3 verweist
neue physische Sicht
Block 2
Knoten
ID Typ
2
7
8
9
AND-Split
NORMAL
NORMAL
AND-Join
...
8
Block 2
2
7
8
9
3
10
9
2
2
3
2
7
8
3
9
7
8
9
Block 3
Kanten
von nach
2
3*
6*
9
2
7
7
8
8
9
Start*
2
9
10*
Knoten
ID Typ
3
4
5
6
XOR-Split
NORMAL
NORMAL
XOR-Join
Kanten
von nach
3
4
4
6
3
5
5
6
2*
3
6
9*
Knoten
ID Typ
3
4
5
6
XOR-Split
NORMAL
NORMAL
XOR-Join
Kanten
von nach
3
4
3
5
4
6
5
6
* Knoten außerhalb des Blocks
Abbildung 24: Hierarchiedarstellung bei expliziter Blockstruktur
Für jeden Block wird hierbei eine Referenz auf das Block-Objekt benötigt. Dieses besteht
aus einem Objektheader, der Block-ID und den Kanten und Knoten. Hinzu kommt die ID
des Schemas und implementierungsabhängige Speicherstrukturen. sKnoten (Block) wird
auf die gleiche Weise berechnet wird wie sKnoten (Schema), wobei nur die Knoten, welche
zum angegebenen Block gehören in die Berechnung einbezogen werden.
sKanten (Block) ist folgendermaßen definiert:
sKanten (B) = (nKanten (B) + neingehende
Kanten (B)
+ nausgehende
Kanten (B))
·2 · sint + ∆Kanten (B)
Hierbei müssen wie bereits erwähnt auch die ein- und ausgehenden Kanten des Blocks
betrachtet werden.
Im Vergleich zu einem nicht blockorientiert gespeicherten Schema (vergleiche Abschnitt 2.2) ergibt sich somit einen Speicherplatzmehraufwand von
∆explizit
Schema (S) +
X
[sRef erenz + sObjektheader + sint + 2 · sint · nausgehende
Kanten (b)]
Block b
31
3 Konzepte
Dieser befindet sich in der Komplexitätsklasse O(nSplit(S)), da die Anzahl der Blöcke
direkt von der Anzahl der Split-Knoten abhängt.
3.2.3.2 Implizite Blöcke Neben der expliziten Blockrepräsentation lassen sich diese
auch implizit darstellen, da bereits jeder Knoten die Information beinhaltet, zu welchem
Block er gehört (Split-Knoten-ID, siehe Abbildung 6). Aufgrund dessen ist es nicht notwendig, die Blockstruktur explizit zu realisieren, sie kann implizit vorgehalten werden.
Hierdurch entsteht kein zusätzlicher Speicherplatzaufwand, da nur eine flache Knotenmenge gehalten wird.
Es ist allerdings ein linearer Zeitaufwand nötig, um die Menge der Knoten zu berechnen,
welche zu einem bestimmten Block gehören, da alle Knoten auf ihre Blockzugehörigkeit
untersucht werden müssen. Gleiches gilt für die Information, welche Blöcke im Schema
vorhanden sind. Um herauszufinden, welche Kanten zu einem Block gehören, müssen
alle Kanten untersucht werden. Es fällt somit ein linearer Aufwand an. Da die Menge aller Knoten und aller Kanten des Schemas bereits vorliegt, fällt hierfür konstanter
Zeitaufwand an.
Der Speicherplatzaufwand für ein implizites Schema S wird berechnet mit
implizit
simplizit
Schema (S) = sint + sKnoten (S) + sKanten (S) + nKnoten (S) · sRef erenz + ∆Schema (S)
Da ∆implizit
Schema (S) = ∆Schema (S) gilt, fällt kein zusätzlicher Speicherplatz gegenüber einem
nicht blockorientiert gespeicherten Schema an.
simplizit
Schema (S) = sSchema (S)
3.2.3.3 Fazit Es ist zu erkennen, dass beide Realisierungsmöglichkeiten Vorteile bieten,
welche in Tabelle 1 zusammengefasst sind. Es bleibt zu untersuchen, welche quantitativen
Unterschiede sich in Speicherverbrauch und in der Laufzeit zeigen.
zusätzlicher Speicherplatz
Blockmenge
Knoten → Block
Block → Knotenmenge
Block → Kantenmenge
Knoten-/Kantenmenge
Explizite Blöcke
O(nSplit (S))
O(1)
O(1)
O(1)
O(1)
O(nKnoten (S))
Implizite Blöcke
0
O(nKnoten (S))
O(1)
O(nKnoten (S))
O(nKanten (S))
O(1)
Tabelle 1: Explizite und implizite Realisierung von Blöcken
32
3.3 Grobe Clusterung
3.3 Grobe Clusterung
Neben der expliziten und impliziten Markierung besteht auch die Möglichkeit, einen Instanzzustand mithilfe der Clusterung abzubilden (vergleiche Abschnitt 2.1.3). Diese ist
für eine Schemaevolution vorteilhaft, da die zustandsbasierte Verträglichkeit nur einmal
überprüft werden muss und für ganze Cluster bestimmt werden kann, ob die referenzierten
Instanzen migrierbar oder nicht migrierbar sind. Es muss nicht jede Instanz separat untersucht werden. In [KRRD09] wurde allerdings festgestellt, dass die Clusterung aufgrund
des hohen Speicherbedarfs und der großen Anzahl an Clustern bei Parallelverzweigungen
nicht einsetzbar ist. In diesem Abschnitt wird die Idee der Clusterung hin zu einer speicherplatzeffizienten Optimierung der Schemaevolution entwickelt, die so genannte grobe
Clusterung.
Hierbei wird die in Abschnitt 3.1.2 vorgestellte Partitionierung anhand der Blockstruktur eines Schemas ausgenutzt. Für jeden Block des Schemas existiert ein Cluster. Ein
Cluster referenziert diejenigen Instanzen, welche Knoten mit dem Zustand RUNNING
des entsprechenden Blocks beinhalten. Der Cluster ist somit ein grober Anhaltspunkt,
wo eine Instanz in ihrer Ausführung steht. Die grobe Clusterung ist im Vergleich zur
(genauen) Clusterung aus Abschnitt 2.1.3 somit ebenfalls zustandsbasiert, repräsentiert
allerdings keine vollständige Instanzmarkierung.
0
2
10
3
11
4
3
12
11
6
5
Start
13
9
2
7
14
10
17
15
8
End
16
0
0
2
10
2
10
3
11
3
11
Hierarchie der Blockstruktur
Clusterbaum
Abbildung 25: Hierarchie der Blockstruktur und Clusterbaum
33
3 Konzepte
Es entsteht analog zur Hierarchie der Blockstruktur ein Clusterbaum, welcher aus der
hierarchischen Anordnung der Cluster besteht (vergleiche Abbildung 25). Dieser Clusterbaum ist eindeutig für ein Schema und lässt sich zur Modellierzeit berechnen. Die
exponentielle Anzahl an Clustern, welche durch Parallelverzweigungen entstehen und
welche in [KRRD09] zur Verwerfung des Ansatzes geführt haben, treten hierbei nicht
auf. Dies ist in der Blockstruktur des Schemas begründet: Es gibt nicht für jeden Zweig
eines Split-Knotens einen eigenen Cluster, sondern für den Split-Knoten mitsamt seiner
Zweige genau einen. Hierdurch potenziert sich die Anzahl der Cluster bei Parallelverzweigungen nicht mehr, sie reduziert sich auf nSplit (S) + 1. Aufgrund dessen ist es möglich,
den Clusterbaum eines Schemas im Primärspeicher zu halten.
Es lässt sich nicht nur im Clusterbaum eine Analogie zu den für Blöcke angestrengten
Betrachtungen ziehen. Auch die Vorgänger-/Nachfolgerbeziehung zwischen Clustern lässt
sich analog zu den der Blöcke definieren. Ein Cluster c1 ist also Vorgänger eines Clusters
c2 , sofern der Block von c1 Vorgänger des Blocks von c2 ist. Dies gilt analog für die
übrigen Relationen zwischen Blöcken.
Das Beispiel aus Abbildung 26 veranschaulicht, wie eine Instanz durch diesen Clusterbaum „wandert“. Bei der Instanz befindet sich als erstes der Start-Knoten im Zustand
RUNNING. Da dieser Knoten zum Wurzelblock gehört, wird diese Instanz vom Wurzelcluster referenziert. Durch Weiterschalten wird der Start-Knoten COMPLETED und
Knoten 2 RUNNING. Da sich nun nur im Block 2 Knoten im Zustand RUNNING befinden, wandert die Instanz im Clusterbaum nach unten: Nur der Cluster 2 referenziert
die Instanz. Nach dem Weiterschalten des AND-Split-Knotens befinden sich nun in zwei
verschiedenen Blöcken Knoten im Zustand RUNNING, somit wird auch die Instanz von
den Clustern 2 und 3 referenziert. Die Zugehörigkeit einer Instanz zu mehreren Clustern
ist somit möglich. Zum nächsten Schritt im Beispiel wurde mehrfach weitergeschaltet.
Der untere Zweig des AND-Splits wurde vollständig abgearbeitet. Der AND-Join-Knoten
ist allerdings noch nicht aktiviert, da sich ein Knoten auf dem oberen Zweig noch in Ausführung befindet. Dieser ist in Block 3, somit wird die Instanz nur noch von Cluster 3
referenziert. Durch weiteres, mehrmaliges Weiterschalten wird schließlich der AND-JoinKnoten in den Zustand RUNNING versetzt. Hierdurch wandert die Instanz im Clusterbaum wieder nach oben in den Cluster 2.
Die Änderung der Clusterzugehörigkeit kann hierbei durch den Algorithmus zum Weiterschalten berechnet werden. Dieser kennt den Block des aktiven Knotens vor dem Weiterschalten und weiß, zu welchem Knoten weitergeschaltet wird. Diese Information kann zur
effizienten Berechnung der (neuen) Clusterzugehörigkeit benutzt werden.
3.3.1 Schemaevolution
Bei einer Schemaevolution wird das Schema S an einer bestimmten Stelle x geändert.
Nach [Rin04, Jur06] müssen alle Instanzen von S in den Primärspeicher eingelagert und
einzeln untersucht werden (vergleiche Abschnitt 2.4). Dies ist sehr aufwändig. Durch den
34
3.3 Grobe Clusterung
Instanz
Clusterbaum
4
9
10
15
8
4
9
10
15
8
4
3
10
15
8
4
3
9
10
15
8
4
11
5
Start
End
14
13
9
2
7
17
16
12
6
3
End
14
11
13
2
7
17
16
12
6
5
Start
End
14
11
9
17
16
13
2
7
End
12
6
5
Start
17
14
13
2
7
End
16
11
5
Start
17
12
6
3
14
13
2
7
NOT_ACTIVATED
11
5
Start
SKIPPED
COMPLETED
12
6
3
RUNNING
8
10
15
16
Abbildung 26: Beispiel für die grobe Clusterung
35
3 Konzepte
Einsatz der groben Clusterung lässt sich dies allerdings optimieren. Mit ihr ist es möglich,
die zu untersuchende (und damit einzulagernde) Menge an Instanzen zu verringern, da
leicht festgestellt werden kann, welche Instanzen sich in ihrer Ausführung zwingend vor
der geänderten Stelle x und welche sich zwingend nach x befinden. Erstere sind migrierbare Instanzen, letztere können nicht migriert werden (vergleiche Abbildung 27). Hierbei
ist es nicht nötig, diese Instanzen aus dem Sekundärspeicher einzulagern, es genügt, die
Clusterzugehörigkeit zu untersuchen. Übrig bleibt noch eine Menge an Instanzen, welche sich in der Ausführung nahe an der Stelle x befinden. Diese werden dann mit den
in [Rin04, Jur06] vorgestellten Methoden untersucht. Danach kann eine Migration der
Instanzen wie in Abschnitt 2.4 beschrieben durchgeführt werden.
neu
3
Start
7
5
2
11
6
4
9
10
13
End
12
8
Clusterbaum
0
migrierbar
2
genauer untersuchen
6
10
nicht migrierbar
genauer untersuchen
Abbildung 27: Beispiel einer Schemaevolution bei grober Clusterung
Der im Folgenden vorgestellte Algorithmus preEvolveSelection teilt die Instanzen
durch Auswerten des Clusterbaums in folgende drei Mengen ein:
• Die Instanz ist migrierbar
• Die Instanz ist nicht migrierbar
• Die Instanz muss mithilfe der Methoden aus [Rin04, Jur06] genauer untersucht
werden
Hierfür ist ein weiterer Algorithmus, findPositions, nötig, der die im Schema geänderten Stellen findet. Nach dessen Vorstellung folgt preEvolveSelection.
3.3.1.1 Stellen Die Änderungen eines Schemas können zu Stellen zusammengefasst
werden. Dies berechnet der hier vorgestellte Algorithmus findPositions (siehe An-
36
3.3 Grobe Clusterung
hang D.2). Hierbei bildet jeder Block, der dem Wurzelblock direkt untergeordnet ist eine
potentielle Stelle. Diese existiert dann, wenn der Block selbst oder einer seiner direkten
oder indirekten Unterblöcke Änderungen enthält. Diese Änderungen sind dann dieser
Stelle zugeordnet, wie es in Abbildung 28 dargestellt ist. In der Abbildung ist auch der in
Block 3 hinzugefügte Knoten der Stelle 2 zugeordnet. Für die Berechnung hiervon genügt
ein Verfolgen des reduzierten Weges (vergleiche Abschnitt 3.2.2) vom Start-Knoten zum
geänderten Knoten. Der zuerst besuchte Split-Knoten spannt den direkt dem Wurzelblock untergeordneten Block der Stelle auf. Für Änderungen im Wurzelblock existiert
keine Stelle. Das ist möglich, da diese für preEvolveSelection einen Spezialfall darstellen. Sie werden in findPositions ignoriert.
neu
#$ %nderun&n
0
10
2
3
11
4
3
12
6
11
5
Start
13
9
2
7
14
10
17
15
8
End
16
neu
Abbildung 28: Zuordnung von Änderungen zu einer Stelle
Ziel des Algorithmus ist nicht nur die Bestimmung der Stellen, sondern auch festzustellen,
ob eine Instanz diese Stelle in der Ausführung passiert haben kann, ohne einen geänderten
Knoten ausgeführt zu haben. Ein geänderter Knoten ist hierbei das Ziel einer gelöschten
oder hinzugefügten Kante [Jur06].
Dies wird über den so genannten Stellentyp abgebildet, welcher jeder Stelle zugeordnet
ist. Dieser ist entweder LEAKY, wenn eine Instanz diese Stelle passiert haben kann, ohne
einen geänderten Knoten ausgeführt zu haben, oder im gegensätzlichen Fall COMPACT.
Somit ist die markierte Stelle der Instanz des Schemas 1 in Abbildung 29 COMPACT. Die
Stelle der anderen dargestellten Instanz ist allerdings LEAKY. Durch die grobe Clusterung stehen nur Informationen zu den Knoten zu Verfügung, welche aktuell RUNNING
sind. Überträgt man dies auf die Instanz des Schemas 2, so ist nur bekannt, dass Knoten 6 RUNNING ist. Auf Basis dieser Information ist nicht entscheidbar, ob die Instanz
den geänderten Zweig des Split-Knotens ausgeführt hat. Dies wird durch den Stellentyp
LEAKY verdeutlicht.
37
3 Konzepte
RUNNING
COMPLETED
SKIPPED
NOT_ACTIVATED
Instanz auf Schema 1
neu
3
Start
2
5
6
End
5
6
End
4
Stelle
Instanz auf Schema 2
neu
3
Start
2
4
Stelle
Abbildung 29: COMPACT und LEAKY
Für die Unterscheidung ob eine Stelle LEAKY oder COMPACT ist, müssen die reduzierten Wege aller Änderungen einer Stelle zum Start-Knoten betrachtet werden. Diese
werden durch Einträge in der Menge splitBranches repräsentiert. Die Einträge sind
Tripel der Form {s, b, n}. Hierbei ist s ein Split-Knoten auf einem der reduzierten Wege.
Verfolgt man von diesem ausgehend den Zweig b, so ist der nächste Knoten auf dem reduzierten Weg der Knoten n. Dieser ist entweder wiederum ein Split-Knoten (für den ein
eigener Eintrag in splitBranches existiert) oder es ist ein geänderter Knoten. Hierdurch
sind die reduzierten Wege aller Änderungen verfügbar. Die rekursive Methode isFull
überprüft auf Basis dieser Menge und einer gegebenen ID eines Split-Knotens, ob der
von diesem aufgespannte Block durchlaufen werden kann, ohne einen geänderten Knoten
ausgeführt zu haben. Hierfür untersucht isFull zuerst den Typ des Split-Knotens. Ist
dies ein AND-Split-Knoten so genügt es, wenn sich auf einem Zweig des Split-Knotens
eine unpassierbare Änderung befindet (also isFull für diese Änderung „wahr“ als Ergebnis liefert). Ist der von isFull zu untersuchende Knoten ein XOR-Split-Knoten, so
müssen auf allen Zweigen unpassierbare Änderungen sein. Im gegensätzlichen Fall ist der
Block passierbar, ohne eine Änderung ausgeführt haben zu müssen. Folgerichtig liefert
isFull in diesem Fall „falsch“ zurück.
Um den Typ einer Stelle zu berechnen, genügt es, isFull mit dem Split-Knoten aufzurufen, welcher den Block der Stelle aufspannt. Wird „wahr“ zurückgeliefert, so ist die Stelle
COMPACT, andernfalls LEAKY.
38
3.3 Grobe Clusterung
3.3.1.2 Kategorisierung der Instanzen Aufbauend auf dem vorgestellten Algorithmus
findPositions ist es möglich, Instanzen anhand ihrer Clusterzugehörigkeit in die oben
genannten Kategorien einzuteilen. Der Algorithmus hierfür, preEvolveSelection (siehe
Anhang D.3) wird in diesem Abschnitt vorgestellt.
Die Zuordnung der Änderung zu Stellen wird von findPositions vorgenommen. Hierbei
werden die Änderungen im Wurzelblock ignoriert, da diese keiner Stelle zuzuordnen sind.
Diese werden als Spezialfall behandelt. Hierbei gilt, dass bei einer Änderung im Wurzelblock alle Instanzen als nicht-migrierbar einzuordnen sind, welche von Clustern referenziert werden, die der Änderung nachgelagert sind. In Abbildung 30 ist dies illustriert.
Hierbei müssen Instanzen aus dem Cluster 7 als nicht migrierbar eingestuft werden, da
sie der Änderung nachgelagert sind. Für Instanzen, welche sich im Wurzelcluster befinden
gilt in jedem Fall, dass diese mit den Methoden aus [Rin04, Jur06] genauer untersucht
werden müssen.
2
neu
7
8
3
Start
2
5
4
6
7
10
End
9
Abbildung 30: Änderung im Wurzelblock
Die Änderungen aus allen anderen Blöcken wurden von findPositions zu Stellen zugeordnet. Diese Stellen befinden sich ebenfalls in einer topologischen Sortierung, die durch
die topologischen IDs der Split-Knoten gegeben ist, welche die Blöcke der Stellen aufspannen. Somit kann die erste von Instanzen durchlaufene Stelle vom Typ COMPACT
mithilfe des Algorithmus getNodeRelation berechnet werden. Alle Instanzen, welche von
Clustern referenziert werden, die dieser Stelle folgen, müssen als nicht migrierbar eingestuft werden. Diese haben zwangsweise einen geänderten Knoten der COMPACT Stelle
durchlaufen.
Instanzen, welche von einem Cluster referenziert werden, der einer LEAKY Stelle folgt,
haben nicht zwangsweise einen geänderten Knoten der Stelle ausgeführt. Die Entscheidung, ob diese Instanzen migrierbar sind oder nicht, kann nicht mithilfe der groben
Clusterung getroffen werden. Somit müssen diese Instanzen mit den Methoden aus
[Rin04, Jur06] genauer untersucht werden. Hierbei gibt es keinen Sinn, eine bereits als
nicht migrierbar eingestufte Instanz als genauer zu untersuchen einzustufen. Sollte der
topologisch ersten LEAKY Stelle also eine COMPACT Stelle folgen, müssen Instanzen aus Clustern, welche der COMPACT Stelle nachgeordnet sind, als nicht migrierbar
eingestuft werden.
Bisher wurden Instanzen eingestuft, welche von Clustern referenziert werden, die einer
Stelle folgen. Instanzen, die sich in ihrer Ausführung vor der topologisch ersten Stelle
39
3 Konzepte
befinden, sind migrierbar. Es ist sichergestellt, dass diese noch keinen geänderten Knoten
ausgeführt haben.
Nicht betrachtet wurden bisher Instanzen, welche sich in der Ausführung in einem Block
befinden, welcher der (topologisch) ersten Stelle zugeordnet ist. Diese Instanzen könnten alle mithilfe von [Rin04, Jur06] genauer untersucht werden. Es ist allerdings eine
genauere Unterscheidung aufgrund der groben Clusterung möglich. Es werden alle geänderten Knoten der (topologisch) ersten Stelle untersucht. Instanzen, welche sich im
Cluster eines geänderten Knotens befinden, müssen genauer untersucht werden, da sich
nicht herausfinden lässt, ob diese den geänderten Knoten bereits ausgeführt haben. Für
untergeordnete Cluster kann untersucht werden, ob der Block der Änderung nachgelagert
ist. Sollte dies so sein, sind alle Instanzen des entsprechenden Clusters und dessen Untercluster nicht migrierbar. Befindet sich der untergeordnete Cluster auf einem anderen
Zweig als die Änderung und wird der Cluster von einem AND-Split-Knoten aufgespannt,
müssen die Instanzen des Clusters (und dessen untergeordneten) mit den Methoden aus
[Rin04, Jur06] genauer untersucht werden. Sollte der Cluster von einem XOR-Split aufgespannt werden, ist dies nicht notwendig, da eine Instanz, welche von diesem Untercluster
referenziert wird, den Zweig des Unterclusters und somit nicht den Zweig mit der Änderung ausgeführt hat. Instanzen aus einem Untercluster, welcher der Änderung vorgelagert
ist, sind ebenfalls weiterhin potentiell migrierbar. Zusätzlich müssen alle anderen Cluster der Stelle untersucht werden. Ist einer der Cluster der Änderung nicht vorgelagert,
müssen die Instanzen hierin mithilfe [Rin04, Jur06] genauer untersucht werden.
Da diese Berechnungen für jede Änderung der (topologisch) ersten Stelle durchgeführt
werden, ist es möglich, dass Instanzen mehrfach bewertet wurden. Aufgrund dessen ist
es notwendig, die Mengen anschließend zu bereinigen. Hierbei erfolgt eine Priorisierung
der Kategorien:
1. „nicht migrierbar“
2. „genauer untersuchen“
3. „migrierbar“
Das heißt, ist eine Instanz in der ersten Kategorie, muss sie aus den Instanzmengen
der anderen Kategorien entfernt werden. Wurde eine Instanz in die zweite Kategorie
eingeordnet, muss sie aus der Instanzmenge der dritten entfernt werden. Nach der Überprüfung der Instanzen der zweiten Kategorie nach [Rin04, Jur06] kann die Migration wie
in Abschnitt 2.4 fortgesetzt werden.
3.3.2 Fazit
In diesem Abschnitt wurde die grobe Clusterung vorgestellt, welche zur Optimierung
des Laufzeitaufwands bei einer Schemaevolution herangezogen werden kann. Mithilfe der
groben Clusterung ist es möglich, eine Einschätzung zu treffen, wo eine Instanz in ihrem
Ablauf in etwa steht. Es wurden zwei Algorithmen vorgestellt, welche in Kombination eine Einteilung der Instanzen vor einer Schemaevolution in migrierbare und nicht
40
3.4 Instanzbasierte Änderungen
migrierbare Instanzen, sowie in Instanzen vornehmen, welche nach den Methoden aus
[Rin04, Jur06] genauer untersucht werden müssen. Dadurch müssen nicht mehr alle Instanzen eines Schemas bei einer Schemaevolution einzeln untersucht und hierfür aus dem
Sekundärspeicher eingelagert werden.
3.4 Instanzbasierte Änderungen
Wie in Abschnitt 2.2 bereits erwähnt, unterstützt das ADEPT2-Metamodell instanzbasierte Änderungen. In diesem Abschnitt wird allerdings aufgezeigt, dass die Realisierung
des Konzepts in der bisherigen Form [KR10] nicht ausreichend ist. Dies wird daraufhin
entsprechend erweitert. Anschließend werden Definitionen zur Speicherbetrachtung der
Deltaschicht aufgezeigt und mithilfe dieser die verschiedenen Repräsentationen erläutert.
Abschließend werden instanzbasierte Änderungen in Bezug auf die grobe Clusterung untersucht.
3.4.1 Erweiterung des Deltaschicht-Modells
Um eine praktische Anwendung der Deltaschicht zu ermöglichen, muss diese angepasst
werden. So wurde bei bisherigen Betrachtungen (vergleiche Abschnitt 2.2.3) davon ausgegangen, dass verschobene Knoten mithilfe ihrer ID repräsentiert werden können. Dies
reicht allerdings nicht aus, da sich beim Verschieben unter Umständen die Split-KnotenID und die Zweig-ID des Knotens ändern, was dazu führt, dass diese Werte ebenso überlagert werden müssen. Hierbei lohnt sich die Überlagerung des ganzen Knoten-Objekts,
da dieses nicht viele weitere Eigenschaften hat. Für eine Überlagerung der Split-KnotenID und Zweig-ID in separaten Mengen würde zusätzlicher Speicherbedarf zur Verwaltung
dieser anfallen.
Ebenso beachtet werden muss, dass sich bei einer instanzbasierten Änderung die topologischen IDs nicht nur von den überlagerten Knoten ändern. Wird ein Knoten durch
die Deltaschicht eingefügt oder gelöscht, so ändern sich auch die topologischen IDs aller
nachgelagerten Knoten. Gleiches gilt beim Verschieben eines Knotens, hier ändern sich
die topologischen IDs der Knoten, welche sich (topologisch) zwischen der alten und neuen
Position im Schema befinden. Dies ist mit dem bisherigen Ansatz, die topologische ID im
Knoten vorzuhalten (vergleiche Abbildung 6), nicht effizient umsetzbar: Die Deltaschicht
müsste schon bei kleinen Änderungen eine Vielzahl an Knoten überlagern, nur, weil deren topologische IDs nicht mehr korrekt sind. Dieses Problem wird dadurch behoben,
dass die topologischen IDs fortan nicht mehr innerhalb der Knoten sondern im Schema
beziehungsweise in der Deltaschicht verwaltet werden. Dies illustriert Abbildung 31.
41
3 Konzepte
logische Sicht
2
Start
physische Sicht
Deltaschicht
Anfrage
hinzugefügte Knoten
4
ID
topologische ID
Split ID
Zweig ID
2
1
0
0
gelöschte Knoten
verschobene Knoten
keine
4
3
ID
topologische ID
Split ID
Zweig ID
ID
topologische ID
Split ID
Zweig ID
4
2
0
0
hinzugefügte Kanten
2
4
keine
3
3
0
0
gelöschte Kanten
4
3
2
End
Knoten-ID
topologische ID
4
3
2
3
3
logische Sicht
2
ID
topologische ID
Split ID
Zweig ID
Blockinformation
Start
impliziter
physische Sicht bei
zugrundeliegendes Schema
Anfrage
wird potentiell
weitergeleitet
Knoten
Start
2
3
3
ID
topologische ID
Split ID
Zweig ID
2
1
0
0
Kanten
End
Start
2
3
2
3
End
3
2
0
0
Knoten-ID
End
topologische ID
2
1
3
2
Abbildung 31: Kapselung bei Einsatz der erweiterten Deltaschicht
3.4.2 Speicherbetrachtungen
Für eine Deltaschicht D, welche auf einem Schema S beruht, sei S + D das Schema
inklusive den Änderungen der Deltaschicht (resultierendes Schema). Analog zu den Betrachtungen für Schemata gilt hierfür:
•
•
•
•
•
•
•
•
•
•
42
nKanten (D) Anzahl der Kanten von S + D
nKnoten (D) Anzahl der Knoten von S + D
nSplit (D) Anzahl der Split-Knoten von S + D
nHKanten (D) Anzahl der hinzugefügten Kanten in D
nGKanten (D) Anzahl der gelöschten Kanten in D
nHKnoten (D) Anzahl der hinzugefügten Knoten in D
nGKnoten (D) Anzahl der gelöschten Knoten in D
nV Knoten (D) Anzahl der verschobenen Knoten in D
nHBlock (D) Anzahl der hinzugefügten Blöcke in D
nGBlock (D) Anzahl der gelöschten Blöcke in D
3.4 Instanzbasierte Änderungen
• nV Block (D) Anzahl der verschobenen Blöcke in D
• nT opIDs (D) Anzahl der überlagerten topologischen IDs in D
Es gilt weiterhin:
sKanten (D) = (nHKanten (D) + nGKanten (D)) · 2 · sint + sKantenattribute (D)
+∆Kanten (D)
und
sT opIDs(D) = nT opIDs(D) · 2 · sint + ∆T opIDs(D)
Hierbei ist zu beachten, dass die topologischen IDs eine Zuordnung von einer KnotenID zu einer (neuen) topologischen ID sind und somit zwei ganzzahlige Werte anfallen
(vergleiche Abbildung 31).
sKnoten (D) = nGKnoten (D) · sint
+(nHKnoten (D) + nV Knoten (D)) · (4 · sint + sObjektheader )
+2 · nHBlock (D) · sint
+∆Knoten (D)
Für gelöschte Knoten reicht die Speicherung dessen ID. Der zweite Summand berechnet
den benötigten Speicherplatz für überlagerte, also hinzugefügte und verschobene Knoten.
Jeder Knoten besteht aus vier ganzzahligen Werten, wobei für Split- und Join-Knoten
jeweils ein weiterer hinzukommt. Den hierfür nötigen Speicheraufwand stellt der dritte
Summand dar. Dieser lässt sich aus der Anzahl der hinzugefügten Blöcke berechnen,
da jeder hinzugefügte Block einem hinzugefügten Split- und einem hinzugefügten JoinKnoten entspricht.
Für die gesamte Deltaschicht ergibt sich
sDelta (D) = sKanten (D) + sKnoten (D) + sT opIDs (D)
+(nHKnoten (D) + nV Knoten (D)) · sRef erenz
+∆Delta (D)
3.4.3 Repräsentation
Nach der Erweiterung von Schemata um Blockinformationen muss auch die Deltaschicht
diese abbilden. Dies folgt aus der Anforderung, dass eine Deltaschicht die gleichen Zugriffsmöglichkeiten wie ein vollwertiges Schema bieten muss (vergleiche Abschnitt 2.2.3).
Hierbei ergeben sich analog zu Schemata die Möglichkeiten, die Blockinformation explizit
oder implizit darzustellen. Diese Varianten werden im Folgenden vorgestellt.
43
3 Konzepte
3.4.3.1 Explizite Blockinformation Die explizite Darstellung der Blockinformation in
der Deltaschicht geschieht analog zur entsprechenden Repräsentation von Schemata: Der
Deltaschicht sind Deltablöcke zugeordnet, welche wiederum die Änderungen einzelner
Blöcke repräsentieren. Hierbei besitzen die Deltablöcke Mengen, welche hinzugefügte und
gelöschte Knoten und Kanten, verschobene Knoten und Änderungen der topologischen
IDs darstellen. Analog zu „normalen“ Blöcken, werden auch von den Deltablöcken eingehende und ausgehende Kanten der einzelnen Blöcke abgebildet. Die Deltablöcke müssen
nur vorgehalten werden, sofern Änderungen im entsprechenden Block getätigt wurden.
Hierunter fallen auch jegliche Änderungen auf Blockebene, also das Hinzufügen von neuen Blöcken und das Verschieben und Löschen von vorhandenen Blöcken. Zusätzlich hält
die Deltaschicht eine Menge, welche Knoten-IDs zu Block-IDs auflöst. Hierbei existieren
für alle Knoten Einträge in der Menge, für die sich sie Blockzuordnung geändert hat.
Die explizite Repräsentation der Deltaschicht ist in Abbildung 32 dargestellt: In Block 3
wird Knoten 10 hinzugefügt. Hieraus folgt, dass auch für die Blöcke 0 und 2 Deltablöcke
erzeugt werden müssen, da sich innerhalb dieser Blöcke topologische IDs ändern.
Für Anfragen an die Deltaschicht nach den hinzugefügten oder gelöschten Knoten
oder Kanten oder nach verschobenen Knoten müssen die Informationen der einzelnen
Deltablöcke zusammengefasst und somit linearer Zeitaufwand getrieben werden, also
O(nHBlock (D) + nV Block (D) + nGBlock (D)).
Bei Schemata wurde die Komplexität für die Berechnung folgender Mengen untersucht:
•
•
•
•
•
Auflösung Knoten-ID zu Block-ID
Blockmenge des Schemas
Knotenmenge eines Blocks
Kantenmenge eines Blocks
Knoten- und Kantenmenge des gesamten Schemas
Diese Anfragen können aufgrund der Kapselung von Schemata auch an eine Deltaschicht
gestellt werden. Für die Auflösung einer Knoten-ID zur Block-ID wird hierzu zuerst die
in der Deltaschicht vorhandene Menge untersucht, ob zum entsprechenden Knoten ein
Eintrag vorhanden ist. Ist dies der Fall, wird der korrespondierende Wert zurückgeliefert. Ansonsten wird die Anfrage an das Schema weitergeleitet. Es entsteht ein zusätzlicher konstanter Zeitaufwand gegenüber der Anfrage an ein Schema. Die Blockmenge
ist durch Abrufen der Blockmenge des Schemas und Anpassen dieser mit den in der
Deltaschicht vorhanden Deltablöcken verfügbar. Es entsteht zusätzlicher linearer Aufwand auf der Anzahl der Deltablöcke. Die Knoten- und Kantenmenge eines bestimmten
Blocks kann durch Überlagerung des Blocks des Schemas mit den Informationen des
entsprechenden Deltablocks errechnet werden, sofern dieser vorhanden ist. Hierzu muss
die Knoten-/Kantenmenge des zugrunde liegenden Blocks abgerufen werden und diese
durch die im Deltablock gespeicherten Mengen der hinzugefügten und gelöschten Knoten
beziehungsweise Kanten mit linearem Zeitaufwand angepasst werden. Die Anfrage nach
allen im resultierenden Schema vorhandenen Knoten und Kanten benötigt analog zur
entsprechenden Repräsentation von Schemata linearen Aufwand, da die Informationen
für die einzelnen Blöcke gesammelt und zusammengefügt werden müssen.
44
3.4 Instanzbasierte Änderungen
0
2
Deltaschicht
logische Sicht mit topologischen IDs
3
4
3
10
4
3
2
5
Start
2
0
5
9
9
1
7
Auflösung Knoten
6
6
5
8
7
End
10
8
Block:
Knoten
Block
10
3
physische Sicht
Deltablöcke:
Block 0
hinzugefügte Knoten
gelöschte Knoten
verschobene Knoten
hinzugefügte Kanten
gelöschte Kanten
Knoten-ID
topologische ID
keine
keine
keine
keine
keine
End
10
hinzugefügte Knoten
gelöschte Knoten
verschobene Knoten
hinzugefügte Kanten
gelöschte Kanten
Knoten-ID
topologische ID
7
8
9
Block 2
keine
keine
keine
keine
keine
7
8
9
hinzugefügte Knoten
gelöschte Knoten
verschobene Knoten
hinzugefügte Kanten
gelöschte Kanten
Knoten-ID
topologische ID
10
5
6
4
5
6
Block 3
keine
10
4
10
keine
10
6
4
6
0
2
logische IDs
3
*
4
6
5
it to
)(
5
Schema
'
logische Sicht
3
3
2
Start
0
2
4
9
8
1
7
6
8
End
9
7
Abbildung 32: Deltaschicht mit expliziter Blockinformation
3.4.3.2 Implizite Blockinformation Bei der Darstellung mit impliziter Blockinformation werden für hinzugefügte, gelöschte und verschobene Knoten und für hinzugefügte und
gelöschte Kanten Mengen vorgehalten. Dies gleicht der Repräsentation aus Abbildung 31.
45
3 Konzepte
Wird die Menge der hinzugefügten oder gelöschten Knoten oder Kanten, oder der verschobenen Knoten angefragt, so können diese in konstanter Zeit beantwortet werden, da
diese Mengen bereits vorliegen.
Die im vorigen Abschnitt rekapitulierten Anfragen an Schemata können auch an eine
Deltaschicht mit impliziter Blockinformation gestellt werden. So kann die Knoten-ID
zur korrespondierenden Block-ID ebenfalls mithilfe der im Knoten-Objekt vorhandenen
Split-Knoten-ID aufgelöst werden. Dieses ist entweder direkt in der Deltaschicht verfügbar (wenn der Knoten verschoben oder hinzugefügt wurde) oder kann vom zugrunde
liegenden Schema abgerufen werden. Es fällt gegenüber einer Anfrage an ein Schema
zusätzlicher, konstanter Zeitaufwand an. Die Blockmenge kann durch Berechnung der
Blockmenge des Schemas errechnet werden. Hierbei müssen für in der Deltaschicht hinzugefügte Knoten mit linearem Zeitaufwand überprüft werden, ob diese einen neuen Block
aufspannen und somit ein weiterer Block existiert. Ebenso muss die Menge der gelöschten
Knoten linear untersucht werden, ob ein Block komplett entfernt wurde. Gleiches gilt für
die Bestimmung der Knoten- und Kantenmengen eines bestimmten Blocks: Nachdem die
ursprünglichen Daten vom Schema abgerufen sind, müssen diese mit linearem Zeitaufwand durch Untersuchen der in der Deltaschicht vorhandenen Mengen angepasst werden.
Das Ergebnis der Anfrage nach allen im resultierenden Schema vorhandenen Knoten oder
Kanten wird errechnet, indem die Daten des zugrunde liegenden Schemas abgerufen und
durch die Mengen der hinzugefügten und gelöschten Knoten und Kanten angepasst wird.
Hierbei fällt ebenfalls linearer Zeitaufwand an.
3.4.3.3 Vergleich Betrachtet man den Speicheraufwand, der für die explizite Repräsentation der Deltaschicht getrieben werden muss, so erkennt man, dass dieser unverhältnismäßig ist. Wird nur ein Knoten verschoben, müssen möglicherweise mehrere Deltablöcke
vorgehalten werden. Dieser Speichermehraufwand ist mit den leicht besseren Laufzeiten
bei Anfragen nicht auszugleichen. Deshalb wird der Ansatz der expliziten Speicherung
der Blockinformationen in der Deltaschicht verworfen. Dies schließt allerdings nicht aus,
dass die Repräsentation mit expliziter Blockstruktur bei Schemata eingesetzt wird. Es ist
somit möglich, dass eine Deltaschicht mit impliziter Blockstruktur ein Schema überlagert,
welches in expliziter Blockstruktur vorliegt.
3.4.4 Clusterung und Schemaevolution
Die in Abschnitt 3.3 vorgestellte grobe Clusterung optimiert eine Schemaevolution dahingehend, dass einige Instanzen bereits vor deren Einlagerung aus dem Sekundärspeicher
als migrierbar und nicht migrierbar eingestuft werden können. Hierbei müssen allerdings
auch instanzbasiert geänderte Instanzen betrachtet werden. Ziel ist ebenfalls eine Einstufung dieser in migrierbare, nicht migrierbare und genauer zu untersuchende Instanzen.
Bei einer Schemaevolution werden für instanzbasiert geänderte Instanzen die Änderungen am ursprünglichen Schema und die instanzbasierten miteinander vereinigt. Diese
46
3.4 Instanzbasierte Änderungen
strukturellen Anpassungen sind nach der Überprüfung der strukturellen Konfliktfreiheit
zusätzlich zur Überprüfung der zustandsbasierten Verträglichkeit zu tätigen. Die grobe
Clusterung bildet allerdings ausschließlich den Zustand von Instanzen ab und besitzt keine Informationen über deren Struktur. Es ist somit offensichtlich, dass alleine mithilfe der
groben Clusterung die Entscheidung nicht getroffen werden kann, ob eine instanzbasiert
geänderte Instanz migrierbar oder nicht migrierbar ist. Die Instanz muss also eingelagert werden, wenn die zustandsbasierte Verträglichkeit gegeben ist, um die strukturellen
Überprüfungen und Anpassungen durchzuführen. Sollte die zustandsbasierte Verträglichkeit allerdings ergeben, dass die Instanz nicht migrierbar ist, so muss die Struktur auch
nicht untersucht werden. Dies führt zu der Annahme, dass in diesem Fall die instanzbasiert geänderte Instanz auch nicht eingelagert werden muss und die grobe Clusterung zur
Bestimmung der zustandsbasierten Verträglichkeit herangezogen werden kann.
RUNNING
COMPLETED
8
4
3
SKIPPED
Schema
NOT_ACTIVATED
2
Start
7
6
10
5
9
2
7
Instanz mit Änderung
Clusterbaum
3
0
Start
End
2
ie
Versch
6
5
ben
8
4
7
10
End
9
Abbildung 33: Schemaevolution mit instanzbasierten Änderungen
Abbildung 33 stellt allerdings ein Gegenbeispiel dar. Auf dem dargestellten Schema basiert eine Instanz mit einer instanzbasierten Änderung. Es wird davon ausgegangen, dass
die Instanz im Clusterbaum des ursprünglichen Schemas gehalten werden kann. Angenommen es wird die selbe Änderung auf dem Schema durchgeführt, welche bereits für die
Instanz durchgeführt wurde. Es wird also Knoten 4 zwischen Knoten 7 und 8 verschoben.
Hierbei wird die Kante von Knoten 3 nach Knoten 6 gelöscht und somit Knoten 6 als
geändert markiert. Dies bedeutet, dass Cluster 2 vom Algorithmus findPositions korrekterweise als Stelle vom Typ COMPACT erkannt wird. Somit werden alle Instanzen aus
Clustern als nicht migrierbar eingestuft, welche Cluster 2 nachgeordnet sind. Dies trifft
auch auf die gezeigte instanzbasiert geänderte Instanz zu, obwohl diese migrierbar ist,
da die instanzbasierten Änderungen und die Änderungen am Schema übereinstimmen.
Hieraus folgt, dass die Annahme fehlerhaft war, auch instanzbasiert geänderte Instanzen
im Clusterbaum des ursprünglichen Schemas halten zu können.
47
3 Konzepte
Somit stellt sich heraus, dass die grobe Clusterung keine Vorteile für instanzbasiert geänderte Instanzen bietet. Diese müssen bei einer Schemaevolution alle eingelagert und
nach den bisherigen Methoden [Rin04, Jur06] untersucht werden.
3.5 Fazit
In diesem Kapitel wurde eine geeignete Partitionierung für Schemata gefunden und die
Repräsentation von Schemata aus Abschnitt 2.2 entsprechend angepasst. Aufbauend darauf wurde die grobe Clusterung eingeführt, welche auf Basis einer impliziten oder expliziten Markierung einer Instanz für eine Laufzeitoptimierung bei einer Schemaevolution
herangezogen werden kann. Es wurden Algorithmen beschrieben, mit deren Hilfe eine
Einordnung der Instanzen vor einer Schemaevolution in migrierbare und nicht migrierbare Instanzen möglich ist, sowie Instanzen bestimmt werden können, welche nach den
Methoden aus [Rin04, Jur06] genauer untersucht werden müssen. Danach wurden instanzbasierte Änderungen und deren Repräsentation der Blockinformation betrachtet.
Abschließend wurde festgestellt, dass die grobe Clusterung keine Möglichkeit der Optimierung für instanzbasiert geänderte Instanzen bereit hält.
48
4 Umsetzung
In diesem Kapitel wird die Realisierung einer Workflow-Testumgebung beschrieben, welche auf den in Kapitel 2 vorgestellten Grundlagen beruht und die Konzepte aus Kapitel 3
beinhaltet. Ziel ist es, mithilfe dieser Umsetzung quantitative Untersuchungen der verschiedenen vorgestellten Varianten zur Realisierung durchzuführen.
Schemata und Instanzen müssen sowohl im Primär- als auch im Sekundärspeicher repräsentiert werden. Dies ist nötig, da Anfragen an die Workflow-Testumgebung im Primärspeicher stattfinden, die Daten allerdings auch persistiert werden müssen. Nach einführenden Betrachtungen werden zuerst die Primärspeicherrepräsentationen untersucht. Hierauf
aufbauend folgen die Repräsentationen im Sekundärspeicher. Dieses Kapitel schließt eine
Betrachtung von Anfragen auf Kollektionen von Schemata und Instanzen ab.
4.1 Einführung
Für eine Realisierung der Konzepte ist die Entscheidung für eine Programmiersprache
und -umgebung nötig. Hier bietet sich Java™ 1 [GJSB05] an, da es weit verbreitet, einfach
anzuwenden und auf verschiedenen Softwareplattformen lauffähig ist.
In diesem Abschnitt werden zuerst für diese Arbeit relevante Aspekte von Java™ betrachtet. Anschließend wird ein Überblick über die Architektur der Workflow-Testumgebung
gegeben.
4.1.1 Relevante Aspekte von Java™
Die Java™ Virtual Machine (JVM) ist eine Ausführungsumgebung für objektorientierte
Programme, welche in so genanntem Bytecode vorliegen. Dieser entsteht durch Übersetzen von Quelltext, welcher üblicherweise in der Programmiersprache Java™ geschrieben
ist. Durch diese Abstraktionsebene ist es möglich, ein in Bytecode vorliegendes Programm
auf unterschiedlichen Wirtssystemen auszuführen, sofern für dieses eine JVM existiert.
Außerdem wird in der JVM ein Garbage Collector [JL96, Knu97] eingesetzt, welcher
nicht weiter benötigte Java™ -Objekte aus dem Primärspeicher automatisch entfernt.
Die JVM setzt außerdem die HotSpot-Optimierung [Sun09a] ein. Dies ist ein sogenannter Just-In-Time-Übersetzer, welcher den vorhandenen Bytecode bei Bedarf in nativen
Programmcode übersetzt. Dieser kann auf dem Wirtsrechner ohne weitere Interpretation
der JVM ausgeführt werden.
Diese Optimierungen beeinflussen die Messungen, die Ziel der Umsetzung sind. Es ist zum
Beispiel nicht vorhersagbar, wann der Speicher bereinigt und Rechenzeit für das Auffinden und Beseitigen der nicht länger benötigten Objekte benötigt wird. Um die hieraus
resultierenden Messfehler zu minimieren, werden die Messungen mehrmals durchgeführt
1
Java™ ist eine eingetragene Marke der Firma Sun Microsystems Inc.
49
4 Umsetzung
und ein Mittelwert errechnet. Außerdem wird der Garbage Collector während der Messungen an geeigneten Stellen aufgerufen [Rou02], um eine automatische Bereinigung des
Speichers möglichst zu unterbinden.
Auch die HotSpot-Optimierung erzeugt Messfehler. So ist nicht abzuschätzen, wann der
Bytecode in nativen Code übersetzt wird. Hier hilft ein Kommandozeilenparameter für
die JVM, welcher erzwingt, dass bereits beim ersten Aufruf einer Methode diese in nativen Programmcode übersetzt werden soll: -XX:CompileThreshold=1 [Sun09c]. Hierfür
ist es allerdings nötig, dass vor der ersten Messung bereits alle benötigten Methoden
einmal aufgerufen wurden. Deshalb wird bei den Messungen ein so genannter First Shot
durchgeführt, welcher das gleiche Verhalten wie ein normaler Testlauf hat, jedoch nicht
zum Ergebnis gerechnet wird.
Die Einführung des First Shot behebt auch das Problem, dass die JVM bei der ersten
Benutzung einer Klasse die zugehörigen Daten aus dem Dateisystem lädt. Dies verfälscht
den Testlauf ebenfalls.
Um die Größe von Java™ -Objekten zu bestimmen, werden zuerst mithilfe des Garbage Collectors alle Daten aus dem Primärspeicher entfernt, die nicht mehr referenziert
werden. Dann wird der verbrauchte Speicherplatz gemessen und 100.000 Objekte der zu
untersuchenden Klasse erzeugt. Hiernach folgt eine erneute Messung des verbrauchten
Speicherplatzes. Dadurch ist es möglich, den Speicherplatz eines Objektes zu errechnen
[Rou02, Kre02]. Messfehler, die durch einen nicht wie erwartetet funktionierenden Garbage Collector entstehen, werden durch die große Anzahl an Objekten minimiert.
In dieser Arbeit kommt die 32-Bit-Variante der Sun Java™ VM in der Version 1.6.0_16
unter Ubuntu Linux 9.04 zum Einsatz.
4.1.2 Architektur
Die Anforderung an die mithilfe von Java™ realisierte Testumgebung besteht in der
Durchführung von quantitativen Messungen des Speicherverbrauchs und der Laufzeit.
Diese Messungen werden durch so genannte Tests abgedeckt. Das Hauptprogramm bekommt als Kommandozeilenparameter die Namen der Tests übergeben, startet diese
hiernach und gibt die Ergebnisse auf der Konsole und, falls gewünscht, in eine Textdatei
aus.
Database
Test
setup(Database db)
cleanup()
firstShot()
run()
getID()
setDatabase(String db)
getConnectionURL()
loadClass()
getDriverClassName()
getHibernateDialect()
getDataType(String defaultName)
Abbildung 34: Test- und Database-Schnittstelle
50
4.1 Einführung
Die Tests implementieren die Test-Schnittstelle (Interface), welche in Abbildung 34 dargestellt ist. Diese bietet die Möglichkeit, den Test vorzubereiten, einen First Shot, die
eigentliche Messung und Aufräumarbeiten nach dem Testlauf durchzuführen. Die Tests
sind unabhängig vom eingesetzten Sekundärspeicher, dessen Daten zur Ansteuerung von
einer Implementierung der Database-Schnittstelle geliefert werden, welche ebenfalls in
Abbildung 34 dargestellt ist. Mithilfe dieser Schnittstelle ist es möglich, den Namen
der zu benutzenden Datenbank des RDBMS zu wählen und die für JDBC notwendige
ConnectionURL abzurufen. Diese beinhaltet alle zum Verbindungsaufbau nötigen Eigenschaften. Die Methode loadClass() lädt den JDBC-Treiber. Um das RDBMS auch für
JPA (Hibernate) benutzen zu können, werden die beiden weiteren Informationen der
Treiberklasse und des einzusetzenden Hibernate-Dialekts [Red09] benötigt. Abschließend
stellt die Schnittstelle eine Möglichkeit zur Anpassung von Datentyp-Namen zur Verfügung. Bei PostgreSQL heißt der Standard-Datentyp CLOB zum Beispiel TEXT. Das Hauptprogramm wählt eine Implementierung der Database-Schnittstelle anhand der ID aus,
welche als Kommandozeilenparameter übergeben wird. Diese wird dann der Implementierung der Test-Schnittstelle zur Verfügung gestellt.
Schemata
Primärspeicherrepräsentationen
Template
Sekundärspeicherrepräsentationen
TemplateStorage
Tests
Test
Test #1
Instanzen
Primärspeicherrepräsentationen
Instance
Hauptprogramm
Test #2
Sekundärspeicherrepräsentationen
InstanceStorage
Test ...
Cluster
Datenbankinformation
Database
Primärspeicherrepräsentation
Cluster
Deltaschicht
Primärspeicherrepräsentation
DeltaLayer
Abbildung 35: Architektur der Workflow-Testumgebung mit Schnittstellen
51
4 Umsetzung
Der Rest der Workflow-Testumgebung teilt sich auf in die Bereiche Schemata, Instanzen,
instanzbasierte Änderungen (Deltaschicht) und Cluster. Die Tests realisieren Zugriffe
hierauf und führen die spezifischen Messungen durch. Abbildung 35 illustriert dies. Hierbei sind die Namen der jeweils eingesetzten Schnittstellen den einzelnen Komponenten
zugeordnet.
4.2 Primärspeicher
Die Entitäten werden im Primärspeicher durch Java™ -Objekten dargestellt. Es existieren
sowohl für Schemata als auch für Instanzen verschiedene Repräsentationen, welche in
Kapitel 2 und 3 konzeptionell vorgestellt wurden. Deren Umsetzung wird zusammen mit
den Repräsentationen einer Deltaschicht und der groben Clusterung im Folgenden nach
der Betrachtung der Standard-Datenstrukturen von Java vorgestellt.
4.2.1 Standard-Datenstrukturen von Java™
Bei der Realisierung der Entitäten durch Primärspeicherobjekte ist eine Nutzung von
Standard-Datenstrukturen von Java™ sinnvoll. Die Eigenschaften der verschiedenen bereitgestellten Varianten werden hier erläutert.
Eine häufig benötigte Datenstruktur ist eine Map. Diese stellt eine Zuweisung eines Wertes zu einem Schlüssel dar. Es gibt verschiedene Implementierungsvarianten einer Map
in der Standardbibliothek, welche sich in der Verwaltung der Schlüssel unterscheiden:
HashMap benutzt Hashing [CLRS09], während TreeMap eine Baumstruktur benutzt. Somit hat eine HashMap erwartet konstanten Zugriff, eine TreeMap logarithmischen. Der
Speicherplatzverbrauch der Implementierungen ist in Abbildung 36 gegenüber gestellt.
4492
4000
3500
Größe in Bytes
3000
2500
HashMap
TreeMap
2000
1500
1000
500
0
1
10
20
30
40
50
60
70
80
Anzahl gespeicherte Objekte
Abbildung 36: Speicherverbrauch von Maps
52
90
100
4.2 Primärspeicher
Hierbei ist zu erkennen, dass die HashMap nahezu einen linearen Speicheraufwand zu
der Anzahl der in ihr gespeicherten Objekte besitzt, genau wie die TreeMap. Die treppenartigen Abweichungen wie zum Beispiel bei 50 gespeicherten Objekten sind in einer
Vergrößerung der Hashtabelle begründet, welche die HashMap verwaltet. Aufgrund der
konstanten Zugriffszeit auf die Daten ist die Benutzung der HashMap im Allgemeinen
vorzuziehen. Wird eine Sortierung der Schlüssel-Wert-Paare der Map benötigt, muss allerdings TreeMap benutzt werden, da HashMap die Daten nicht sortiert liefern kann.
Eine weitere häufig benötigte Datenstruktur ist das Set. Dieses stellt eine Menge von beliebigen Java™ -Objekten dar. Auch hier existieren analog zu Maps die beiden grundlegenden Implementierungen HashSet und TreeSet. Diese greifen auf die Implementierungen
von Map zurück, wobei jeweils nur Schlüssel gespeichert werden. Aufgrund dessen gelten
für HashSet und TreeSet die gleichen Betrachtungen wie für HashMap und TreeMap.
4.2.2 Schemata
Auf Basis der vorgestellten Standard-Datenstrukturen von Java™ ist die Ausarbeitung von Primärspeicherrepräsentationen von Schemata möglich, welche die
Template-Schnittstelle implementieren. Diese ist in Abbildung 37 dargestellt. In dieser,
wie in folgenden Abbildungen, wird eine Semantik für Pfeile benutzt, welche sich an der
von Klassendiagrammen der Unified Modeling Language [Obj06] anlehnt. Hierbei bilden
Pfeile mit ausgefüllter Spitze eine Assoziation mit Angabe der Navigierbarkeit. Bei denjenigen mit unausgefüllten Spitzen wird weiter zwischen gestrichelten und durchgezogenen
Pfeilen unterschieden. Die gestrichelten stellen eine Schnittstellenrealisierungsbeziehung
dar, die durchgezogenen eine Generalisierung.
Die Template-Schnittstelle bietet die Möglichkeit, neben der Blockstruktur auch einen
Knoten anhand seiner ID abzurufen, und die Vorgänger, Nachfolger und die topologische
ID eines Knotens zu bestimmen. Außerdem lässt sich die Relation zwischen zwei Knoten
mithilfe des Algorithmus getNodeRelation aus Kapitel 3.2.2 berechnen. Für Schemata
existieren zwei Repräsentationen, wie in Abschnitt 3.2.3 erläutert: ExplicitTemplate
bietet eine Repräsentation mit expliziter Blockstruktur, während ImplicitTemplate eine
mit impliziter Blockstruktur darstellt. Auf diese wird nach der Vorstellung der KnotenObjekte näher eingegangen.
Den beiden Repräsentationen ist gemein, dass Kantenmengen durch eine Map dargestellt
werden. Diese besitzt als Schlüssel die ID des Knotens bei dem Kanten beginnen. Als
Wert wird ein Set benutzt, welches die IDs der Zielknoten der Kanten beinhaltet. Eine
Menge an topologischen IDs von Knoten wird ebenfalls von einer Map repräsentiert. Der
Schlüssel ist hierbei die ID des Knotens und der Wert die zugeordnete topologische ID.
4.2.2.1 Knoten Ein Knoten wird durch ein Objekt der Klasse Node repräsentiert. Diese
Klasse hält alle Attribute eines Knoten. Hierbei wird die topologische ID des Knotens
nach Abschnitt 3.4.1 vom Schema bzw. von der Deltaschicht verwaltet. Es ist allerdings
53
4 Umsetzung
Template
getAllNodes()
getAllEdges()
getNode(int nodeID)
getTopologicalIDOfNode(int nodeID)
getNodeSuccessors(int nodeID)
getNodePredecessors(int nodeID)
getNodeRelation(int node1ID, node2ID)
getAllBlocks()
ExplicitTemplate
ImplicitTemplate
getBlock(int blockID)
getBlockOfNode(int nodeID)
getNodesOfBlock(int blockID)
Repräsentation mit expliziter Blockstruktur
Repräsentation mit impliziter Blockstruktur
nodes: Map<int, Node>
edges: Map<int, Set<int>>
edgesBackwards: Map<int, Set<int>>
topIDs: Map<int, int>
blocks: Map<int, Block>
nodeToBlock: Map<int, int>
getAllNodes()
getAllEdges()
getNode(int nodeID)
getAllNodes()
getAllEdges()
getTopologicalIDOfNode(int nodeID)
getNodeSuccessors(int nodeID)
getNodePredecessors(int nodeID)
getNodeRelation(int node1ID, node2ID)
getNode(int nodeID)
getTopologicalIDOfNode(int nodeID)
getNodeSuccessors(int nodeID)
getNodePredecessors(int nodeID)
getNodeRelation(int node1ID, node2ID)
getAllBlocks()
getAllBlocks()
getBlock(int blockID)
getBlockOfNode(int nodeID)
getNodesOfBlock(int blockID)
getBlock(int blockID)
getBlockOfNode(int nodeID)
getNodesOfBlock(int blockID)
Block
ID: int
nodes: Map<int, Node>
edges: Map<int, Set<int>>
edgesBackwards: Map<int, Set<int>>
topIDs: Map<int, int>
getAllNodes()
getAllEdges()
getNode(int nodeID)
getTopologicalIDOfNode(int nodeID)
getNodeSuccessors(int nodeID)
getNodePredecessors(int nodeID)
Node
ID: int
type: NodeType
branchID: int
splitID: int
SplitNode
JoinNode
ID: int
type: NodeType
branchID: int
splitID: int
joinID: int
ID: int
type: NodeType
branchID: int
splitID: int
correspondingSplitID: int
Abbildung 37: Template-Schnittstelle
zu beachten, dass es zwei Knotentypen gibt, für die mehr Daten existieren. Ein SplitKnoten benötigt die ID des zugehörigen Join-Knotens und ein Join-Knoten die ID des
korrespondierenden Split-Knotens. Dies ist über Vererbung [DN67] gelöst: SplitNode
leitet von Node ab und enthält ein weiteres Feld, das die nötigen Informationen bereit
hält. Entsprechendes gilt für JoinNode.
Eine Menge von Knoten wird durch eine Map repräsentiert. Diese benutzt als Schlüssel die
ID des Knoten und als zugeordneten Wert das Node-Objekt. Dies wird der Verwendung
eines Sets vorgezogen um einen konstanten Laufzeitfaktor beim Zugriff auf einen Knoten
aufgrund seiner ID zu erreichen. Bei einem Set wäre es nötig, die ganze Menge der Knoten
54
4.2 Primärspeicher
auf den gesuchten hin zu untersuchen, während dieser bei einer Map direkt abgerufen
werden kann.
Node-Objekte werden von beiden folgenden Schema-Repräsentationen zur Darstellung
von Knoten benutzt.
4.2.2.2 ExplicitTemplate Ein ExplicitTemplate repräsentiert ein Schema, welches
die Blockinformation explizit speichert (vergleiche Abbildungen 23 und 24). Hierbei wird
ein Block mithilfe eines Objekts der Klasse Block repräsentiert. Das Schema referenziert
mehrere dieser Block-Objekte. Diese beinhalten jeweils die Knoten und Kanten des entsprechenden Blocks. Da die Kanten zum Beispiel für den Algorithmus getNodeRelation
rückwärts gerichtet benötigt werden, wird zusätzlich eine Menge dieser gehalten. Um
die geforderte konstante Laufzeit beim Auflösen einer Knoten-ID zu einer Block-ID zu
gewährleisten, wird eine weitere Menge innerhalb von ExplicitTemplate benötigt, welche dies abbildet. Es ist zu bedenken, dass Abschnitt 3.2.3.1 davon ausgeht, dass die
Block-ID eines Knoten durch dessen Split-Knoten-ID verfügbar ist und somit von keinem zusätzlichen Speicherbedarf ausgeht. Hier wird allerdings eine Knoten-ID zu einer
Block-ID aufgelöst. Das Knoten-Objekt ist allerdings nur innerhalb des Block-Objekts
vorhanden. Um dieses abzurufen muss allerdings die gesuchte Block-ID vorhanden sein,
es entsteht somit ein zirkulärer Bezug. Aufgrund dessen wird eine weitere Menge benötigt, welche Knoten-ID auf Block-ID abbildet. Dies geschieht mit einer Map. Es entsteht
ein zusätzlicher Speicherplatzaufwand von O(nKnoten (S)).
Aufgrund der expliziten Partitionierung ergibt sich eine geeignete Möglichkeit zur Aufteilung der Knoten- und Kantenmengen in Primär- und Sekundärspeicher. So werden
initial nicht alle Block-Objekte im Primärspeicher gehalten. Wird ein nicht eingelagerter
Block oder ein Knoten dessen angefragt, kann dieser beim Sekundärspeicher angefragt
und nachgeladen werden.
4.2.2.3 ImplicitTemplate Diese Klasse repräsentiert ein Schema mit impliziter Blockrepräsentation (vergleiche Abschnitt 3.2.3.2). Analog zu Block beinhaltet auch diese
Klasse die Kanten zusätzlich rückwärts gerichtet. Zusätzlich zu den Kantenmengen werden die topologischen IDs und eine Knotenmenge mit Objekten der Klasse Node gehalten.
Ein dynamisches Nachladen der Blöcke, wie ExplicitTemplate es unterstützt, ist bei
ImplicitTemplate nicht sinnvoll, da flache Knoten- und Kantenmengen gehalten werden. Es wäre denkbar, diese Mengen zu partitionieren und die Knotenobjekte erst einzulagern, wenn der Block angefragt wird. Hier entsteht allerdings ein zirkulärer Bezug.
Soll ein Knoten anhand seiner ID abgerufen werden, so muss überprüft werden, ob dieser eingelagert ist. Ist dies nicht der Fall, so muss dessen Block bestimmt und dieser
eingelagert werden. Um allerdings die Auflösung von Knoten-ID nach Block-ID zu bewerkstelligen, wird die Split-Knoten-ID des Knotens benötigt. Dieser soll aber gerade
nachgeladen werden und ist deshalb nicht im Primärspeicher vorhanden. Somit müsste
hierfür analog zu ExplicitTemplate eine weitere Menge geführt werden, welche diese
55
4 Umsetzung
Auflösung übernimmt. Dies erfordert allerdings zusätzlichen Speicher mit Aufwand von
O(nKnoten (S)), was unverhältnismäßig hoch ist, da sonst der Speichervorteil gegenüber
einem Schema mit expliziten Blockinformationen verloren geht. Deshalb unterstützt die
Realisierung mit impliziten Blockinformationen kein dynamisches Nachladen.
4.2.3 Deltaschicht
Template
getAllNodes()
getAllEdges()
getNode(int nodeID)
getTopologicalIDOfNode(int nodeID)
getNodeSuccessors(int nodeID)
getNodePredecessors(int nodeID)
getNodeRelation(int node1ID, node2ID)
getAllBlocks()
getBlock(int blockID)
getBlockOfNode(int nodeID)
getNodesOfBlock(int blockID)
ExplicitTemplate
Repräsentation mit
expliziter Blockstruktur
DeltaLayer
baseTemplate: Template
addedNodes: Map<int, Node>
ImplicitTemplate
Repräsentation mit
impliziter Blockstruktur
movedNodes: Map<int, Node>
deletedNodes: Set<int>
addedEdges: Map<int, Set<int>>
addedEdgesBackwards: Map<int, Set<int>>
deletedEdges: Map<int, Set<int>>
deletedEdgesBackwards: Map<int, Set<int>>
topIDs: Map<int, int>
getAllNodes()
getAllEdges()
getNode(int nodeID)
getTopologicalIDOfNode(int nodeID)
getNodeSuccessors(int nodeID)
getNodePredecessors(int nodeID)
getNodeRelation(int node1ID, node2ID)
getAllBlocks()
getBlock(int blockID)
getBlockOfNode(int nodeID)
getNodesOfBlock(int blockID)
addNode(Node n)
addEdge(int fromNodeID, int toNodeID)
removeNode(int nodeID)
removeEdge(int fromNodeID, int toNodeID)
moveNode(Node newNode)
setOverridingTopIDs(Map topIDs)
Abbildung 38: Die Klasse DeltaLayer
Aufbauend auf den vorgestellten Repräsentationen von Schemata muss zur Unterstützung
von instanzbasierten Änderungen eine Deltaschicht definiert werden. In Abschnitt 3.4.3
wurde festgestellt, dass hierbei nur die Repräsentation mit impliziten Blockstrukturen
56
4.2 Primärspeicher
sinnvoll ist. Dessen Repräsentation wird mithilfe der Klasse DeltaLayer realisiert. Diese besitzt die Möglichkeit, zu einem zugrunde liegenden Schema Knoten und Kanten
hinzuzufügen, daraus zu löschen oder als verschoben zu markieren. Ebenso wird eine
Referenz auf das zugrunde liegende Schema gehalten und die überlagerten topologischen
IDs verwaltet. Es werden hier keine Korrektheitsuntersuchungen durchgeführt, da diese
von Änderungsoperationen [Jur06] durchgeführt werden und im Rahmen dieser Arbeit
nicht relevant sind.
Wie in Abbildung 38 dargestellt, implementiert DeltaLayer die Schnittstelle Template
um wie ein Schema („gekapselte Vorlage“ [KR10]) benutzt werden zu können. Hierbei
werden die Methoden von Template innerhalb von DeltaLayer neu implementiert. Diese
nehmen die Überlagerung der instanzbasierten Daten über die Schemadaten vor.
4.2.4 Instanzen
Wie für Schemata gibt es auch für Instanzen zwei verschiedene Repräsentationen, welche
in Abschnitt 2.2 vorgestellt wurden. Hierzu zählt eine mit impliziten Knotenzuständen,
ImplicitInstance, und eine mit expliziten, ExplicitInstance. Beide implementieren
die Schnittstelle Instance, um von der Workflow-Testumgebung einheitlich behandelt
werden zu können.
Von einer Instanz kann man die Zustände der einzelnen Knoten abfragen und diese neu
setzen. Außerdem kann die Instanz weitergeschaltet und instanzbasiert geändert werden,
wie in Abbildung 39 dargestellt.
Instance
getTemplate()
getNodeState(int nodeID)
proceed(int nodeID, int branchID)
adhocModify(DeltaLayer d)
ImplicitInstance
ExplicitInstance
Repräsentation mit
expliziter Knotenmarkierung
Repräsentation mit
impliziter Knotenmarkierung
ID: int
ID: int
template: Template
template: Template
nodeState: Map<int, State>
getTemplate()
getNodeState(int nodeID)
proceed(int nodeID, int branchID)
adhocModify(DeltaLayer d)
nodesRunning: Set<int>
nodesSkipped: Set<int>
getTemplate()
getNodeState(int nodeID)
proceed(int nodeID, int branchID)
adhocModify(DeltaLayer d)
Abbildung 39: Instance-Schnittstelle
57
4 Umsetzung
4.2.4.1 ExplicitInstance Diese Klasse hält die Knotenzustände für jeden Knoten des
Schemas explizit vor: Es wird eine Map vorgehalten, welche jeder Knoten-ID einen Knotenzustand zuordnet.
Hinzu kommen ein Feld für die ID der Instanz und eine Referenz auf das zugrunde
liegende Schema.
4.2.4.2 ImplicitInstance ImplicitInstance repräsentiert eine Instanz, welche die
Knotenzustände implizit speichert, wie in Abschnitt 2.2 erwähnt. Es werden zwei Mengen
in Form von Sets gehalten: Die IDs der Knoten, welche RUNNING respektive SKIPPED
sind. Die Knotenzustände COMPLETED und NOT_ACTIVATED werden aus diesen
Mengen zur Laufzeit berechnet.
Auch hier kommen Felder für die ID der Instanz und eine Referenz auf das zugrunde
liegende Schema hinzu.
4.2.5 Cluster
Für die Realisierung der groben Clusterung aus Abschnitt 3.3 existiert die Klasse Cluster
(vergleiche Abbildung 40). Diese bildet einen Clusterbaum ab, welcher für ein bestimmtes
Schema bei dessen Erzeugung angelegt wird. Die Map instances repräsentiert die Cluster.
Schlüssel ist hierbei die ID des Clusters, Wert ist ein Set von Instanz-IDs, welche sich in
diesem Cluster befinden. Die Hierarchie der Cluster wird über zwei weitere Maps abgebildet: topCluster und subClusters. topCluster besitzt als Schlüssel die Cluster-IDs
und als Wert die ID des jeweils übergeordneten Clusters. subClusters bildet die Gegenrichtung ab, speichert also zu einer Cluster-ID als Schlüssel ein Set der untergeordneten
Cluster. Instanzen, welche auf dem Schema beruhen, aber instanzbasiert geändert sind,
werden in der separaten Menge adhocInstances gehalten, welche mithilfe eines Sets
umgesetzt wurde.
Cluster
instances: Map<int, Set<int>>
subClusters: Map<int, Set<int>>
topCluster: Map<int, int>
adhocInstances: Set<int>
getTemplate()
getInstances(int clusterID)
proceedInstance(int instanceID, int nodeID, int branchID)
evolve(DeltaLayer d)
modifyInstance(int instanceID, DeltaLayer d)
createNewInstance()
Abbildung 40: Die Klasse Cluster
Die Cluster-Klasse bietet die Möglichkeit, die Instanzen eines bestimmten Clusters abzurufen, instanzbasiert zu ändern, oder eine Schemaevolution auf dem zugrunde liegenden
58
4.3 Sekundärspeicher
Schema durchzuführen. Hierfür bekommt die entsprechende Methode einen DeltaLayer
übergeben, welcher die Änderungen auf dem ursprünglichen Schema repräsentiert. Es
werden dann alle Instanzen des Schemas mithilfe des Algorithmus preEvolveSelection
aus Anhang D.3 klassifiziert. Die genauere Untersuchung der Instanzen, welche durch
den Algorithmus in preciseCheck eingeordnet wurden, wird allerdings nicht realisiert,
da dies im Rahmen dieser Arbeit nicht relevant ist.
Außerdem bietet Cluster die Möglichkeit, eine Instanz weiterzuschalten. Dies darf bei
Einsatz der groben Clusterung nicht von der Instanz behandelt werden, da nach dem
Weiterschalten eine Neubewertung der Clusterzugehörigkeit der Instanz notwendig ist.
Dies kann nur Cluster vornehmen.
4.2.6 Zusammenfassung
In diesem Abschnitt wurden die Realisierungsmöglichkeiten der Entitäten der WorkflowTestumgebung im Primärspeicher aufgezeigt. Diese basieren auf Java™ -Objekten. Aufbauend auf der Betrachtung der Standard-Datenstrukturen von Java™ wurden die verschiedenen Varianten erläutert, ein Schema darzustellen, ebenso wie die verschiedenen
Möglichkeiten eine Instanz zu repräsentieren. Danach wurde die Umsetzung der groben
Clusterung aus Abschnitt 3.3 betrachtet und die Repräsentation der Deltaschicht vorgestellt.
Mithilfe dieser Primärspeicher-Umsetzung ist bereits eine funktionsfähige Testumgebung
verfügbar. Es können Schemata angelegt und Instanzen darauf basierend definiert werden.
Diese können weitergeschaltet werden und bieten somit die grundlegende Funktionalität
eines Workflow-Systems. Durch die Möglichkeit, Instanzen instanzbasiert zu ändern, und
dank den Überlegungen bezüglich der Schemaevolution sind die grundlegenden Flexibilitätseigenschaften von ADEPT2 verfügbar.
4.3 Sekundärspeicher
Eine Workflow-Umgebung benötigt neben einer Primärspeicherrepräsentation der Entitäten des Systems auch eine Sekundärspeicherrepräsentation, um realistische Verhältnisse
abzubilden. Da Rechner in zeitlich unbestimmten Abständen neu gestartet werden oder
Sicherungskopien der Daten angelegt werden sollen, ist eine persistente Speicherung dieser in der Realität unerlässlich.
Hierzu werden verschiedene Realisierungsalternativen vorgestellt. Nachdem die Begriffe
Primärspeicher und Sekundärspeicher präziser definiert wurden, folgt die Untersuchung
der verschiedenen Sekundärspeicherrepräsentationen von Schemata und Instanzen. Anschließend werden Zwischenspeicher, so genannte Caches [Cle06], zur Optimierung des
Zugriffs auf die Entitäten eingeführt. Um Messungen auch ausschließlich unter Verwendung des Hauptspeichers ohne aufwändige Änderungen an der Architektur der Workflow-
59
4 Umsetzung
Testumgebung realisieren zu können, wird abschließend die Speicherung der Entitäten in
flüchtigen Speicher vorgestellt.
4.3.1 Realisierungsalternativen
Es existieren verschiedene Ansätze, die Entitäten im Sekundärspeicher zu speichern. Hierzu zählt die Persistierung im Dateisystem, mithilfe eines relationalen Datenbanksystems
und die Verwendung der Java™ Persistence API [Sun06]. Diese werden im Folgenden
vorgestellt.
Für die Speicherung im Dateisystem ist die Verwendung von XML [W3C06] aufgrund dessen Verbreitung und einfachen Handhabung zu empfehlen. Darauf basierend werden Schema- und Instanz-Repräsentationen definiert. Die Erstellung der XMLRepräsentation nennt man Serialisierung, die Rücktransformation Deserialisierung. Die
Schemata und Instanzen werden in das XML-Format serialisiert, im Dateisystem gespeichert und können hieraus wieder geladen und deserialisiert werden.
Eine weitere Möglichkeit besteht in der Anwendung von relationalen Datenbanksystemen
(RDBMS) [Cod70]. Hier wird eine Auswahl der am Markt erhältlichen Systeme getroffen:
• PostgreSQL [Pos09a], Version 8.4.0
• Oracle [Ora09a] XE, Version 10.2.0.1
• DB2 [IBM09a] Express-C, Version 9.70
Die Anbindung dieser Systeme an die JVM wird mit der Java™ Database Connectivity
(JDBC) [Sun09b] realisiert. Nachdem ein geeignetes Datenbanklayout gefunden wurde,
können die Daten aus Schemata und Instanzen über die JDBC-Verbindung in den Tabellen des RDBMS gespeichert werden. Beim Laden werden die Daten über die JDBCVerbindung abgerufen und zu Java™-Objekten zusammengefasst.
Als letzte hier behandelte Möglichkeit, bietet sich noch die Java™ Persistence API (JPA)
zur Persistierung von Daten an. Dies ist die Definition einer Schnittstelle, welche die Möglichkeit bietet, Java™-Objekte als Ganzes in relationale Datenbanksysteme zu speichern
und daraus zu laden. Hierzu sind Annotationen [GJSB05] an die Primärspeicherklassen
notwendig, aus welchen die JPA-Implementierung ein Datenbanklayout generiert. Das
Speichern und Laden geschieht mithilfe von Reflection [Sun09d]. Als Implementierung
von JPA wurde im Rahmen dieser Arbeit Hibernate [Red09] gewählt. Hierbei wurden
folgende Versionen benutzt: Hibernate 3.3.2GA, Hibernate Annotations 3.4.0GA und
Hibernate EntityManager 3.4.0GA.
4.3.2 Schnittstellen zwischen Primär- und Sekundärspeicher
Bei den vorgestellten Varianten zur Speicherung im Sekundärspeicher entsteht das Problem, dass die Grenzen zwischen Primär- und Sekundärspeicher verschwimmen. Dies
60
4.3 Sekundärspeicher
liegt daran, dass die Objekte, welche die Speicherung implementieren, ebenfalls im Primärspeicher liegen und hier gegebenenfalls Daten zwischenspeichern: RDBMS führen im
Allgemeinen Primärspeichercaches und die Daten werden von JDBC im Primärspeicher
transportiert. Das gleiche Bild ergibt sich bei JPA: Bei einer solchen Implementierung
fällt zusätzlicher Aufwand im Primärspeicher an, um Anfragen beantworten zu können.
Trotzdem kann die Workflow-Testumgebung nicht auf diese Daten zugreifen.
Um dieses Problem handhaben zu können, werden die Begriffe Primärspeicher und Sekundärspeicher für die folgenden Betrachtungen präzisiert:
• Daten im Primärspeicher sind diejenigen Daten, auf welche von der WorkflowTestumgebung zugegriffen werden kann
• Zum Sekundärspeicher gehören alle Daten, welche in einem RDBMS, im Dateisystem oder per JPA gespeichert sind und Daten, die im Hauptspeicher zur Verwaltung dieser Daten dienen
Hierbei haben die Begriffe Primärspeicher und Sekundärspeicher fortan nicht länger eine
Bindung an die physischen Speicherschichten aus Abbildung 16.
Primärspeicher
Sekundärspeicher
Tests
TemplateStorage
versch. Implementierungen
Templates
RDBMS
Dateisystem
Instances
Cluster
InstanceStorage
versch. Implementierungen
DeltaLayer
Abbildung 41: Speichertrennung anhand von Storage-Schnittstellen
Um diese Grenze zu verdeutlichen, werden so genannte Storage-Schnittstellen eingeführt. Diese bilden die Trennung zwischen Workflow-Testumgebung und der persistenten Datenhaltung. Es gibt für die Speicherung von Schemata und Instanzen unterschiedliche Storage-Schnittstellen (vergleiche Abbildung 41): TemplateStorage, respektive InstanceStorage. Diese werden mehrmals implementiert, um die aufgezeigten Realisierungsalternativen abzubilden.
4.3.3 Schemata
Schemata können im Sekundärspeicher auf verschieden Arten repräsentiert werden. Es
werden nacheinander eine Speicherung als XML-Dateien und in RDBMS mit expliziter
und impliziter Blockstruktur betrachtet. Diesen Abschnitt schließt die Betrachtung der
61
4 Umsetzung
Speicherung mithilfe von JPA ab. Jede dieser Varianten besitzt eine Implementierung
der TemplateStorage-Schnittstelle.
Primärspeicher
Sekundärspeicher
TemplateStorage
Template
ExplicitTemplate
ImplicitTemplate
RDBMS explizit
RDBMS implizit
XML-Datei
JPA
Abbildung 42: Schemarepräsentationen im Primär- und Sekundärspeicher
Durch die Einführung der Template-Schnittstelle ist der Zugriff auf die Primärspeicherdaten unabhängig von der Sekundärspeicherrepräsentation (vergleiche Abbildung 42). Es
ist also möglich, dass ein Schema im Primärspeicher mit impliziter Blockstruktur vorliegt,
der Sekundärspeicher die Daten allerdings mit einer expliziten Blockstruktur speichert.
Die Implementierung des Sekundärspeichers ruft hierbei die einzelnen Blöcke von der Primärspeicherrepräsentation ab und speichert diese. Gleiches gilt im gegensätzlichen Fall:
Es ist möglich, dass die Daten im Primärspeicher mit expliziter Blockinformation gehalten werden, im Sekundärspeicher allerdings implizit gespeichert werden. Hierbei ruft
die Sekundärspeicherimplementierung die Knoten- und Kantenmenge sowie die Menge
der topologischen IDs als flache Mengen von der Primärspeicherrepräsentation ab und
speichert diese.
Dies gilt nicht beim Einsatz von JPA als Sekundärspeichervariante. Da JPA die Primärspeicherobjekte als Ganzes speichert, befinden sich die Daten im Sekundärspeicher auch
in der gleichen Repräsentation wie im Primärspeicher.
Die TemplateStorage-Schnittstelle (siehe Abbildung 43) muss neben dem Laden, Speichern und Erzeugen von Schemata auch die Abfrage von einzelnen Blöcken eines
Schemas unterstützen. Dies wird benötigt, um das dynamische Nachladen zu ermöglichen, welches die Primärspeicherrepräsentation ExplicitTemplate benutzt. Ebenfalls ist es nötig, die Zuordnung von Knoten-IDs zu Block-IDs abrufen zu können
(getBlockInformation), da dies ebenfalls von der Primärspeicherrepräsentation benötigt wird, um den zirkulären Bezug beim Auflösen einer Knoten-ID zu einer Block-ID
zu beheben (siehe Abschnitt 4.2.2.2). Um die Implementierung zu initialisieren ist die
Methode init(Database db) und zur Bereinigung cleanup vorhanden. Ebenso benötigt
werden Methoden, die einzelne Knoten, alle Knoten, alle Kanten und alle topologischen
IDs abrufen. Für die in diesem Abschnitt folgenden Varianten zur Sekundärspeicherrepräsentation wird jeweils untersucht, in wie fern sie das dynamische Nachladen von
Blöcken unterstützen.
62
4.3 Sekundärspeicher
TemplateStorage
getTemplate(Template t, int templateID)
getNode(Template t, int nodeID)
getBlock(Template t, int blockID)
getAllNodes(Template t)
getAllEdges(Template t)
getAllTopIDs(Template t)
getBlockInformation(Template t)
createTemplate(Template t)
storeTemplate(Template t)
storeBlock(Block b)
init(Database db)
cleanup()
Abbildung 43: TemplateStorage-Schnittstelle
In Abschnitt 3.4.1 wurde festgestellt, dass die topologischen IDs der Knoten vom Schema
verwaltet werden müssen, da sich diese bei Einsatz einer Deltaschicht auch bei Knoten
ändern, die selbst nicht geändert wurden. Bei der Implementierung des Sekundärspeichers
für Schemata kann hiervon allerdings abgesehen werden, da diese nur Schemata und
keine Deltaschichten speichert und somit keine Überlagerung stattfindet. Hier können
die topologischen IDs bei den Knoten gespeichert werden.
TemplateSerialization
serializeTemplateExplicit(Template t)
serializeTemplateImplicit(Template t)
deserializeTemplate(String s)
serializeBlock(Block b)
deserializeBlock(String s)
deserializeBlockFromTemplate(String s)
deserializeNode(String s)
Abbildung 44: TemplateSerialization-Schnittstelle
Die in Abbildung 44 dargestellte Schnittstelle TemplateSerialization erlaubt das Serialisieren und Deserialisieren von einzelnen Blöcken und ganzen Schemata. Ebenso können
einzelne Knoten, welche aus einer serialisierten Zeichenkette extrahiert wurden, deserialisiert werden. Für diese Schnittstelle existiert XMLTemplateSerialization als implementierende Klasse, welche XML zur De-/Serialisierung benutzt. Hierbei gibt es eine
XML-Repräsentation mit expliziter und eine mit impliziter Blockstruktur. Die explizite
besitzt hierbei einen <template>-Tag, welcher neben der ID des Schemas (<id>-Tag)
mehrere <block>-Tags beinhaltet. Diese sind aus den Knoten (<node>-Tags) und Kanten (<edge>-Tags) und der jeweiligen ID (<id>-Tag) der einzelnen Blöcke aufgebaut.
Die implizite Repräsentation beinhaltet keine <block>-Tags, sondern nur <node>- und
<edge>-Tags. Die Unterscheidung zwischen expliziter und impliziter Repräsentation wird
durch ein Attribut des umschließenden <template>-Tags getroffen. Dieser besitzt das
63
4 Umsetzung
Attribut type, welches entweder explicit oder implicit ist. Dies ist in Abbildung 45
beispielhaft illustriert.
0
2
logische Sicht
3
Start
5
2
End
physische Sicht (XML)
4
explizit
<template type="explicit">
<id>1</id>
<block>
<id>0</id>
<node>
<id>0</id>
<topid>0</topid>
<type>NORMAL</type>
<split>0</split>
<branch>0</branch>
</node>
<node>
<id>1</id>
<topid>6</topid>
<type>NORMAL</type>
<split>0</split>
<branch>0</branch>
</node>
<egde><from>0</from><to>2</to></edge>
<egde><from>5</from><to>1</to></edge>
</block>
<block>
<id>2</id>
<node>
<id>2</id>
<topid>1</topid>
<type>AND_SPLIT</type>
<split>0</split>
<branch>0</branch>
<join>5</join>
</node>
<node>
<id>3</id>
<topid>2</topid>
<type>NORMAL</type>
<split>2</split>
<branch>0</branch>
</node>
<node>
<id>4</id>
<topid>3</topid>
<type>NORMAL</type>
<split>2</split>
<branch>1</branch>
</node>
<node>
<id>5</id>
<topid>4</topid>
<type>AND_JOIN</type>
<split>0</split>
<branch>0</branch>
<correspondingsplit>2</correspondingsplit>
</node>
<egde><from>0</from><to>2</to></edge>
<egde><from>2</from><to>3</to></edge>
<egde><from>2</from><to>4</to></edge>
<egde><from>3</from><to>5</to></edge>
<egde><from>4</from><to>5</to></edge>
<egde><from>5</from><to>1</to></edge>
</block>
</template>
</template>
implizit
<template type="implicit">
<id>1</id>
<node>
<id>0</id>
<topid>0</topid>
<type>NORMAL</type>
<split>0</split>
<branch>0</branch>
</node>
<node>
<id>1</id>
<topid>6</topid>
<type>NORMAL</type>
<split>0</split>
<branch>0</branch>
</node>
<node>
<id>2</id>
<topid>1</topid>
<type>AND_SPLIT</type>
<split>0</split>
<branch>0</branch>
<join>5</join>
</node>
<node>
<id>3</id>
<topid>2</topid>
<type>NORMAL</type>
<split>2</split>
<branch>0</branch>
</node>
<node>
<id>4</id>
<topid>3</topid>
<type>NORMAL</type>
<split>2</split>
<branch>1</branch>
</node>
<node>
<id>5</id>
<topid>4</topid>
<type>AND_JOIN</type>
<split>0</split>
<branch>0</branch>
<correspondingsplit>2</correspondingsplit>
</node>
<egde><from>0</from><to>2</to></edge>
<egde><from>2</from><to>3</to></edge>
<egde><from>2</from><to>4</to></edge>
<egde><from>3</from><to>5</to></edge>
<egde><from>4</from><to>5</to></edge>
<egde><from>5</from><to>1</to></edge>
</template>
Abbildung 45: XML-Repräsentationen von Schemata
4.3.3.1 XML mit expliziter Blockstruktur Die vorgestellte Serialisierung von Schemata als XML mit expliziter Blockstruktur lässt sich zur Persistierung der Daten benutzen.
64
4.3 Sekundärspeicher
Hierzu wird diese in einer Datei gespeichert, wobei der Dateiname aus der ID des Schemas
gebildet wird. Dies ist sinnvoll, da für eine Deserialisierung die ID des Schemas vorhanden ist (vergleiche TemplateStorage-Schnittstelle) und der zugehörige Dateiname somit
in konstanter Zeit bestimmt werden kann.
Dynamisches Nachladen einzelner Blöcke aus Schemata wird von dieser Sekundärspeicherrepräsentation nicht effizient unterstützt, da die Informationen zusammen mit allen
anderen Daten des Schemas in einer einzigen Datei vorliegen. Hierbei muss zumindest
ein Teil der nicht benötigten Daten geparst werden: Kommt ein SAX-Parser [MB02] zum
Einsatz, so kann nach dem Auffinden des zu ladenden Blocks abgebrochen werden, nachdem dieser deserialisiert wurde. Für die Anfrage nach der Zuordnung der Knoten-IDs zu
den Block-IDs muss allerdings das ganze Schema geparst werden, da alle Knoten einzeln
untersucht werden müssen.
4.3.3.2 XML mit impliziter Blockstruktur Analog zur Speicherung mit einer expliziten Blockstruktur, wird auch bei der Speicherung mit einer impliziten Blockstruktur
ein Schema von XMLTemplateSerialization serialisiert und anschließend in eine Datei gespeichert. Diese hat ebenfalls einen Dateinamen, welcher aus der ID des Schemas
besteht.
Das dynamische Nachladen von Blöcken wird hierbei nicht effizient unterstützt: Es muss
jeweils das ganze Schema deserialisiert werden und der angefragte Block identifiziert und
zurückgeliefert werden. Gleiches gilt für die Anfrage nach der Auflösung von Knoten-ID
nach Block-ID.
4.3.3.3 RDBMS mit expliziter Blockstruktur Die explizite Blockstruktur aus Abschnitt 3.2.3.1 lässt sich im Sekundärspeicher auch mithilfe von RDBMS umsetzen. Ziel ist es, alle Informationen zusammen zu speichern, welche zu einem
Block gehören. Hierbei lassen sich die Daten serialisiert speichern und die von der
TemplateSerialization-Schnittstelle definierten Methoden wiederverwenden: Es existiert eine Tabelle Block, welche die ID des Blocks und den ganzen Block in serialisierter
Form vorhält. Die Serialisierung wird in einer Spalte gespeichert, welche zeichenbasierte große Objekte („Character Large Object“, „CLOB“ [IBM09b, Ora09b] bzw. „TEXT“
[Pos09b]) halten kann.
Zusätzlich zur Tabelle Block existiert eine Tabelle Template, welche in einer 1:nBeziehung zu Block steht. Diese bildet auf logischer Ebene ein vollständiges Schema
ab und wird für die Vollständigkeit der Repräsentation benötigt.
Diese Repräsentation von Schemata im Sekundärspeicher unterstützt das dynamische
Nachladen von Blöcken effizient, da die gewünschten Informationen direkt zugänglich
vorliegen. Um der Primärspeicherrepräsentation allerdings für die Knoten die zugeordneten Block-IDs liefern zu können, wird eine weitere Tabelle (BlockInfo) benötigt. Wäre
diese nicht vorhanden, so müsste bei einer entsprechenden Anfrage das ganze Schema
65
4 Umsetzung
geladen und somit alle Blöcke deserialisiert werden. Da dies unverhältnismäßig und eine
effizientere Lösung mit wenig Aufwand realisierbar ist, hält die Tabelle BlockInfo die
benötigten Informationen.
Block
Template
1
n
ID INTEGER PRIMARY KEY
content CLOB
ID INTEGER PRIMARY KEY
1
BlockInfo
n
blockID INTEGER PRIMARY KEY
nodeID INTEGER PRIMARY KEY
Abbildung 46: Schemata mit expliziter Blockstruktur im RDBMS
Neben der vorgestellten Repräsentation ist eine zweite möglich. Die eingesetzten RDBMS
unterstützen das Speichern von XML-Daten mit einem gesonderten XML-Datentyp, also
nicht CLOB. Dies hat den Vorteil, dass an das RDBMS gestellte Anfragen auch auf
Daten zugreifen können, welche in einer Spalte mit serialisierten Daten persistiert sind.
Somit wird die Tabelle BlockInfo überflüssig. Diese zweite Repräsentation mit expliziter
Blockstruktur ist in Abbildung 47 dargestellt.
Block
Template
ID INTEGER PRIMARY KEY
1
n
ID INTEGER PRIMARY KEY
content XML
Abbildung 47: Schemata mit expliziter Blockstruktur in XML im RDBMS
4.3.3.4 RDBMS mit impliziter Blockstruktur Bei einer impliziten Realisierung der
Blockstruktur werden analog zur Primärspeicherrepräsentation aus Abschnitt 4.2.2 alle
Knoten in einer Tabelle Node gehalten. Diese stehen von einer Tabelle Template ausgehend in einer 1:n-Beziehung. Gleiches gilt für die Tabelle Edges, welche die Kanten eines
Schemas beinhaltet. Dies ist in Abbildung 48 dargestellt.
Das dynamische Nachladen von Blöcken wird von dieser Sekundärspeicherrepräsentation
unterstützt. Hierfür werden bei einer Anfrage nach einem Block die zugehörigen Knoten
aus der Tabelle Node und die benötigten Kanten aus der Tabelle Edges geladen und zu
einem Block-Objekt zusammengefasst. Die Auflösung von Knoten-IDs zu Block-IDs ist
durch eine Abfrage der Spalten ID und splitNodeID der Tabelle Node möglich.
66
4.3 Sekundärspeicher
Node
Template
1
ID INTEGER PRIMARY KEY
n
ID INTEGER PRIMARY KEY
branchID INTEGER
splitNodeID INTEGER
nodeType INTEGER
topologicalID INTEGER
referencedID INTEGER
name VARCHAR
1
1
to
from
n
n
Edge
Abbildung 48: Schemata mit impliziter Blockstruktur im RDBMS
4.3.3.5 JPA Bei einer Speicherung der Daten mithilfe von JPA werden die im Primärspeicher vorhandenen, zu persistierenden Klassen mit Annotationen versehen. Aus diesen generiert die Implementierung von JPA, ein Datenbanklayout. Da im Primärspeicher
zwei Repräsentationen von Schemata verfügbar sind (vergleiche Abschnitt 4.2.2), existieren auch für JPA diese beiden Repräsentationen: Schemata mit expliziter Blockstruktur
und mit impliziter.
Die explizite Repräsentation aus Abbildung 49 ist vergleichbar mit der Speicherung in einem RDBMS mit expliziter Blockstruktur: Ein Schema enthält mehrere
Blöcke und diese enthalten die Knoten, Kanten und topologischen IDs in serialisierter Form als zeichenbasierte große Objekte. Hierbei geschieht die Serialisierung über
die Externalizable-Schnittstelle von Java™ . Es kann keine Implementierung von
TemplateSerialization zur Serialisierung herangezogen werden, da JPA die Serialisierung intern steuert und die in Java™ mitgelieferte Serialization-API anspricht, welche
durch Externalizable gesteuert werden kann. Dies hat zur Folge, dass im RDBMS,
nicht pures XML gespeichert wird, sondern dieses noch einen Serialisations-Header besitzt. Dieser beinhaltet eine Beschreibung der serialisierten Klasse.
Das dynamische Nachladen von Blöcken wird bei Einsatz der expliziten Blockstruktur
unterstützt. Hierbei kommt die von JPA definierte Query Language [Sun06] zum Einsatz,
welche ähnlich zu SQL [ISO92] ist. Mithilfe dieser Sprache ist es möglich, einzelne Block
-Objekte zu laden. Hierbei ist die Auflösung von Knoten-IDs zu Block-IDs allerdings nur
durch Laden aller Blöcke zu erreichen, da die Knoteninformationen in der Tabelle Block
serialisiert gespeichert werden.
Block
Template
ID INTEGER PRIMARY KEY
1
n ID INTEGER PRIMARY KEY
edges CLOB
nodes CLOB
topologicalIDs CLOB
Abbildung 49: Schemata mit expliziter Blockstruktur mit JPA
Wie bei der Speicherung in einem RDBMS mit impliziter Blockstruktur, repräsentiert
auch die JPA-Variante mit impliziter Blockstruktur ein Schema durch eine Knotenmenge, welche in einer separaten Tabelle gespeichert wird (vergleiche Abbildung 50). Die
67
4 Umsetzung
Kantenmenge und die Menge der topologischen IDs hingegen werden hier serialisiert gespeichert, da JPA keine Unterstützung bietet, eine Map, welche als Wert ein Set oder nur
eine Zahl hat, in einer eigenen Tabelle darzustellen.
Node
Template
1
n ID INTEGER PRIMARY KEY
branchID INTEGER
splitNodeID INTEGER
type INTEGER
correspondingSplitID INTEGER
joinNodeID INTEGER
name VARCHAR
ID INTEGER PRIMARY KEY
edges CLOB
topologicalIDs CLOB
Abbildung 50: Schemata mit impliziter Blockstruktur mit JPA
Durch Einsatz der Query Language von JPA lassen sich bei der impliziten Realisierung
die Knoten leicht bestimmen, welche zu einem bestimmten Block gehören, da die SplitKnoten-ID als Feld in der Tabelle Node vorhanden ist. Die Zuordnung der Kanten und
der topologischen IDs gestaltet sich allerdings schwierig, da diese als zeichenbasiert große
Objekte in der Tabelle Template vorliegen. Hierfür muss somit das ganze Schema geladen
werden. Für die Auflösung von Knoten-ID zu Block-ID muss das entsprechende KnotenObjekt eingelagert werden, was mit der JPA Query Language möglich ist. Die Block-ID
liegt dann in Form der Split-Knoten-ID des Node-Objekts vor.
4.3.4 Instanzen
Wie bei Schemata ergeben sich für Instanzen verschiedene Repräsentationen im Sekundärspeicher. Implementierungen zur Speicherung der Daten als XML-Dateien, in einem
RDBMS und eine Realisierung mithilfe von JPA existieren als Implementierungen der
InstanceStorage-Schnittstelle, welche in Abbildung 51 dargestellt ist.
InstanceStorage
getInstance(Instance i, int id)
createInstance(Instance i)
storeInstance(Instance i)
init(Database db)
cleanup()
Abbildung 51: InstanceStorage-Schnittstelle
Alle Implementierungen von InstanceStorage benötigen hierzu eine TemplateStorage.
Von einer Instanz kann direkt das zugehörige Schema abgerufen werden. Deshalb wird
zum Beispiel beim Laden einer Instanz das entsprechende Objekt benötigt, welches von
der zur Verfügung stehenden TemplateStorage geladen wird. Eine weitere Möglichkeit
besteht darin, das korrekte Schema beim Laden einer Instanz der Methode zu übergeben, womit ein erneutes Laden umgangen wird. Die InstanceStorage-Schnittstelle stellt
Methoden zur Verfügung um Instanzen zu laden, speichern oder neu anzulegen.
68
4.3 Sekundärspeicher
Analog zu Schemata ist es aufgrund der Einführung der Instance-Schnittstelle auch bei
Instanzen möglich, diese in einer Repräsentation mit expliziten Knotenzuständen im Primärspeicher zu halten, während im Sekundärspeicher eine Repräsentation mit impliziten
Knotenzuständen vorliegt. Ebenso ist es möglich, die Instanz im Primärspeicher implizit und im Sekundärspeicher explizit zu halten. Wie auch schon bei Schemata gilt auch
für Instanzen bei Einsatz von JPA als Sekundärspeichervariante, dass die Sekundärspeicherrepräsentation immer der des Primärspeichers entspricht. Abbildung 52 illustriert
dies.
Primärspeicher
Sekundärspeicher
InstanceStorage
Instance
ExplicitInstance
ImplicitInstance
RDBMS explizit
RDBMS implizit
XML-Datei
JPA
Abbildung 52: Instanzrepräsentation im Primär- und Sekundärspeicher
Für die Serialisierung von Instanzen sorgt, analog zu TemplateSerialization für Schemata, die Schnittstelle InstanceSerialization (vergleiche Abbildung 53). Diese bietet
die Möglichkeit, eine Instanz oder eine Menge von Knotenzuständen zu de-/serialisieren.
Es existiert mit XMLInstanceSerialization eine Implementierung, welche XML zur
De-/Serialisierung benutzt. Hierbei stehen wiederum Methoden für eine explizite und eine implizite XML-Repräsentation zur Verfügung. Die explizite Repräsentation beinhaltet
in einem <instance>-Tag die ID der Instanz (<id>-Tag) und die des zugrunde liegenden
Schemas (<templateid>-Tag). Zusätzlich werden die Knotenzustände aller Knoten innerhalb von <nodestate>-Tags gehalten. Diese beinhalten die Knoten-ID (<node>-Tag)
und den zugehörigen Knotenzustand (<state>-Tag).
InstanceSerialization
serializeInstanceExplicit(Instance i)
serializeInstanceImplicit(Instance i)
deserializeInstance(String s)
serializeNodeStatesExplicit(Map<int, State> nodeStates)
serializeNodeStatesImplicit(Set<int> nodesRunning, Set<int> nodesSkipped)
deserializeNodeStatesExplicit(String s)
deserializeNodeStatesImplicit(String s)
Abbildung 53: InstanceSerialization-Schnittstelle
Die implizite XML-Repräsentation gleicht der expliziten, nur dass nicht für alle Knoten
ein Knotenzustand vorhanden ist. Es werden nur RUNNING und SKIPPED Zustände
gespeichert. Die Unterscheidung der beiden Repräsentationen findet durch ein Attribut
69
4 Umsetzung
logische Sicht
des umschließenden <instance>-Tags statt: Dieser besitzt ein Attribut type welches als
Wert entweder explicit oder implicit besitzt. Dies ist in Abbildung 54 dargestellt.
Start
2
3
-
physische Sicht (XML)
explizit
<instance type="explicit">
<id>1</id>
<templateid>1</templateid>
<nodestate>
<node>0</node>
<state>COMPLETED</state>
</nodestate>
<nodestate>
<node>2</node>
<state>COMPLETED</state>
</nodestate>
<nodestate>
<node>3</node>
<state>RUNNING</state>
</nodestate>
<nodestate>
<node>4</node>
<state>NOT_ACTIVATED</state>
</nodestate>
<nodestate>
<node>1</node>
<state>NOT_ACTIVATED</state>
</nodestate>
</instance>
E+,
RUNNING
COMPLETED
SKIPPED
NOT_ACTIVATED
implizit
<instance type="implcit">
<id>1</id>
<templateid>1</templateid>
<nodestate>
<node>3</node>
<state>RUNNING</state>
</nodestate>
</instance>
Abbildung 54: XML-Repräsentationen von Instanzen
4.3.4.1 XML mit expliziten Knotenzuständen Analog zu den XML-Repräsentationen
von Schemata wird auch die explizite XML-Repräsentation für eine Instanz von
XMLInstanceSerialization erzeugt. Diese Serialisierung wird dann in einer Datei mit
der Instanz-ID als Dateinamen gespeichert. Dies ist sinnvoll, da hiermit Anfragen, eine
Instanz auf Basis ihrer ID zurück zu liefern, am schnellsten beantwortet werden können.
4.3.4.2 XML mit impliziten Knotenzuständen Für die implizite XML-Repräsentation
von Instanzen gilt ebenso, dass die Instanzen von XMLInstanceSerialization in eine
Repräsentation mit impliziten Knotenzuständen de-/serialisiert und in Dateien gespeichert werden. Hierbei kommt ebenfalls die Instanz-ID als Dateiname zum Einsatz.
4.3.4.3 RDBMS mit expliziten Knotenzuständen Bei der Speicherung einer Instanz
mit expliziten Knotenzuständen in einem RDBMS ist es sinnvoll, die Knotenzustände
in serialisierter Form zu halten, da dies für jeden Knoten nur ein zugeordneter Wert
ist. Eine zusätzliche Tabelle hierfür ist nicht sinnvoll. Somit werden bei einer expliziten
Repräsentation alle Knotenzustände von XMLInstanceSerialization serialisiert und in
der Spalte für zeichenbasierte große Objekte der Tabelle Instance gespeichert. Hinzu
kommen Felder für die ID der Instanz und die ID des zugrunde liegenden Schemas.
70
4.3 Sekundärspeicher
Instance
ID INTEGER PRIMARY KEY
templateID INTEGER
nodeStates CLOB
Abbildung 55: Instanzen mit expliziten Knotenzuständen im RDBMS
Auch hier ist es, analog zu in RDBMS gespeicherten Schemata mit expliziter Blockstruktur, möglich, die serialisierten Daten in einer Spalte des XML-Datentyps zu speichern.
Hierdurch sind Abfragen auch auf die serialisierten Daten möglich.
Instance
ID INTEGER PRIMARY KEY
templateID INTEGER
nodeStates ./0
Abbildung 56: Instanzen mit expl. Knotenzuständen in XML im RDBMS
4.3.4.4 RDBMS mit impliziten Knotenzuständen Wie schon für die Speicherung
in einem RDBMS mit expliziten Knotenzuständen ist es auch bei dieser Variante zu aufwändig, eine eigene Tabelle für die impliziten Knotenzustände zu erstellen. Diese werden auch in serialisierter XML-Form gehalten, deren Umwandlung von
XMLInstanceSerialization zur Verfügung gestellt wird. Hinzu kommen Felder für die
ID der Instanz und die ID des zugrunde liegenden Schemas, wie Abbildung 57 zeigt.
Instance
ID INTEGER PRIMARY KEY
templateID INTEGER
nodesStatesImplicit CLOB
Abbildung 57: Instanzen mit impliziten Knotenzuständen im RDBMS
4.3.4.5 JPA Analog zu den Betrachtungen aus Abschnitt 4.3.4.3 und 4.3.4.4 wurden
die Annotationen für eine Speicherung mithilfe von JPA so gewählt, dass die beiden zur
Verfügung stehenden Primärspeicherrepräsentationen aus Abschnitt 4.2.4 im Sekundärspeicher mithilfe von serialisierten Knotenzustandsmengen repräsentiert werden. Hierbei
ist, analog zur JPA-Repräsentation von Schemata, zu beachten, dass die Serialisierung
durch die Implementierung der Externalizable-Schnittstelle realisiert wird, da JPA die
von Java™ zur Verfügung gestellte Serialization-API verwendet und eine Implementierung
von InstanceSerialization nicht benutzt werden kann.
Instance
ID INTEGER PRIMARY KEY
templateID INTEGER
nodeStates CLOB
Abbildung 58: Instanzen mit expliziten Knotenzuständen mit JPA
71
4 Umsetzung
Instance
ID INTEGER PRIMARY KEY
templateID INTEGER
nodesR1nnin2 3456
nodes78ipped CLOB
Abbildung 59: Instanzen mit impliziten Knotenzuständen mit JPA
4.3.5 Zwischenspeicher
Es ist unter Umständen sinnvoll, die Implementierungen der SekundärspeicherSchnittstellen mit Zwischenspeichern (Caches) im Hauptspeicher zu versehen. Für die
restliche Workflow-Testumgebung soll dies transparent geschehen, deshalb bieten sich
Implementierungen von TemplateStorage und InstanceStroage an. Diese, als Caching
Storage bezeichneten Klassen, greifen auf eine der bereits vorgestellten Implementierungen zurück, um die Entitäten persistent zu speichern. Bei einem Abruf der Entität wird
diese aus der zugrunde liegenden Implementierung abgerufen und zwischengespeichert.
Bei einer erneuten Anfrage nach dieser Entität wird diejenige aus dem Zwischenspeicher
zurückgeliefert und keine neue von der zugrunde liegenden Implementierung angefragt.
Dies spart den Zugriff auf das RDBMS oder das Dateisystem und beschleunigt somit den
Vorgang. Abbildung 60 illustriert dies.
Primärspeicher
Sekundärspeicher
CachingStorage
Tests
TemplateStorage
versch. Implementierungen
RDBMS
InstanceStorage
Dateisystem
versch. Implementierungen
CachingStorage
Abbildung 60: Caching Storage
Es ist sinnvoll, die Größe des Zwischenspeichers zu begrenzen, da der Hauptspeicher
(vergleiche Abbildung 16) im Extremfall sonst vollständig mit Schema- und Instanzdaten
gefüllt ist. Dieser wird allerdings zum Beispiel auch für die Programmausführung benötigt
und sollte deshalb nicht vollständig mit diesen Daten gefüllt werden. Aufgrund dessen
bietet sich ein so genannter Least-Recently-Used-Cache (LRU-Cache) [OOW93] an. Hat
dieser seinen maximalen Füllstand erreicht, wird die am längsten nicht mehr benötigte
Entität aus dem Zwischenspeicher gelöscht. Sollte diese später wieder angefragt werden,
so wird diese erneut durch die der Caching Storage zugrunde liegende Implementierung
geladen und wieder in den Zwischenspeicher eingefügt.
72
4.4 Anfragen auf Kollektionen
Wird die Caching Storage angewiesen, eine Entität zu speichern, muss diese im Zwischenspeicher invalidiert und die zugrunde liegende Implementierung angewiesen werden, die
neue Entität zu persistieren.
Aufgrund der Definitionen aus Abschnitt 4.3.2 zählt auch eine Caching Storage korrekterweise zum Sekundärspeicher.
4.3.6 Flüchtiger Speicher
Um auch Messungen durchführen zu können, welche ausschließlich auf den Hauptspeicher
zugreifen, bieten sich weitere Implementierungen der Schnittstelle TemplateStorage und
InstanceStorage an. Hierdurch kann auch der flüchtige Speicher transparent
von der Workflow-Testumgebung angesprochen werden. Die Implementierungen der
Storage-Schnittstellen, welche Transient Storage genannt werden, benutzen StandardDatenstrukturen von Java™ zur Verwaltung der Daten im Hauptspeicher. Hierbei ist es
nicht sinnvoll, eine Transient Storage mit einer Caching Storage zu verbinden, da beide
die Daten im Hauptspeicher halten. Hier reicht eine Transient Storage aus.
Auch eine Transient Storage gehört nach Abschnitt 4.3.2 korrekterweise zum Sekundärspeicher.
4.3.7 Zusammenfassung
Es wurden verschiedene Realisierungsvarianten zur Speicherung von Schemata und Instanzen im Sekundärspeicher vorgestellt. Nachdem die Begriffe Primär- und Sekundärspeicher aufgrund von Ungenauigkeiten präzisiert wurden, folgte die Erläuterung der
verschiedenen Repräsentationen. Hierbei wurden XML-Serialisierungen vorgestellt und
Möglichkeiten aufgezeigt, die Entitäten in RDBMS und mit Hilfe von JPA zu persistieren. Aufbauend hierauf wurden Caching Storages und Transient Storages eingeführt.
Mithilfe dieser Ergebnisse ist es nun möglich, einen Prozess vollständig in Primär- und
Sekundärspeicher zu beschreiben. Dies umfasst die grundlegenden Betrachtungen aus
Kapitel 2 und die hierauf basierenden Konzepte aus Kapitel 3.
4.4 Anfragen auf Kollektionen
Zum Abschluss der Umsetzung fehlt noch die Eingliederung der Anfragen auf Kollektionen in die zuvor ausgearbeitete Architektur der Workflow-Testumgebung. Dies wird in
diesem Abschnitt erläutert.
73
4 Umsetzung
4.4.1 Instanzen eines Schemas
Um die Auslastung der Prozesslinien (vergleiche Abbildung 15) abschätzen zu können,
ist es hilfreich, die Instanzen eines bestimmten Schemas abrufen zu können. Hierfür wird
eine Methode benötigt, welche die entsprechenden Instanzen finden kann. Diese muss
Bestandteil der InstanceStorage-Schnittstelle werden, da die Implementierungen von
diesem über alle nötigen Informationen verfügen.
Um diese Anfrage zu beantworten, benötigen die Implementierungen, welche XML benutzen lineare Laufzeit auf der Anzahl der Instanzen. Es müssen alle verfügbaren Dateien
im Dateisystem untersucht werden, ob die jeweilige Instanz zum angegebenen Schema
gehört. Dies ist sehr aufwändig.
Die Implementierungen auf Basis von RDBMS können diese Anfrage jedoch effizient
beantworten. Hier steht die Anfragesprache SQL zur Verfügung, mit der Abfragen auf die
templateID-Spalte der Tabelle Instance möglich ist. Diese Spalte ist in allen RDBMSRepräsentationen von Instanzen vorhanden.
Auch die Implementierung mit JPA bietet eine performante Beantwortung der Anfrage.
Die JPA Query Language bietet die Möglichkeit, die Instanzen aufgrund der ID eines
Schemas zu laden.
4.4.2 Instanzen mit bestimmten Knotenzustand
Um die Auslastung einzelner Abteilungen eines Unternehmens einschätzen zu können,
ist die Abfrage nach Instanzen eines Schemas mit einem bestimmten Knoten in einem
bestimmten Zustand hilfreich (vergleiche Abschnitt 2.4.3). Auch diese Anfrage muss von
InstanceStorage abgedeckt werden.
Wie bei der Abfrage nach den Instanzen eines Schemas können die XMLImplementierungen auch diese Anfrage nicht performant beantworten. Es müssen ebenso
alle Instanzen untersucht werden.
Bei Instanzen, welche in einem RDBMS gespeichert sind, werden die Knotenzustände als
ein zeichenbasiert großes Objekt gespeichert. Dies hat zur Folge, dass hier nicht effizient
mithilfe von SQL selektiert werden kann. Es müssen ebenfalls alle Instanzen geladen und
untersucht werden.
Hier schafft der Datentyp XML der eingesetzten RDBMS Abhilfe. Mithilfe von diesem
können Anfragen auch auf diejenigen Spalten gestellt werden, in denen Daten in serialisierter Form vorliegen. Die zu stellenden Anfragen sind hierbei abhängig vom RDBMS, da
diese keinen einheitlichen Standard implementieren. Der Einsatz eines XPath-Ausdrucks
[W3C99] zur Selektion der Daten aus der gespeicherten XML-Zeichenkette ist allen untersuchten RDBMS allerdings gemein. Eine beispielhafte Anfrage wird in Listing 1 vorgestellt. Diese wird an DB2 gerichtet und basiert auf der Annahme, dass die Instanzen
mit expliziten Knotenzuständen vorgehalten werden (siehe Abbildung 56). Als Parameter
74
4.4 Anfragen auf Kollektionen
werden die ID des Schemas und des Knotens sowie der gesuchte Knotenzustand übergeben. Die Anfrage erzeugt über die in i.nodeStates gespeicherte XML-Zeichenkette eine
virtuelle Tabelle. Diese besitzt die Spalten nodeid und nodestate, welche durch Parsen
aller Ergebnisse der XPath Anfrage /nodestates erzeugt werden. Diese virtuelle Tabelle
kann dann im WHERE-Teil der Anfrage benutzt werden und die Selektion der Instanzen auf
die beschränkt werden, welche den entsprechenden Knoten im korrekten Zustand haben.
1
2
3
4
5
6
7
8
9
10
11
FROM
Instance AS i ,
XMLTABLE (’$d/ nodestate ’
passing i . nodeStates as " d"
COLUMNS
nodeid INTEGER PATH ’node ’,
nodestate VARCHAR (20) PATH ’state ’) AS x
WHERE
i. templateID = ? AND
x. nodeid = ? AND
x. nodestate = ?
Listing 1: Anfrage an DB2 mit Auswertung der XML-Daten
Durch den Einsatz des XML-Datentyps und der Möglichkeit, die hiermit gespeicherten
Daten in Abfragen einzubinden, kann die Anfrage effizient beantwortet werden. Sollte
die RDBMS-Repräsentation die Knotenzustände allerdings implizit vorhalten und nicht
nach dem Knotenzustand RUNNING oder SKIPPED gesucht werden, ist eine effiziente
Beantwortung auch hier nicht möglich. Der Sekundärspeicher kann die Knotenzustände
bei einer impliziten Speicherung nicht berechnen, da der hierfür notwendige Algorithmus
nur im Primärspeicher vorliegt. Hierbei ist die Umsetzung des Algorithmus in Stored
Procedures denkbar. Dies wurde allerdings im Rahmen dieser Arbeit nicht umgesetzt, da
dies den Rahmen sprengen würde.
Bei einer Umsetzung mittels JPA lässt sich diese Anfrage ebenfalls nicht performant
beantworten, da hier die Knotenzustände ebenfalls in serialisierter Form gespeichert werden. Hierbei besteht allerdings kein Zugriff auf den Datentyp der entsprechenden Spalte
im zugrunde liegenden RDBMS.
4.4.3 Zusammenfassung
Es wurde die Kompatibilität der einzelnen Sekundärspeicherrepräsentationen zu den Anfragen auf Kollektionen untersucht, wie sie in Abschnitt 2.4.3 definiert wurden. Hierbei
stellte sich heraus, dass die Anfrage nach Instanzen mit einem bestimmten Knoten in
einem bestimmten Zustand besser beantwortet werden kann, sofern die Knotenzustände
in einer Spalte mit XML-Datentyp gespeichert werden, wenn mithilfe eines RDBMS gespeichert wird. Außerdem wurde festgestellt, dass die Repräsentation von Instanzen im
Dateisystem mithilfe von XML keine performante Beantwortung der Anfragen zulässt.
75
4 Umsetzung
4.5 Zusammenfassung
In diesem Kapitel wurde die Umsetzung einer Workflow-Testumgebung auf Basis der
Grundlagen aus Kapitel 2 erläutert. Diese beinhaltet die Konzepte aus Kapitel 3. Es
wurden verschiedene Repräsentationen der Entitäten sowohl für den Primär- als auch
den Sekundärspeicher vorgestellt. Als Sekundärspeicher kommt hierbei eine Speicherung
in serialisierter Form im Dateisystem, eine Speicherung in einem RDBMS und eine Speicherung mithilfe von JPA in Frage. Abschließend wurden Anfragen auf Kollektionen und
deren Umsetzung auf Basis der verschiedenen Sekundärspeicherrepräsentationen betrachtet. Dies rundet die Umsetzung der Workflow-Testumgebung ab. Es ist eine grundlegende
Funktionalität gegeben: Es können Schemata und darauf basierende Instanzen definiert
werden. Es besteht die Möglichkeit, diese instanzbasiert zu ändern und weiterzuschalten.
Hierbei sind jeweils verschiedene Speicherrepräsentationen vorgestellt worden, welche im
Folgenden nach der qualitativen Betrachtung aus Kapitel 3 auch quantitativ untersucht
werden können.
76
5 Messungen
Nachdem die Umsetzung der Grundlagen und Konzepte sowohl in Primär- als auch in
Sekundärspeicherstrukturen vorgestellt wurde, schließt sich in diesem Kapitel die Vorstellung und Interpretation der damit durchgeführten Zeit- und Speichermessungen an.
Hierbei wird zuerst auf die Rahmenbedingungen und dann auf die Messergebnisse der
verschiedenen Sekundärspeicherrepräsentationen eingegangen. Im Anschluss folgt die
Auswertung der Messungen zur Primärspeicherrepräsentation. Das Fazit dieser Arbeit
schließt dieses Kapitel ab.
5.1 Rahmenbedingungen
In diesem Abschnitt werden die Rahmenbedingungen der Messungen vorgestellt. Zuerst
wird der Rechner beschrieben, auf dem diese durchgeführt wurden. Um die Vergleichbarkeit zu wahren, müssen die ermittelten Zeitwerte allerdings unabhängig vom Rechner
sein. Aufgrund dessen wird anschließend der in dieser Arbeit gewählte Weg beschrieben,
die Eigenschaften des Rechners aus den Messergebnissen „herauszurechnen“. Dies ist für
die gemessenen Werte bezüglich des Speicherverbrauchs nicht notwendig, da diese unabhängig vom eingesetzten Rechner sind, wenn man von der Bitbreite der Architektur
absieht (32 Bit und 64 Bit).
Der Rechner, auf dem die Messungen durchgeführt wurden, besitzt einen 32 Bit AMD
Athlon™ XP 2800+ Prozessor [Adv09]. Dieser ist mit 2083 Megahertz getaktet und greift
auf einen Hauptspeicher von 1 Gigabyte Größe zu. Um die Messungen nicht zu verfälschen
wurde kein Auslagerungsspeicher eingerichtet, der zur Erweiterung des Hauptspeichers
durch Einsatz der Festplatte dienen würde. Als Betriebssystem kommt Ubuntu Linux
[Can09] in der Version 9.04 zum Einsatz.
Um die Messergebnisse vergleichen zu können, müssen die im Folgenden vorgestellten
Zeitwerte allerdings unabhängig vom eingesetzten Rechner sein. Hierzu wird die Performanz von diesem gemessen und aus den absoluten Ergebnissen der Messungen durch
Division herausgerechnet. Dieser Grundwert des Rechners wird im Rahmen dieser Arbeit
dadurch bestimmt, dass die Dauer eines bestimmten Schleifendurchlaufs gemessen wird.
Der hierfür eingesetzte Java™ -Quelltext ist in Listing 2 vorgestellt.
1
2
3
4
5
int i = 0;
long after ;
long before = System . nanoTime () ;
while (i < 690000) i ++;
after = System . nanoTime () ;
Listing 2: Quelltext zur Performanzbestimmung
Es wird die Zeit gemessen, welche für eine 690.000 malige Erhöhung einer Zählvariable
benötigt wird. Hierfür kommt die Methode System.nanoTime() zum Einsatz, welche die
77
5 Messungen
aktuelle Systemzeit in 10−9 Sekunden liefert. Der resultierende Grundwert ist durch die
Subtraktion after-before verfügbar.
Diese Messung wurde auf dem zur Verfügung stehenden Rechner 1.000 mal direkt hintereinander durchgeführt. Es fiel auf, dass sich der resultierende Wert bei den ersten sieben
aufeinander folgenden Messungen um mehr als das 200-fache von den folgenden Werten
unterscheidet. Die Ergebnisse wurden um diese ersten sieben Werte und um „Ausreißer“
bereinigt und der Durchschnitt der restlichen Werte berechnet. Hierbei stellte sich für
den eingesetzten Rechner ein Grundwert von 1.0 Millisekunden heraus, wobei dieser auf
die erste Nachkommastelle kaufmännisch gerundet ist.
Die im Folgenden präsentierten Zeitwerte sind die absolut gemessenen Werte in Millisekunden geteilt durch den Grundwert 1.0 des eingesetzten Rechners, sofern dies nicht
anders angegeben ist.
Die kaufmännische Rundung auf die erste Nachkommastelle wurde im Allgemeinen auch
bei den absoluten Werten aller Messungen im Folgenden durchgeführt. Bei Sekundärspeichermessungen ist es meist nicht sinnvoll, die Messungen genauer anzugeben, da
in diesem Bereich „natürliche“ Schwankungen der Messergebnisse auftreten. Dies ist in
der Komplexität der Rechnersysteme begründet. Hierbei befinden sich auf der eingesetzten Maschine gleichzeitig zur Messung auch noch andere Prozesse in der Ausführung
(zum Beispiel Kernel- und Shell-Prozesse aber vor allem auch Prozesse des eingesetzten
RDBMS). Dabei kann die „Umschaltung“ zwischen den Prozessen nicht beeinflusst oder
gemessen – und somit aus dem Ergebnis herausgerechnet – werden. Die Werte sind ebenso
abhängig von eingesetzten Zwischenspeichern, welche ebenfalls nicht beeinflusst werden
können. Diese befinden sich zum Beispiel auf Software-Ebene im Kernel oder aber auch
auf Hardware-Ebene (wie zum Beispiel Prozessor-Caches). Bei den Sekundärspeichermessungen wird nur in ausgewählten Fällen eine genauere Betrachtung vorgestellt. Bei
Primärspeichermessungen sind die Werte in Tausendstel des Grundwertes angegeben, da
hierbei kein weiterer zeitintensiver Prozess die Rechenzeit des Rechners benötigt, wie das
durch die Prozesse des eingesetzten RDBMS bei Sekundärspeichertests der Fall ist. Es sei
an dieser Stelle darauf hingewiesen, dass Werte, welche im Bereich von Tausendstel des
Grundwertes liegen, zum Teil starken (relativen) Schwankungen unterliegen. Die Messergebnisse wurden dabei nach bestem Wissen und Gewissen ausgewertet, sollten allerdings
dennoch eher als grobe Richtwerte angesehen werden.
Für die Messungen wurden drei unterschiedliche Schemata eingesetzt. Zum Einen das
„einfache“ Schema, welches wenige Knoten und nur zwei Blöcke besitzt (vergleiche Abbildung 61). Auf der anderen Seite wurden zwei Schemata mit mehreren Knoten und
Blöcken eingesetzt: Im Normalfall wurde das in Abbildung 62 vorgestellte „komplexe“
Schema benutzt. Da dieses relativ unübersichtlich ist, wurde für Tests, welche auf einer
Menge von Instanzen arbeiten das „evolve“-Schema aus Abbildung 63 benutzt. Auf diesem können Instanzen leicht gleich auf die Knoten verteilt werden. Dies bedeutet, dass
für jeden Knoten des Schemas genau eine Instanz existiert, welche diesen Knoten im
Zustand RUNNING hat.
78
5.2 Sekundärspeichermessungen
3
2
Start
5
End
4
Abbildung 61: Das einfache Schema für die Messungen
6
3
4
5
7
8
9
10
11
17
14
12
13
16
15
Start
2
23
18
19
20
21
End
22
Abbildung 62: Das komplexe Schema für die Messungen
3
Start
9
4
2
7
5
10
13
8
6
15
11
12
17
14
19
16
End
18
Abbildung 63: Das „evolve“-Schema für die Messungen
5.2 Sekundärspeichermessungen
Auf Basis der vorgestellten Rahmenbedingungen werden in diesem Abschnitt die Ergebnisse von Sekundärspeichermessungen vorgestellt. Diese gliedern sich in Messungen des
Ein- und Auslagerns der Primärspeicherdaten und Anfragen auf Kollektionen. Auf diese
Aspekte wird nach einer einführenden Beschreibung der durchgeführten Messungen auf
Basis der einzelnen eingesetzten Sekundärspeichervarianten (XML, RDBMS und JPA)
eingegangen. Ziel ist es, am Ende dieses Abschnittes sowohl eine optimale Sekundärspeicherrepräsentation als auch eine optimale Speichervariante durch einen Vergleich der
einzelnen Varianten zu bestimmen.
79
5 Messungen
5.2.1 Einführung
Dieser Abschnitt beschreibt die Testfälle, mit welchen die Ergebnisse im Folgenden gemessen wurden. Hierbei werden die Testfälle des Ein- und Auslagerns von Instanzen
und Schemata betrachtet, sowie die einzelnen eingesetzten Anfragen auf Kollektionen
vorgestellt.
Das Einlagern von Instanzen erfolgt auf direktem Weg: Es wird ein Objekt der Klasse ExplicitInstance oder ImplicitInstance erzeugt und die eingesetzte Implementierung der InstanceStorage-Schnittstelle angewiesen, dieses mit den entsprechenden
Daten zu füllen. Für Schemata gilt folgende Vorgehensweise: Zuerst wird ein Objekt
der Klasse ExplicitTemplate oder ImplicitTemplate erzeugt, welches dann durch die
eingesetzte Implementierung von TemplateStorage initialisiert wird. Anschließend wird
ExplicitTemplate oder ImplicitTemplate angewiesen, alle Daten zu laden. Hierbei
werden verschiedene Methoden aus TemplateStorage aufgerufen, je nachdem ob die Daten im Sekundärspeicher explizit oder implizit vorgehalten werden.
Das Auslagern von Schemata und Instanzen ist trivial: Das Primärspeicherobjekt wird an
die Implementierung von TemplateStorage bzw. InstanceStorage übergeben. Danach
führt diese die Persistierung durch.
Bei der Auswertung der Ergebnisse des Ein- und Auslagerns muss zwischen Schemata und
Instanzen unterschieden werden: Schemata werden im Normalfall nur ein mal gespeichert,
dafür sehr häufig geladen. Dies liegt daran, dass Schemata nicht geändert werden. Sollte
eine Änderung an einem Schema nötig werden, muss eine Schemaevolution durchgeführt
werden und dadurch neben dem alten ein neues Schema angelegt werden. Somit ist
die Zeit zum Einlagern von Schemata bei einer Interpretation stärker zu gewichten als
die zum Auslagern. Bei Instanzen zeigt sich ein anderes Bild: Diese müssen in etwa
gleich oft geladen wie gespeichert werden. Soll eine Instanz weitergeschaltet werden, wird
diese aus dem Sekundärspeicher geladen, im Primärspeicher weitergeschaltet und wieder
ausgelagert.
Neben dem Ein- und Auslagern von Schemata und Instanzen wurden auch Messungen von Anfragen auf Kollektionen durchgeführt. In Abschnitt 2.4.3 wurden zwei solcher Anfragen vorgestellt. Zum Einen müssen alle Instanzen eines gegebenen Schemas
gefunden werden können. Diese Anfrage wird im Folgenden aus Platzgründen in den
entsprechenden Diagrammen mit „Instanzen“ abgekürzt. Die zweite definierte Anfrage
an Kollektionen sucht nach Instanzen, welche einen bestimmten Knoten in einem bestimmten Zustand haben. Diese Anfrage wurde für alle Sekundärspeichervarianten vier
mal durchgeführt. Zuerst wurde nach dem Start-Knoten gesucht. Dieser sollte einmal im
Zustand RUNNING und einmal im Zustand COMPLETED sein. Danach wurde nach
dem End-Knoten gesucht und die gleichen Zustände untersucht. Die Ergebnisse sind in
den folgenden Diagrammen jeweils passend betitelt („StartRunning“ , „StartComplete“ ,
„EndRunning“ und „EndComplete“ ).
80
5.2 Sekundärspeichermessungen
Als Initialisierung dieser Anfragen auf Kollektionen wurden zwei Schemata erzeugt. Ein
einfaches und eines vom Typ „evolve“ (vergleiche Abschnitt 5.1). Danach wurden für beide
Schemata so viele Instanzen erzeugt, wie diese Knoten haben. Abschließend wurden die
Instanzen mehrfach weitergeschaltet und gleich über die Knoten verteilt. Es existierte
somit für jedes Schema und jeden Knoten genau eine Instanz, welche diesen Knoten im
Zustand RUNNING hatte.
5.2.2 XML
Dieser Abschnitt stellt die Ergebnisse von Messungen vor, welche Schemata und Instanzen in XML-Dateien ein- und auslagern. Hierbei ist die Serialisierung der Daten
von zentraler Bedeutung. Deshalb wird zunächst der Zeitaufwand betrachtet, welcher
für die De-/Serialisierung, also die Transformation der Primärspeicherobjekte in XML
und zurück, von Schemata und Instanzen nötig ist. Danach werden die Ergebnisse der
Speicherung dieser Daten im Dateisystem aufgeschlüsselt nach Schemata und Instanzen vorgestellt. Abschließend wird auf die Messergebnisse bei Anfragen auf Kollektionen
eingegangen.
5.2.2.1 Serialisierung Um die Persistierung mithilfe von XML-Dateien zu ermöglichen,
müssen sowohl Schemata als auch Instanzen serialisiert werden (vergleiche Abschnitt 4.3).
Abbildung 64: De-/Serialisieren eines Blocks
Hierzu wurde auch die Laufzeit der De-/Serialisierung von einzelnen Blöcken eines Schemas betrachtet. Die Ergebnisse sind in Abbildung 64 dargestellt. Die explizite Primärspeicherrepräsentation hat hierbei beim Serialisieren Vorteile, da die Blockrepräsentation
bereits vorliegt und nicht berechnet werden muss. Auch beim Deserialisieren ist die ex-
81
5 Messungen
plizite Primärspeicherrepräsentation schneller, da die implizite die (expliziten) Daten
umformen muss.
serialisieren
deserialisieren
Abbildung 65: De-/Serialisieren von Schemata
Auf Basis der Messwerte von Schemata aus Abbildung 65 ist zu erkennen, dass das De/Serialisieren von Schemata in eine XML-Repräsentation mit impliziter Blockstruktur im
Allgemeinen schneller ist als die Serialisierung in eine mit expliziter Blockstruktur. Dies
ist unabhängig von der Primärspeicherrepräsentation in der das Schema vorliegt, wobei
der Vorteil einer impliziten XML-Repräsentation bei einer impliziten Primärspeicherrepräsentation größer ist. Dies liegt daran, dass die explizite Primärspeicherrepräsentation
hierbei in eine implizite umgewandelt werden muss.
Die Messergebnisse des De-/Serialisierens von Instanzen ist in Abbildung 66 dargestellt.
Hierbei fällt auf, dass es am Schnellsten ist, wenn man als XML-Repräsentation die gleiche
82
5.2 Sekundärspeichermessungen
serialisieren
deserialisieren
Abbildung 66: De-/Serialisieren von Instanzen
Repräsentation wählt, wie sie im Primärspeicher vorliegt. Ist eine Instanz mit impliziten
Knotenzuständen vorhanden, ist es vergleichsweise langsam, diese in eine explizite Repräsentation zu serialisieren. Für eine explizite Primärspeicherrepräsentation ist hingegen die
Deserialisierung aus einer impliziten Repräsentation vergleichsweise langsam. In beiden
Fällen müssen die fehlenden Knotenzustände COMPLETED und NOT_ACTIVATED
errechnet werden.
5.2.2.2 Schemata Diese Serialisierung ist Basis der Speicherung von Schemata in
XML-Dateien. Die in Abbildung 67 dargestellten Messergebnisse umfassen sowohl die
De-/Serialisierung als auch das Laden und Speichern dieser in eine Datei. Es wird erwartet, dass Schemata, die im Primärspeicher in expliziter Blockrepräsentation vorliegen
83
5 Messungen
auch am schnellsten in eine XML-Datei gespeichert werden, welche explizit serialisiert
wird. Die Messungen zeigen allerdings das Gegenteil. So zeigen die Werte für das Speichern eines komplexen Schemas, dass ein in impliziter Blockrepräsentation im Primärspeicher vorliegendes Schema schneller in eine XML-Datei mit expliziter Blockrepräsentation gespeichert werden kann als ein Schema, welches in expliziter Blockrepräsentation im
Primärspeicher vorliegt. Ebenso gilt die Gegenrichtung, was aus den anderen, paarweise
markierten Zahlen in den Diagrammen ablesbar ist. Dieses Verhalten ist nicht erklärbar,
wurde aber durch mehrfache Ausführung der Messungen bestätigt.
laden
speichern
Abbildung 67: Messergebnisse für Schemata bei einer XML-Datei-Speicherung
Vergleicht man die implizite Sekundärspeicherrepräsentation mit der expliziten, so stellt
man fest, dass die implizite unabhängig von der eingesetzten Primärspeicherrepräsentation Vorteile bietet. Hierbei ist zu beachten, dass Schemata häufig geladen und selten
gespeichert werden. Somit ist die Ladezeit höher zu gewichten. Es fällt auf, dass für ein
84
5.2 Sekundärspeichermessungen
einfaches, mit expliziten Blockinformationen vorliegendes Schema die explizite Sekundärspeicherrepräsentation schneller ist als die implizite. In allen anderen Fällen ist eine
implizite Sekundärspeicherrepräsentation vorteilhaft.
5.2.2.3 Instanzen Für Instanzen existieren die beiden Sekundärspeicherrepräsentationen mit expliziten und impliziten Knotenzuständen. Diese müssen ebenfalls zuerst serialisiert und dann in eine Datei gespeichert werden.
laden
speichern
Abbildung 68: Messergebnisse für Instanzen bei einer XML-Datei-Speicherung
In Abbildung 68 sind die Messergebnisse dargestellt. Es fällt auf, dass keine merklichen Unterschiede in den Ergebnissen zwischen impliziter und expliziter Primärspeicher-
85
5 Messungen
repräsentation bestehen, wenn eine implizite XML-Repräsentation gewählt wird. Dies
ist erstaunlich, da beim Laden erwartet wird, dass die explizite Primärspeicherrepräsentation langsamer ist. Hierbei müssen die fehlenden Zustände COMPLETED und
NOT_ACTIVATED errechnet werden, da sie nicht in der XML-Datei vorliegen. Anschaulich wird dies bei Betrachtung der expliziten XML-Repräsentation. Hier fällt auf,
dass beim Speichern die implizite Primärspeicherrepräsentation langsamer ist, da nicht
alle Zustände, die zur Serialisierung benötigt werden, direkt vorliegen.
Zusammenfassend ist die implizite Sekundärspeicherrepräsentation von Instanzen zu
empfehlen. Sie ist in allen Fällen bezüglich des Zeitaufwands bei Ein- und Auslagerung
der expliziten mindestens gleichwertig.
5.2.2.4 Anfragen auf Kollektionen Die Messergebnisse von Anfragen auf Kollektionen
bei der XML-Datei-Speicherung sind in Abbildung 69 dargestellt. Für die implizite XMLRepräsentation ist erkennbar, dass die beiden Anfragen nach Knoten im Zustand RUNNING in etwa den gleichen Zeitaufwand benötigen. Dieser ist geringer als der Aufwand
bei Anfragen an den Zustand COMPLETED, da dieser bei der impliziten Repräsentation nicht direkt vorhanden ist. Deshalb müssen bei einer Anfrage nach dem Zustand
COMPLETED die Zustände aller Knoten geladen und anschließend im Primärspeicher
ausgewertet werden. Bei Anfragen nach dem Zustand RUNNING können die XML-Daten
dagegen direkt untersucht werden.
Die implizite XML-Repräsentation ist allerdings durchgängig schneller als die explizite.
Dies liegt daran, dass bei der impliziten weniger Daten vorhanden und somit weniger
Zeit dafür aufgewendet werden muss, diese zu untersuchen. Selbst in den Fällen, in denen
bei der impliziten Repräsentation alle Knotenzustände geladen und im Primärspeicher
untersucht werden müssen, ist die explizite Repräsentation langsamer. Hieraus folgt, dass
der Großteil der Zeit beim Laden des Datenstroms von der Festplatte und nicht beim
Deserialisieren und Berechnen der fehlenden Knotenzustände anfällt.
5.2.2.5 Zusammenfassung Bei der Betrachtung der Messungen der Ein-/Auslagerung
von Schemata wurde festgestellt, dass die explizite Sekundärspeicherrepräsentation ausschließlich im Fall von einer expliziten Primärspeicherrepräsentation und einfachen Schemata vorzuziehen ist. Für die Sekundärspeicherrepräsentation von Instanzen wurde sowohl für das Ein- und Auslagern als auch für Anfragen auf Kollektionen festgestellt, dass
die Repräsentation mit impliziten Knotenzuständen der mit expliziten überlegen ist.
5.2.3 PostgreSQL
Neben einer Speicherung in XML-Dateien ist der Einsatz eines RDBMS eine weitere Möglichkeit zur Persistierung. In diesem Abschnitt werden die Messergebnisse beim Einsatz
von PostgreSQL vorgestellt. Hierbei wird ebenfalls zunächst auf das Ein- und Auslagern
86
5.2 Sekundärspeichermessungen
evolve-Schema
einfaches Schema
Abbildung 69: Anfragen auf Kollektionen bei XML-Datei-Speicherung
von Schemata und Instanzen und im Anschluss auf Anfragen auf Kollektionen eingegangen.
5.2.3.1 Schemata Die Messergebnisse für Schemata sind in Abbildung 70 dargestellt.
Hierbei wird unterschieden zwischen den drei Sekundärspeicherrepräsentationen, welche
in Abschnitt 4.3.3 vorgestellt wurden: Eine Repräsentation mit impliziten Blockinformationen, eine mit expliziten, eine mit expliziten unter Einsatz des XML-Datentyps und
eine mit impliziten Blockinformationen.
87
5 Messungen
laden
speichern
Abbildung 70: Messergebnisse von Schemata bei Einsatz von PostgreSQL
Man erkennt, dass die implizite Sekundärspeicherrepräsentation der expliziten bei dem
eingesetzten einfachen Schema vorteilhaft, beim komplexen Schema hingegen wesentlich
langsamer ist. Dies wurde weitergehend untersucht und herausgefunden, dass diese Grenze von der prozentualen Anzahl der Blöcke zur Anzahl der Knoten abhängig ist. Hierbei
wurde festgestellt, dass die implizite Sekundärspeicherrepräsentation etwa bis zu einem
Grad von 27% Blockanteil schneller ist. Dies wurde anhand von Ladezeiten von Schemata untersucht, welche in die selbe Primärspeicherrepräsentation geladen wurden, wie sie
im Sekundärspeicher vorlagen. Die Knoten wurden hierbei auf die Blöcke gleich verteilt.
Überprüft man dieses Ergebnis anhand der durchgeführten Messung aus Abbildung 70,
88
5.2 Sekundärspeichermessungen
so stellt sich für das eingesetzte komplexe Schema ein Blockanteil etwa 13.6% heraus.
Hierbei ist allerdings die explizite Sekundärspeicherrepräsentation bereits schneller als
die implizite, was dem vorigen Ergebnis von 27% widerspricht. Der Grund hierfür liegt
darin, dass im benutzten komplexen Schema die Knoten nicht gleich auf die Blöcke verteilt sind. Somit hängt der Blockanteil, ab dem die explizite Repräsentation der impliziten
vorzuziehen ist, auch von der Verteilung der Knoten auf die Blöcke ab. Dies konnte im
Rahmen dieser Arbeit nicht weiter untersucht werden.
Beim Vergleich der expliziten Sekundärspeicherrepräsentation mit und ohne XMLDatentyp fällt auf, dass die Daten zwar etwa zwischen 22% und 40% schneller gespeichert,
allerdings 13% bis 16% langsamer geladen werden. Da ein Schema im Normalfall nur einmal gespeichert, aber oft geladen wird, ist die Verwendung des XML-Datentyps beim
Ein- und Auslagern der Daten nicht zu empfehlen.
5.2.3.2 Instanzen Abbildung 71 fasst die Messergebnisse vom Ein- und Auslagern von
Instanzen bei Einsatz von PostgreSQL zusammen. Hierbei wurde eine Repräsentation
mit expliziten und eine mit impliziten Knotenzuständen untersucht, jeweils einmal mit
und einmal ohne Einsatz des XML-Datentyps.
Auffällig hierbei ist der hohe Aufwand, der zur impliziten Speicherung von Instanzen
getrieben werden muss. Der Einsatz des XML-Datentyps beschleunigt diesen Vorgang erheblich. Das Laden aus einer impliziten Sekundärspeicherrepräsentation benötigt, analog
zur Betrachtung für XML-Dateien, selbst für explizite Primärspeicherrepräsentationen
keinen zusätzlichen Zeitaufwand. Auch hier ist ersichtlich, dass eine implizite Sekundärspeicherrepräsentation aufgrund der geringen zu ladenden/zu speichernden Datenmenge
der expliziten vorzuziehen ist.
5.2.3.3 Anfragen auf Kollektionen Abbildung 72 fasst die Messergebnisse von Anfragen auf Kollektionen zusammen. Auffällig hierbei ist, dass die implizite Sekundärspeicherrepräsentation in allen Fällen schneller ist. Dieser Vorteil kann durch Einsatz
des XML-Datentyps weiter ausgebaut werden. Dadurch kann bei Anfragen nach dem
Knotenzustand RUNNING der gespeicherte XML-Datenstrom direkt vom RDBMS ausgewertet werden (vergleiche Abschnitt 4.4.2). Wird der XML-Datentyp nicht eingesetzt
oder nach dem Zustand COMPLETED gesucht, müssen alle Instanzen eingelagert und
im Primärspeicher untersucht werden.
Wird bei der expliziten Repräsentation der XML-Datentyp eingesetzt, so sind Anfragen an Knoten im Zustand COMPLETED in der impliziten Repräsentation mit XMLDatentyp etwa um das 1,7-fache langsamer. Bei Anfragen nach dem Knotenzustand RUNNING hingegen besitzt die explizite Repräsentation etwa die 2,2-fache Laufzeit wie die
implizite. Somit ist der Einsatz einer impliziten Sekundärspeicherrepräsentation unter
Verwendung des XML-Datentyps in Bezug auf Anfragen auf Kollektionen zu empfehlen.
89
5 Messungen
laden
speichern
Abbildung 71: Messergebnisse von Instanzen bei Einsatz von PostgreSQL
5.2.3.4 Zusammenfassung Bei Einsatz von PostgreSQL ist es sinnvoll, für einfache
Schemata die implizite, für komplexe Schemata die explizite Sekundärspeicherrepräsentation zu benutzen. Der Einsatz des XML-Datentyps ist nicht zu empfehlen. Bei Instanzen hat sich gezeigt, dass es am schnellsten ist, diese im Sekundärspeicher mit impliziten
Knotenzuständen unter Einsatz des XML-Datentyps zu repräsentieren. Dies ist sowohl
für die Ein-/Auslagerung als auch für Anfragen auf Kollektionen vorteilhaft.
90
5.2 Sekundärspeichermessungen
evolve-Schema
einfaches Schema
Abbildung 72: Anfragen auf Kollektionen bei Einsatz von PostgreSQL
5.2.4 Oracle
Neben PostgreSQL wurde auch die Speicherung in einem RDBMS von Oracle untersucht. Die hierbei festgestellten Messergebnisse werden im Folgenden vorgestellt. Es wird
wiederum zuerst auf das Ein- und Auslagern von Schemata, dann von Instanzen und
schließlich auf Anfragen auf Kollektionen eingegangen.
5.2.4.1 Schemata Die Messergebnisse des Ein-/Auslagerns von Schemata in verschiedenen Repräsentationen sind in Abbildung 73 dargestellt.
91
5 Messungen
laden
speichern
Abbildung 73: Messergebnisse von Schemata bei Einsatz von Oracle
Die implizite Sekundärspeicherrepräsentation ist bei den Ladezeiten jeweils am schnellsten und deren Einsatz somit zu empfehlen. Das Speichern ist im komplexen Fall zwar
langsamer als bei einer expliziten Realisierung, da ein Schema aber einmal gespeichert
und oft geladen wird, fällt diese Beobachtung nicht ins Gewicht. Auffallend ist außerdem,
dass das Laden eines explizit im Sekundärspeicher vorliegenden Schemas bei Einsatz des
XML-Datentyps sehr langsam ist im Vergleich zur Variante ohne Einsatz des XMLDatentyps. Hierbei ist beim komplexen Schema ein Faktor von bis zu 3.77 und beim
einfachen Schema einer bis zu 3.12 zu erkennen.
92
5.2 Sekundärspeichermessungen
5.2.4.2 Instanzen Aus den in Abbildung 74 dargestellten Ergebnissen ist abzulesen,
dass es beim Einsatz von Oracle vorteilhaft ist, einfache Instanzen mithilfe einer expliziten Sekundärspeicherrepräsentation zu speichern. Dies liegt daran, dass eine Instanz
im Gegensatz zu Schemata auch mehrfach gespeichert wird. Somit gleicht sich die leicht
schlechtere Ladezeit gegenüber der impliziten Repräsentation durch die wesentlich bessere Laufzeit zum Speichern aus. Für komplexe Instanzen ist allerdings eine implizite
Repräsentation mit Einsatz des XML-Datentyps von Vorteil.
laden
speichern
Abbildung 74: Messergebnisse von Instanzen bei Einsatz von Oracle
93
5 Messungen
5.2.4.3 Anfragen auf Kollektionen Bei der Untersuchung der Messergebnisse von Anfragen auf Kollektionen aus Abbildung 75 fallen als erstes die sehr schlechten Zahlen
beim Einsatz des XML-Datentyps auf. Diese liegen bis zu 11,6 mal höher als beim Einsatz des CLOB-Datentyps. Die einzigen Ausnahmen bilden hierbei die beiden Anfragen
nach dem Zustand COMPLETED bei der impliziten Sekundärspeicherrepräsentation,
diese sind schneller als bei der Variante ohne Einsatz des XML-Datentyps. Dies liegt
evolve-Schema
einfaches Schema
Abbildung 75: Anfragen auf Kollektionen bei Einsatz von Oracle
daran, dass bei diesen Anfragen die kompletten serialisierten Daten von der Implementierung von InstanceStorage abgerufen, im Primärspeicher deserialisiert und untersucht
werden. Die im RDBMS vorliegenden XML-Daten werden hierbei nicht, wie bei den Anfragen nach dem Zustand RUNNING, direkt vom RDBMS untersucht. Hieraus kann man
ableiten, dass Oracle die Unterstützung zum Untersuchen von XML-Daten bei Abfragen
94
5.2 Sekundärspeichermessungen
zwar anbietet, diese allerdings nur in unbefriedigender Laufzeit beantworten kann. Wird
allerdings die gesamte XML-Serialisierung abgerufen (wie zum Beispiel bei den Anfragen
zum Knotenzustand COMPLETED und impliziter Realisierung), zeigt sich, dass diese
bei Einsatz des XML-Datentyps schneller geliefert werden, als wenn der CLOB-Datentyp
eingesetzt wird. Es liegt somit nahe, für Oracle eine hybride Lösung aus den bisher vorgestellten einzusetzen. Diese basiert auf der impliziten Repräsentation mit XML-Datentyp,
führt allerdings keine direkten Datenbankanfragen auf den XML-Daten durch, sondern
lädt jeweils die ganzen Daten, deserialisiert im Primärspeicher und wertet diese dann aus.
Die Ergebnisse einer solchen Realisierung sind ebenfalls in den Diagrammen in Abbildung 75 dargestellt. Diese Ergebnisse sind in Bezug auf die Anfrage nach Instanzen eines
Schemas und Instanzen mit Knoten im Zustand RUNNING vergleichbar mit den Werten der impliziten Repräsentation unter Einsatz des XML-Datentyps. Bei Anfragen nach
Knoten im Zustand COMPLETED ist die hybride Repräsentation allerdings wesentlich
schneller und deren Einsatz ist somit zu empfehlen.
5.2.4.4 Zusammenfassung Bei Einsatz von Oracle ist die Repräsentation mit impliziten Blockinformation für Schemata vorteilhaft. In Bezug auf Instanzen wurde festgestellt,
dass die explizite Repräsentation zu empfehlen ist, wenn die Instanz wenige Knoten hat.
Hat sie viele Knoten, ist die implizite Repräsentation unter Einsatz des XML-Datentyps
vorteilhaft. Bei der Betrachtung der Messergebnisse zu Anfragen auf Kollektionen fällt
allerdings auf, dass Abfragen der XML-Daten von Oracle nur mit einer hohen Laufzeit
beantwortet werden können. Aufgrund dessen wurde bei den Anfragen auf Kollektionen
ein Hybridverfahren empfohlen, welches auf der impliziten Repräsentation mit Einsatz
des XML-Datentyps beruht, aber keine Abfragen auf diesen Daten durchführt. Somit ist
die hybride Variante zum Einsatz für Instanzen empfehlenswert.
5.2.5 DB2
Als letztes in dieser Arbeit untersuchtes RDBMS werden in diesem Abschnitt die Ergebnisse von DB2 vorgestellt. Hierbei wird wiederum zuerst auf Schemata, dann auf
Instanzen und schließlich auf Anfragen auf Kollektionen eingegangen.
5.2.5.1 Schemata Abbildung 76 stellt die Messergebnisse von Schemata bei Einsatz
von DB2 dar. Hierbei sind die markierten Paare verwunderlich, da diese in der falschen
Relation zueinander stehen. So wird davon ausgegangen, dass ein einfaches, im Primärspeicher implizit vorliegendes Schema langsamer in eine explizite Sekundärspeicherrepräsentation mit Einsatz des XML-Datentyps gespeichert werden kann, als das selbe Schema, welches allerdings im Primärspeicher bereits explizit vorliegt. Diese Abweichungen
können zum jetzigen Zeitpunkt nicht erklärt werden.
95
5 Messungen
laden
speichern
Abbildung 76: Messergebnisse von Schemata bei Einsatz von DB2
Vergleicht man die Werte, ist ersichtlich, dass sich für das einfache Schema eine implizite
Sekundärspeicherrepräsentation und für das komplexe eine explizite eignet. Dies wurde
bereits bei PostgreSQL beobachtet und wurde auch hier tiefergehend untersucht: Es
wurde ebenfalls ein Test durchgeführt, der die Ladezeiten von Schemata mit variierendem
Blockanteil misst. Hier waren die Knoten ebenfalls gleich auf die Blöcke verteilt. Bei
einem Blockanteil von 25% war die implizite Sekundärspeicherrepräsentation ab etwa
4 bis 5 Knoten gegenüber der expliziten vorteilhaft, bei 22.5% Blockanteil ab etwa 15
Knoten und bei einem Anteil von 20% noch ab etwa 50 Knoten. Ist der Blockanteil
geringer, ist stets die explizite Sekundärspeicherrepräsentation schneller als die implizite,
bei einem Blockanteil über 25% ist die implizite schneller als die explizite. Dies bestätigt
die Ergebnisse aus den Diagrammen aus Abbildung 76. Das komplexe Schema liegt mit
13.6% im Bereich, in dem die explizite Sekundärspeicherrepräsentation vorteilhaft ist,
96
5.2 Sekundärspeichermessungen
das einfache Schema mit einem Blockanteil von 25% und 4 Knoten ist im Bereich, in
dem die implizite Repräsentation schneller ist.
Bei der Betrachtung der Daten aus Abbildung 76 stellt man ebenso fest, dass ein Einsatz
des XML-Datentyps nicht sinnvoll ist, da dieser beim Laden des Schemas langsamer ist.
laden
speichern
Abbildung 77: Messergebnisse von Instanzen bei Einsatz von DB2
5.2.5.2 Instanzen Die implizite Sekundärspeicherrepräsentation unter Einsatz des
XML-Datentyps ist der expliziten überlegen. Dies kann an den Messergebnissen aus Abbildung 77 direkt abgelesen werden. Betrachtet man die Werte der impliziten Repräsentation mit und ohne XML-Datentyp, erkennt man, dass die implizite Repräsentation ohne
XML-Datentyp beim Speichern ein Vorteil von 15.6% hat, während das Laden 9.7% lang-
97
5 Messungen
samer ist als die Repräsentation mit XML-Datentyp. Da eine Instanz allerdings häufig
gespeichert wird, ist die Repräsentation mit XML-Datentyp vorzuziehen.
5.2.5.3 Anfragen auf Kollektionen Abbildung 78 zeigt die Messergebnisse von Anfragen auf Kollektionen bei Einsatz von DB2. Es ist abzulesen, dass die implizite Repräsentation unter Einsatz des XML-Datentyps in den meisten Fällen am schnellsten ist. Eine
evolve-Schema
einfaches Schema
Abbildung 78: Anfragen auf Kollektionen bei Einsatz von DB2
Ausnahme bilden die Abfragen nach COMPLETED Knoten, bei denen durch eine explizite Repräsentation eine bis zu 13.8% schnellere Antwortzeit erreicht werden kann. Auf
der anderen Seite benötigt die explizite Repräsentation mehr als drei mal so lange wie die
implizite, eine Anfrage nach einem RUNNING Knoten zu beantworten. Ein nicht ganz
so krasses, aber dennoch deutliches Bild entsteht bei der Gegenüberstellung der impliziten Repräsentation mit und ohne Einsatz des XML-Datentyps. Hierbei wird in etwa die
98
5.2 Sekundärspeichermessungen
1,5-fache Zeit benötigt, um einen COMPLETED Knoten aus einer XML-Spalte zu laden
gegenüber einer CLOB-Spalte. Dafür benötigt die Repräsentation ohne XML-Datentyp
mehr als doppelt so lange, eine Abfrage auf RUNNING Knoten zu beantworten. Somit
ist die implizite Repräsentation mit Einsatz des XML-Datentyps zu empfehlen.
5.2.5.4 Zusammenfassung Es ist wie bereits bei PostgreSQL vorteilhaft, Schemata
implizit zu repräsentieren, sofern es einfache Schemata sind. Handelt es sich um komplexe,
so eignet sich eine explizite Repräsentation besser. Für Instanzen ist es sinnvoll, diese
mit impliziten Knotenzuständen unter Einsatz des XML-Datentyps zu repräsentieren.
Dies ist sowohl für die Ein- und Auslagerung als auch für Anfragen auf Kollektionen von
Vorteil.
laden
speichern
Abbildung 79: Geschwindigkeitsfaktoren von Schemata bei Einsatz von JPA
99
5 Messungen
5.2.6 JPA
Nach der Vorstellung der Messergebnisse bei direkter Speicherung von Schemata und
Instanzen in RDBMS geht dieser Abschnitt auf die Speicherung mithilfe von JPA ein.
Hierbei werden die Daten ebenfalls in einem der vorgestellten RDBMS gespeichert. Eine
Persistierung mit JPA ist im Allgemeinen wesentlich langsamer als eine direkte Persistierung. Deshalb wurde in den Abbildungen 79 und 80 eine andere Darstellungsform als
bisher gewählt. Die angegebenen Werte sind die Faktoren, um die die Persistierung mit
JPA langsamer ist als eine vergleichbare direkte. Es ist erkennbar, dass das Laden von
Schemata das 5,81-fache bis zum 39,31-fachen der Zeit benötigt, welche bei einer direkten Speicherung anfällt. Das Speichern ist mit dem 3,52-fachen bis 13,39-fachen ebenfalls
auffällig langsamer.
laden
speichern
Abbildung 80: Geschwindigkeitsfaktoren von Instanzen bei Einsatz von JPA
100
5.2 Sekundärspeichermessungen
Bei Instanzen zeigt sich ein nicht ganz so krasses Bild wie bei Schemata, dennoch werden
beim Laden bis zum 8,6-fachen und beim Speichern bis zum 4,97-fachen der Zeit von
einer direkten Speicherung benötigt.
Aufgrund dieser Werte wird JPA fortan nicht weiter betrachtet.
5.2.7 Vergleich
Es wurden sowohl für eine XML-Datei-Speicherung als auch einer Persistierung mit den
verschiedenen RDBMS Messwerte vorgestellt. In diesem Abschnitt werden die jeweils
empfohlenen Repräsentationen in Relation zueinander gesetzt und verglichen. Hierbei
werden wie bisher zuerst Schemata, dann Instanzen und schließlich Anfragen auf Kollektionen betrachtet.
5.2.7.1 Schemata Abbildung 81 zeigt den Vergleich der Werte der jeweils empfohlenen
Repräsentationen beim Ein-/Auslagern von Schemata. Hierbei zeigt sich, dass die XMLDatei-Speicherung am schnellsten ist.
Um einen direkten Vergleich ziehen zu können, wird angenommen, dass auf eine Speicherung eines Schemas 100 Ladevorgänge kommen. Außerdem gebe es gleich viele komplexe
wie einfache Schemata. Dann lässt sich für jede der Varianten ein Performanzkoeffizient
berechnen, wobei l die Ladezeiten und s die Zeiten zum Speichern sind:
cSchema = (
100 · leinf ach + seinf ach 100 · lkomplex + skomplex 1
+
)·
101
101
2
Diese Koeffizienten repräsentieren den durchschnittlichen Zeitaufwand zum Laden oder
Speichern und sind in Abbildung 82 für die einzelnen Speichervarianten in Abhängigkeit
der Primärspeicherrepräsentation angegeben.
Abbildung 82: Performanzkoeffizienten cSchema der einzelnen Varianten
Es ist hierbei ersichtlich, dass die XML-Datei-Speicherung wesentlich schneller ist als
eine Speicherung in einem RDBMS. Hierbei muss angemerkt werden, dass die XMLDatei-Speicherung nicht so viele Möglichkeiten bietet wie eine Persistierung in einem
101
5 Messungen
laden
speichern
Sekundärspeicher
XML
PostgreSQL
Oracle
DB2
1
2
Schema
komplex
einfach
komplex
einfach
komplex
einfach
komplex
einfach
Primärspeicher
implizit
explizit
laden speichern laden speichern
4.42
2.22
3.82
22
2.32
1.32
2.41
1.41
1
1
1
6.5
18.1
6.5
18.11
2
2
2
3
5.1
3.1
5.12
142
25.42
14.22
25.52
2
2
2
4.5
8.9
4.6
8.92
8.21
271
8.21
26.51
2
2
2
3.5
4.8
3.6
5.22
explizite Sekundärspeicherrepräsentation
implizite Sekundärspeicherrepräsentation
Abbildung 81: Vergleich Ein-/Auslagern von Schemata der versch. Varianten
102
5.2 Sekundärspeichermessungen
RDBMS. Diese kann ausschließlich auf Basis der Schema-ID Ein- und Auslagern, während bei RDBMS (effiziente) Abfragen auf weitere, in dieser Arbeit nicht betrachtete
Eigenschaften eines Schemas denkbar sind. Da diese Eigenschaften von RDBMS für Schemata allerdings nicht benötigt werden, wird hier aufgrund des Geschwindigkeitsvorteils
die Speicherung in XML-Dateien empfohlen.
5.2.7.2 Instanzen In Abbildung 83 sind die Messwerte der einzelnen Speicherarten
beim Ein- und Auslagern von Instanzen zusammengefasst. Auch hier wurden, wie schon
bei Schemata, die jeweils empfohlenen Werte in die Diagramme aufgenommen.
Analog zur Betrachtung bei Schemata, lassen sich auch hier Performanzkoeffizienten
berechnen. Es wird davon ausgegangen, dass Instanzen gleich oft gespeichert wie geladen
werden, da diese zum Beispiel nach jedem Weiterschalten gespeichert werden müssen.
Somit wird für den Vergleich der folgende Koeffizient herangezogen:
cInstanz = (leinf ach + seinf ach + lkomplex + skomplex ) ·
1
2
Er gibt an, wie lange es im Durchschnitt dauert, eine Instanz bei der gegebenen Sekundärspeichervariante und unter Abhängigkeit von der Primärspeicherrepräsentation zu laden
und zu speichern. Die Werte sind in Abbildung 84 dargestellt.
Abbildung 84: Performanzkoeffizienten cInstanz der einzelnen Varianten
Analog zur Betrachtung für Schemata ist auch bei der Betrachtung der Ein- und Auslagerung von Instanzen die XML-Datei-Speicherung zu empfehlen. Die Variante, welche
am zweit schnellsten ist, die Speicherung mit PostgreSQL, braucht mehr als 2,3 mal so
lange zum Laden und Speichern einer Instanz.
5.2.7.3 Anfragen auf Kollektionen Zusätzlich zum Ein- und Auslagern von Instanzen
wurden auch Messungen anhand der Anfragen auf Kollektionen durchgeführt. Es fällt
auf, dass bei allen untersuchten Sekundärspeichervarianten die implizite Repräsentation
von Instanzen empfohlen wurde. Hierbei wurde bei allen RDBMS der XML-Datentyp
eingesetzt. Bei Oracle kam die Hybridvariante zum Einsatz, welche keine Abfragen auf
103
5 Messungen
laden
speichern
Sekundärspeicher
XML
PostgreSQL
Oracle
DB2
1
2
3
Schema
komplex
einfach
komplex
einfach
komplex
einfach
komplex
einfach
Primärspeicher
implizit
explizit
laden speichern laden speichern
1.22
1.12
1.22
1.12
2
2
2
1.2
1.1
1.2
1.12
2.73
2.73
2.73
2.63
3
3
3
2.7
2.8
2.7
2.63
6.63
4.63
6.63
4.63
1
1
1
6.8
4
6.8
3.81
2.83
5.13
2.83
5.13
3
3
3
2.8
5.1
2.8
5.13
explizite Sekundärspeicherrepräsentation
implizite Sekundärspeicherrepräsentation
implizite Sekundärspeicherrepräsentation mit XML
Abbildung 83: Vergleich Ein-/Auslagern von Instanzen der versch. Varianten
104
5.2 Sekundärspeichermessungen
die in XML vorliegenden Daten innerhalb des RDBMS tätigt. Die in Abbildung 85 zusammengestellten Werte relativieren die bisherigen Einschätzungen der Performanz der
XML-Datei-Speicherung. Diese benötigt zum Beispiel bei der Anfrage nach allen Instanzen eines Schemas mehr als die 64-fache Zeit, die PostgreSQL benötigt. Dies liegt daran,
dass die Repräsentationen auf Basis von RDBMS diese Information in einer Tabelle direkt vorliegen haben, die XML-Datei-Speicherung aber alle vorhandenen Dateien auf die
Zugehörigkeit untersuchen muss.
evolve-Schema
einfaches Schema
Abbildung 85: Vergleich der Anfragen auf Kollektionen der versch. Varianten
105
5 Messungen
Für einen direkten Vergleich wird auch hier ein Performanzkoeffizient berechnet. Es wird
davon ausgegangen, dass die Anfrage nach Instanzen eines Schemas wesentlich häufiger durchgeführt wird als die Anfrage nach einem bestimmten Knotenzustand. Ebenso
wird angenommen, dass gleich viele einfache Schemata und Schemata vom Typ „evolve“
vorhanden sind. Seien kST die Werte aus Abbildung 85 mit S = {einfach, evolve} und
T = {Instanzen, StartRunning, StartComplete, EndRunning, EndComplete}, dann gilt
für den Koeffizienten:
cKollektion = (
Instanzen + k Zustand
Instanzen + k Zustand
50 · keinf
1
50 · kevolve
ach
einf ach
evolve
+
)·
54
54
2
mit
kSZustand = kSStartRunning + kSStartComplete + kSEndRunning + kSEndComplete
Der Koeffizient repräsentiert also die durchschnittliche Laufzeit einer Anfrage an eine
Kollektion, wenn man davon ausgeht, dass die Instanzen eines Schemas 50 mal häufiger angefragt werden als die Zustände von Knoten. Die in Abbildung 86 vorgestellten
Koeffizienten zeigen, dass die XML-Datei-Speicherung sehr langsam ist. Um einen endgültigen Vergleich zu ziehen, müssen diese Werte mit denen des Ein- und Auslagerns von
Instanzen aus Abbildung 84 vereint werden.
Abbildung 86: Performanzkoeffizienten cKollektion der einzelnen Varianten
Diese Vereinigung stellt Abbildung 87 dar. Die Werte werden durch Addition der Werte
aus den Abbildungen 84 und 86 berechnet. Dies ist anschaulich gesehen die Zeit, die im
Durchschnitt benötigt wird, eine Instanz zu suchen (anhand der Zugehörigkeit zu einem
bestimmten Schema oder auf Basis der Knotenzustände), diese zu laden und danach
wieder zu speichern. Der Koeffizient stellt also einen für die Performanz der Sekundärspeichervariante von Instanzen aussagekräftigen Wert dar.
106
5.3 Primärspeichermessungen
Abbildung 87: Vereinigte Performanzkoeffizienten der einzelnen Varianten
Es ist erkennbar, dass mit Einbeziehung der Anfragen auf Kollektionen nicht länger die
XML-Datei-Speicherung die favorisierte Sekundärspeichervariante ist. PostgreSQL kann
Anfragen dieser Art in 45-46% der Zeit beantworten und ist somit für den Einsatz zur
Speicherung von Instanzen zu empfehlen.
5.2.8 Zusammenfassung
In diesem Abschnitt wurden die Sekundärspeicherrepräsentationen und die Sekundärspeichervarianten untersucht. Es zeigte sich, dass es am schnellsten ist, Schemata in XMLDateien mit impliziter Blockstruktur zu speichern. Werden im Primärspeicher Schemata
mit expliziter Blockstruktur eingesetzt, so ist es vorteilhaft, die Dateien für einfache
Schemata allerdings in einer expliziten XML-Repräsentation zu persistieren. In Bezug
auf Instanzen wurde festgestellt, dass es vorteilhaft ist, diese mithilfe von PostgreSQL
zu speichern. Wird hierbei die Repräsentation mit impliziten Knotenzuständen gewählt
und der XML-Datentyp für die Spalte der Knotenzustände eingesetzt, so ist diese Sekundärspeicherrepräsentation unabhängig von der eingesetzten im Primärspeicher nach
Abschnitt 5.2.7 optimal.
5.3 Primärspeichermessungen
Nach der Betrachtung des Sekundärspeichers, werden in diesem Abschnitt Messergebnisse bezüglich des Primärspeichers vorgestellt. Hierbei wurde für Messungen, bei
denen aufgrund der Architektur des Testsystems (vergleiche Abschnitt 4.1.2) eine
TemplateStorage oder InstanceStorage benötigt wird, diejenige Implementierung gewählt, welche auf flüchtigem Speicher beruht (vergleiche Abschnitt 4.3.6). Nachdem die
verschiedenen Primärspeicherrepräsentationen von Schemata und Instanzen betrachtet
wurden, wird in Bezug auf die grobe Clusterung überprüft, in wie weit sich die Optimierungen durch Messungen bestätigen lassen, welche sich durch Einsatz der groben
Clusterung nach den konzeptionellen Betrachtungen aus Abschnitt 3.3 ergeben.
107
5 Messungen
5.3.1 Schemata und Instanzen
Zur Messung des Primärspeicherverbrauchs von Schemata wurden Schemata mit unterschiedlicher Anzahl Knoten generiert und deren Speicherverbrauch gemessen [Rou02].
Ebenso wurde der prozentuale Anteil an Blöcken variiert. Die Ergebnisse stellt Abbildung 88 dar.
40095
35000
30000
Größe in Byte
explizites Schema mit 20% Split-Knoten
25000
explizites Schema mit 10% Split-Knoten
explizites Schema mit 5% Split-Knoten
20000
implizites Schema mit 20% Split-Knoten
implizites Schema mit 10% Split-Knoten
15000
implizites Schema mit 5% Split-Knoten
10000
5000
0
1
5
10
15
20
25
30
35
40
45
50
Knotenanzahl
Abbildung 88: Primärspeicherverbrauch von Schemata
Für explizite Schemata mit 10% und 20% Split-Knoten sind deutlich die Aufkommen von
neuen Blöcken an den treppenartigen Abweichungen zu erkennen. Die explizite Repräsentation mit 5% Split-Knoten gleicht sich in ihrem Speicherverbrauch dem der impliziten
Repräsentationen an. Auch hierbei sind treppenartige Abweichungen zu erkennen. Diese repräsentieren bei der expliziten Repräsentation die Aufkommen neuer Blöcke, bei
den impliziten Repräsentationen ist dieser Speichermehrverbrauch in der Vergrößerung
der Hashtabelle der eingesetzten HashMap begründet. Allgemein ist die Repräsentation
mit impliziter Blockstruktur im Primärspeicher zu empfehlen, da diese unabhängig vom
Blockanteil wenig Speicher benötigt. Dieses Ergebnis bestätigt die in Abschnitt 3.2.3
erarbeiteten qualitativen Untersuchungen des Speicherverbrauchs von Schemata.
In Abschnitt 3.2.3 wurde allerdings eine längere Laufzeit bei Anfragen an implizite gegenüber expliziten Schemata aufgezeigt. Um dies zu messen, wurden Instanzen weitergeschaltet, welche auf Schemata der unterschiedlichen Repräsentationen beruhen, und die
Laufzeit gemessen. Hierbei kam das komplexe Schema zum Einsatz. Das Ergebnis ist in
Abbildung 89 zu sehen. Zu beachten ist, dass diese Werte im Bereich von Tausendstel
des Grundwertes liegen (siehe Abschnitt 5.1). Bei den in der Tabelle markierten Werten
traten bei der Wiederholung der Messung starke, periodische Schwankungen im Ergebnis auf. Die dargestellten Werte bilden den Mittelwert und sollten aufgrund dessen mit
Vorsicht betrachtet werden. Die jeweiligen Extremwerte sind angegeben. Warum die Er-
108
5.3 Primärspeichermessungen
gebnisse bei diesen Werten so stark schwanken und warum diese Schwankungen nahezu
exakt periodisch sind, konnte im Rahmen dieser Arbeit nicht geklärt werden.
Instanz
Knoten
NORMAL
AND-Split
XOR-Split
AND-Join
Schema
explizit
implizit
explizit
implizit
explizit
implizit
explizit
implizit
explizit
implizit
3.8
2.8
9.8* (4.1-18.5)
2.9
9.7* (7.2-18.4)
9.9* (4.4-18.3)
5.4
3.1
3.4
2.5
4.6
2.6
10.7* (8.0-19.3)
4.9
58.3* (49.5-60.7)
33.5* (22.1-38.8)
* sehr starke periodische Schwankungen im Ergebnis
Abbildung 89: Laufzeit beim Weiterschalten (in Tausendstel des Grundwertes)
Es ist zu erkennen, dass Instanzen, welche auf impliziten Schemata beruhen entgegen der
qualitativen Untersuchungen aus Abschnitt 3.2.3 durchgehend schneller weitergeschaltet
werden können als explizite Schemata. Dies liegt daran, dass das Weiterschalten keine
der Methoden von Schemata aufruft, welche die Blockstruktur von Schemata liefern und
für welche die explizite Primärspeicherrepräsentation schneller ist. Aufgrund dessen und
aufgrund des geringeren Speicheraufwands für implizite Schemata, sind diese für den
Einsatz im Primärspeicher zu empfehlen.
Bei den in Abbildung 89 vorgestellten Messergebnissen wurden zuerst ein normaler Knoten (Knoten 3 des komplexen Schemas), ein AND-Split- (Knoten 2) und ein XOR-SplitKnoten (Knoten 5) weitergeschaltet. Es stellt sich heraus, dass der normale Knoten am
schnellsten weitergeschaltet wird. Der AND-Split-Knoten muss mehrere nachfolgende
109
5 Messungen
Knoten als RUNNING markieren und benötigt deshalb mehr Zeit. Beim Weiterschalten
des XOR-Split-Knoten, bei dem der Zweig von Knoten 10 ausgewählt wurde, fällt zusätzlicher Aufwand an, da der zu aktivierende Zweig zuerst gefunden werden muss. Hierfür
müssen alle nachfolgenden Knoten untersucht werden, wofür zusätzliche Zeit benötigt
wird. Die Knoten der nicht gewählten Zweige werden hierbei zusätzlich als SKIPPED
markiert, was ebenfalls Zeit benötigt. Es ist offensichtlich, dass das Weiterschalten mehr
Zeit benötigt, je mehr Knoten als SKIPPED markiert werden müssen. Somit hängt dieser
Wert direkt mit dem zugrunde liegenden Schema zusammen. Im vorliegenden Fall wurden
neun Knoten als SKIPPED markiert. Neben diesen Knoten wurde außerdem zu einen
AND-Join-Knoten (zu Knoten 23) weitergeschaltet. Bevor dieser aktiviert werden kann,
muss überprüft werden, ob alle Zweige abgearbeitet sind. Dies ist bei einer Instanz mit
impliziter Knotenmarkierung sehr zeitintensiv, da die vorgelagerten Zweige viele Knoten
beinhalten und somit die Rekonstruktion der Knotenzustände aus den impliziten Informationen viel Zeit in Anspruch nimmt. Obwohl bei einer Repräsentation mit impliziten
Knotenzuständen alle anderen Knotentypen schneller weitergeschaltet werden können,
wird dennoch die explizite Repräsentation empfohlen, da diese zum Weiterschalten eines
AND-Join-Knotens im vorliegenden Fall nur etwa ein Elftel der Zeit benötigt und keine
allgemeinen Aussagen zu der Anzahl der Zweige vor einem AND-Join-Knoten oder der
Anzahl der sich darauf befindenden Knoten getroffen werden können.
Neben der Laufzeit des Weiterschaltens wurde auch der Speicherverbrauch der unterschiedlichen Instanzrepräsentationen untersucht. Dieser ist in Abbildung 90 dargestellt.
Bei der expliziten Repräsentation sind die treppenartigen Anstiege bei der Vergrößerung
der Hashtabelle der eingesetzten HashMap zu erkennen. Erwartungsgemäß ist der Speicherverbrauch einer mit impliziten Knotenzuständen gespeicherten Instanz konstant. Die
in Abbildung 90 untersuchten Instanzen hatten jeweils ausschließlich den Start-Knoten
im Zustand RUNNING.
e in Byte
2023
Gr
:
9
1500
explizite Instanz
1000
implizite Instanz
500
0
1
5
10
15
20
25
30
35
40
45
50
Knotenanzahl
Abbildung 90: Primärspeicherverbrauch von Instanzen
Betrachtet man die Laufzeit des Weiterschaltens und den Speicherverbrauch für die beiden Repräsentationen von Instanzen, so stellt sich für Instanzen mit weniger oder genau fünf Knoten heraus, dass die explizite Repräsentation zu empfehlen ist, da diese
im Durchschnitt sowohl schneller weitergeschaltet als auch weniger Speicherplatz benötigt. Sollte die Instanz mehr als fünf Knoten haben, so ist keine eindeutige Empfehlung
110
5.3 Primärspeichermessungen
zu geben. Dies ist abhängig von dem zur Verfügung stehenden Speicher. Ist genügend
Speicher für die Instanzen vorhanden, ist es ratsam, die explizite Repräsentation einzusetzen. Im gegensätzlichen Fall die implizite. Es ist auch denkbar, die Repräsentation
während der Laufzeit des PMS zu wechseln. Ist ein bestimmter Füllgrad des Speichers
nicht erreicht, werden Instanzen mit expliziten Knotenmarkierungen benutzt. Sobald der
Füllstand überschritten ist, werden Instanzen mit impliziten Knotenzuständen repräsentiert. Dies ist in der Praxis zum Beispiel durch Einsatz einer Schnittstelle ähnlich der
Instance-Schnittstelle (siehe Abbildung 39) leicht umsetzbar, da es für die restlichen
Teile des PMS transparent ist, wie die Knotenzustände repräsentiert werden. Das „Umschalten“ der Repräsentation kann hierbei auf die Weise geschehen, dass nach Erreichen
des Füllstandes (oder der Unterschreitung des Füllstandes) die Persistenzschicht, also
bei der hier vorgestellten Architektur die InstanceStorage-Implementierung, die neu
angefragten Instanzen in der entsprechenden Repräsentation liefert. Ebenso ist mithilfe von entsprechend zu wählenden Architekturaspekten eine Propagierung der „neuen“
Repräsentation auf alle im Speicher vorhandenen Instanzen denkbar.
5.3.2 Clusterung
Neben Schemata und Instanzen wurde auch die grobe Clusterung im Primärspeicher
untersucht. Hierbei sind einerseits die Messwerte des Weiterschaltens zu beachten. Nachdem diese vorgestellt wurden, werden die Messergebnisse der Laufzeit des Algorithmus
preEvolveSelection betrachtet.
Die Tabelle in Abbildung 91 stellt die Messwerte des Weiterschaltens mit Neuberechnung
der Clusterzugehörigkeit der ohne Neuberechnung aus Abbildung 89 gegenüber.
Lässt man die aufgrund von Schwankungen markierten Werte außer Acht, so bestätigt
sich, dass das Weiterschalten mit Neuberechnung der Clusterzugehörigkeit langsamer ist
als ohne Neuberechnung. Geht man davon aus, dass, wie bereits empfohlen, Schemata mit
impliziter Blockstruktur und Instanzen mit expliziten Knotenmarkierungen zum Einsatz
kommen, ist durch die Neuberechnung der Clusterzugehörigkeit eine Verschlechterung
der Laufzeit des Weiterschaltens zwischen 35.7% und 41.4% zu erwarten.
Neben dem Weiterschalten ist bei der Messung der groben Clusterung die Laufzeit des Algorithmus preEvolveSelection zu beachten, welche in Tabelle 2 dargestellt ist. Hierbei
wurde ein Schema vom Typ „evolve“ (siehe Abbildung 63) eingesetzt und 20 Instanzen auf
Basis dessen erstellt. Für jeden Knoten des Schemas existiert genau eine Instanz, welche
diesen Knoten im Zustand RUNNING hat. Es wurden die Laufzeiten des Algorithmus
auf Basis der Änderungen gemessen, welche aus Abbildung 92 ersichtlich sind.
Es ist zu erkennen, dass durch geringen Zeitaufwand meist der Großteil der Instanzen als
„migrierbar“ und „nicht migrierbar“ kategorisiert werden kann und nur ein Teil der Instanzen nach den Methoden aus [Rin04, Jur06] genauer untersucht und somit eingelagert
werden muss. Geht man davon aus, dass die in Abschnitt 5.2 empfohlene Sekundärspeicherrepräsentation von Instanzen mithilfe von PostgreSQL eingesetzt wird, hat das
111
5 Messungen
Knoten
NORMAL
AND-Split
XOR-Split
AND-Join
Schema
explizit
implizit
explizit
implizit
explizit
implizit
explizit
implizit
mit Neuberechnung
ohne Neuberechnung
Instanz
Instanz
explizit
implizit
explizit
implizit
4.5 +18.4% 4.5 +32.4%
3.8
3.4
3.8 +35.7% 3.8 +52.0%
2.8
2.5
5.8
5.7 +23.9%
9.8*
4.6
4.1 +41.4% 3.9 +50%
2.9
2.6
25.7
22.8
9.7*
10.7*
13.2
11.7 +138.8%
9.9*
4.9
6.0 +11.1% 40.5
5.4
58.3*
4.3 +38.7% 29.8
3.1
33.5*
* sehr starke periodische Schwankungen im Ergebnis
Abbildung 91: Weiterschalten mit & ohne Clusterung (in Tausendstel)
Einlagern einer Instanz etwa einen Zeitaufwand von 2.7 Zeiteinheiten (vergleiche Abbildung 83). Somit lassen sich durch den Einsatz der groben Clusterung 16.2 bis 32.4
Zeiteinheiten sparen, da 6 bis 12 Instanzen weniger eingelagert werden müssen. Ohne die
grobe Clusterung fallen 54 Zeiteinheiten an, es entsteht somit eine Zeitersparnis von 30%
bis 60%. Hierbei ist die Zeit nicht mit eingerechnet, die zur Untersuchung der Instanzen
nach [Rin04, Jur06] nötig ist und somit durch Einsatz der groben Clusterung für 6 bis
12 Instanzen gespart werden kann.
Diese Werte können allerdings nur als Beispiel dienen. Es lässt sich keine allgemein gültige Aussage daraus ableiten, da die Ergebnisse sehr stark vom ursprünglichen Schema
und den Änderungen darauf abhängen. So wird der Algorithmus zum Beispiel bei einem
Schema, welches keinen Split-Knoten besitzt, also der Wurzelblock der einzige Block
112
5.3 Primärspeichermessungen
3
Start
;<=
;<=
4
9
2
7
5
;<=
10
15
13
8
6
;<=
11
12
17
14
19
16
End
18
;<=
Abbildung 92: Die untersuchten Änderungen am evolve-Schema
Knoten einfügen zwischen
3→4
9 → 10
9 → 10 und 11 → 12
15 → 16
Laufzeit
Ergebnis
77.4
84.1
90.2
76.7
(0 - 12 - 8)
(6 - 0 - 14)
(6 - 6 - 8)
(12 - 0 - 8)
Ergebnis: (migrierbar - nicht migrierbar - genauer untersuchen)
Tabelle 2: Laufzeit von preEvolveSelection (in Tausendstel)
ist, keinen Vorteil bieten, da alle Instanzen als „genauer zu untersuchen“ kategorisiert
werden, weil alle vom Wurzelcluster (dem einzigen Cluster) referenziert werden (vergleiche Abschnitt 3.3.1.2). Allgemein kann die Aussage getroffen werden, dass die grobe
Clusterung mehr Zeitersparnis bei einer Schemaevolution bieten kann, je mehr SplitKnoten im zugrunde liegenden Schema vorhanden sind. Dies folgt, weil jeder zusätzliche Split-Knoten einen zusätzlichen Cluster bedeutet, anhand dessen Instanzen durch
preEvolveSelection vor dem Einlagern kategorisiert werden können. Außerdem ist es
vorteilhaft, wenn möglichst viele dieser Split-Knoten einen Block aufspannen, der dem
Wurzelblock direkt untergeordnet ist, da diese potentiell Stellen aufspannen. Je mehr
(potentielle) Stellen in einem Schema vorhanden sind, desto genauer kann die Position
einer Änderung mithilfe der groben Clusterung bestimmt und damit mehr Instanzen aus
anderen Clustern kategorisiert werden.
Aufgrund der starken Verschlechterung der Laufzeit des Weiterschaltens durch die Neuberechnung der Clusterzugehörigkeit und der starken Abhängigkeit der Zeitersparnis bei
einer Schemaevolution vom zugrunde liegenden Schema und den vorgenommenen Änderungen, kann keine allgemeine Empfehlung zum Einsatz der groben Clusterung in der
Praxis gegeben werden. Es ist denkbar, dass diese von einem Benutzer des PMS beim Modellieren eines Schemas manuell eingeschaltet wird, wenn sich das Schema für die grobe
Clusterung eignet und/oder erwartet wird, dass Schemaevolutionen auf diesem Schema
durchgeführt werden.
113
5 Messungen
5.4 Fazit
Nachdem die Grundlagen und Konzepte und deren Umsetzung in den vorigen Kapiteln
beschrieben wurden, runden die Messungen aus diesem Kapitel die Arbeit ab. Es wurden
verschiedene Repräsentationen sowohl von Schemata als auch von Instanzen untersucht.
Bei Schemata wurde durch die Messungen festgestellt, dass die Vorteile, welche eine
Repräsentation mit expliziter Blockstruktur bei Anfragen bezüglich dieser bietet, in der
Praxis nicht zum Tragen kommen. Schemata mit impliziter Blockstruktur sind sowohl im
Primär- als auch im Sekundärspeicher vorzuziehen, sofern die empfohlene XML-DateiSpeicherung eingesetzt wird. Dies wurde durch Messungen des Ein- und Auslagerns der
Daten, als auch durch Messungen des Weiterschaltens von Instanzen bestimmt.
Für Instanzen wurden ebenfalls die beiden Repräsentationen untersucht. Hierbei stellte sich heraus, dass diese im Sekundärspeicher mithilfe von PostgreSQL und einer Repräsentation mit impliziten Knotenzuständen gespeichert werden sollten. Hierbei ist der
Einsatz des XML-Datentyps für die Spalte der serialisierten Knotenzustände empfehlenswert. Diese Variante bietet die performantesten Zugriffe auf Instanzen, sofern sowohl das
Ein- und Auslagern als auch Anfragen auf Kollektionen beachtet werden. Im Primärspeicher sollten Instanzen hingegen in einer Repräsentation mit expliziten Knotenzuständen
dargestellt werden. Diese bietet eine bessere Performanz beim Weiterschalten, da die
implizite Repräsentation hier bei AND-Join-Knoten sehr schlecht abschneidet.
Nicht empfehlenswert ist der Einsatz von JPA zur Persistierung von Daten, da das Einund Auslagern dabei wesentlich mehr Zeit benötigt als bei einer direkten Speicherung.
Ebenfalls nicht eingesetzt werden sollte Oracle, da die Performanz hierbei nicht ausreichend ist. Auffallend war dies besonders bei Anfragen, welche auf serialisierte XML-Daten
getätigt wurden. Dies ist aber auch beim Betrachten der Abbildungen 82 und 87, welche
die Ergebnisse der einzelnen Varianten zusammenfassen, leicht ersichtlich.
Mit den in Abschnitt 4.3.5 vorgestellten Zwischenspeicher-Implementierungen von
TemplateStorage und InstanceStorage wurden keine Messungen durchgeführt. Dies
liegt daran, dass der Vorteil, den ein „Zwischenschalten“ dieser Klassen bringt, stark vom
Zugriffsverhalten auf Schemata und Instanzen abhängig ist und somit keine allgemeinen
Messungen durch die Testumgebung möglich sind. Der Einsatz solcher Zwischenspeicher
ist allerdings generell zu empfehlen, sofern genug Hauptspeicher vorhanden ist, da hierdurch „teure“ Zugriffe auf den Sekundärspeicher beim Laden von Daten unter Umständen
verhindert werden.
Es kann keine allgemeine Empfehlung für den Einsatz der groben Clusterung aus Abschnitt 3.3 abgegeben werden. Für deren Einsatz ist es notwendig, die Clusterzugehörigkeit beim Weiterschalten neu zu berechnen, was diesen Vorgang stark verlangsamt.
Außerdem kann keine allgemeine Einschätzung über die Zeiteinsparungen getroffen werden, welche die grobe Clusterung bei einer Schemaevolution bringt. Diese hängt stark
vom Schema und von den Änderungen daran ab. Somit ist denkbar, die grobe Clusterung durch manuelles Einschalten während dem Modellieren von Schemata einzusetzen.
114
5.4 Fazit
Dadurch kann eine manuelle Einschätzung getroffen werden, ob die grobe Clusterung
bei einer Schemaevolution dieses Schemas einen Vorteil bringt. Allgemein gilt die Aussage, dass die grobe Clusterung mehr Vorteile bietet, je mehr Split-Knoten im Schema
vorhanden sind. Ebenso kann die grobe Clusterung manuell eingeschaltet werden, wenn
bereits beim Modellieren des Schemas bekannt ist, dass Schemaevolutionen auf diesem
durchgeführt werden.
115
5 Messungen
116
6 Verwandte Konzepte
Im Rahmen dieser Arbeit wurde bisher von interpretierten Graphstrukturen als interne
Repräsentation von Prozessen ausgegangen. Dieses Kapitel rekapituliert die Eigenschaften einer solche Repräsentation und stellt andere mit deren Eigenschaften vor.
Bei einer Repräsentation mit interpretierten Graphstrukturen modelliert der Benutzer
einen Prozess als Graph, welcher intern vom PMS auch als solcher repräsentiert wird
[Rei00, HJKW96]. Dieses interpretiert den Graph und führt auf dieser Basis Aktivitäten
aus, welche den Knoten zugeordnet sind. Anfallende Daten werden ebenfalls durch interpretieren einer geeigneten Graphmodellierung abgebildet. Diese Funktionen führt das
PMS auf einem zentralen Server aus, welcher gleichzeitig Mitarbeitern Überprüfungsmöglichkeiten, wie zum Beispiel die Anfragen auf Kollektionen aus Abschnitt 2.4.3 anbieten
kann. Neben der im Laufe dieser Arbeit betrachteten Anwendung der Schemareferenz
von Instanzen, ist es auch möglich, die Schemadaten beim Erzeugen einer Instanz zu
kopieren. Dieses Vorgehen beinhaltet eine einfachere Anwendung von instanzbasierten
Änderungen. Hierbei ist keine Deltaschicht notwendig, welche die Änderungen gegenüber
einem zugrunde liegendem Schema abbildet, sondern die Änderungen können direkt im
instanzeigenen Graph realisiert werden. Nachteil dieser Repräsentation ist, dass diese
sehr viel Speicher benötigt, da jede Instanz die volle Repräsentation der (redundante)
Schemadaten hält.
Neben interpretierten Graphstrukturen können Prozesse auch als verteilte Prozesspartikel dargestellt werden [DKM+ 97, WWWK96]. Dieser Ansatz verfolgt die Idee, jeden
Prozessschritt in einen eigenständigen Automaten [Sch01b] zu übersetzen. Diese Automaten kommunizieren unabhängig von einem zentralen Server über (Netzwerk-)Nachrichten
miteinander und bilden so den Kontroll- und Datenfluss ab. Jeder dieser Automaten besitzt neben der Logik zur Ausführung des eigentlichen Prozessschrittes Vorbedingungen
und nachgelagerte Aktivitäten. Sind alle Vorbedingungen erfüllt, also alle Nachrichten
von vorgelagerten Automaten korrekt eingetroffen, wird die Ausführung des Prozessschrittes aktiviert. Danach werden alle nachgelagerten Aktivitäten abgearbeitet. Dies
bedeutet, dass allen nachgelagerten Automaten die entsprechenden Nachrichten gesendet werden. Der Vorteil dieser Variante besteht darin, dass sie ohne einen zentralen Server
auskommt und die eigenständigen Automaten somit beliebig verteilt werden können: Es
ist denkbar, diese auf beliebigen Rechnern an beliebigen Orten auszuführen, was zum Beispiel in Hinblick auf datenschutzrechtliche Bestimmungen von Vorteil sein kann. Hieraus
folgt allerdings, dass keine einfache globale Steuerungsmöglichkeit des Prozesses besteht.
Ebenso sind instanzbasierte Änderungen nicht möglich, da das Verhalten der einzelnen
verteilten Automaten nicht auf Instanzbasis verändert werden kann.
Als weitere Möglichkeit lassen sich Prozesse als Regelmenge repräsentieren [DHL90,
BMR96, KPRR95, Joe99]. Hierbei wird der Prozess ebenfalls als Graph modelliert, zur
Repräsentation im PMS allerdings zu einer Regelmenge übersetzt. Diese Regeln werden durch Eintreten von Ereignissen getriggert. Hierbei kann eine Regel zusätzliche
Bedingungen enthalten, wie zum Beispiel die Überprüfung auf eine bestimmte Tages-
117
6 Verwandte Konzepte
zeit. Sind alle Bedingungen erfüllt, wird eine Aktion ausgeführt, welche nach Abarbeitung wiederum Ereignisse auslösen kann. Abgebildet werden können solche regelbasierten Systeme zum Beispiel durch (erweitere) RDBMS. Hierbei bilden Datenbank-Trigger
[Pos09b, Ora09b, IBM09b] die Regeln ab. Innerhalb dieser können verschiedene Bedingungen geprüft und Aktionen ausgeführt werden. Der Datenfluss wird bei diesem Konzept ebenfalls durch die Regeln definiert, so kann in Datenbank-Triggern zum Beispiel
auf bestimmte Felder bestimmter Tabellen zugegriffen werden. Regelbasierte PMS benötigen wenig zusätzlichen Entwicklungsaufwand, da viele RDBMS die Möglichkeit von
Datenbank-Triggern bereits bieten. Die Prozessmodellierung ist allerdings aufgrund der
großen Zahl an Regeln, welche zur Modellierung benötigt werden, sehr komplex. Eine
weitere Auswirkung der Größe der Regelmenge ist, dass diese schnell unübersichtlich
und somit schlecht wartbar wird. Ebenso werden instanzbasierte Änderungen nur sehr
bedingt unterstützt.
Ebenso lässt sich eine weitere Repräsentation direkt auf RDBMS definieren. Hierbei besteht eine Instanz aus einem Eintrag in einer Tabelle. Diese besitzt außer den Daten des
Prozesses an sich noch eine weitere Spalte, welche den Zustand angibt. Auf Basis dieses
Zustandsfelds kann eine Selektion zur Ansicht für den Benutzer generiert werden. Dies ist
zum Beispiel durch Einsatz von Views möglich [ISO92]. Hierbei existiert für jeden Benutzer eine View, welche die Einträge in der Tabelle auf Basis des Zustandsfelds filtert. Somit
bekommt der Benutzer diejenigen Instanzen in genau den Zuständen angezeigt, für die
er als Bearbeiter vorgesehen ist. Nachteil ist, dass auch hier instanzbasierte Änderungen
nur sehr schwierig umsetzbar sind.
Prozesse lassen sich intern auch als Menge von Operationen repräsentieren, wobei die einzelnen (Prozess-)Instanzen als Datenobjekte von Operation zu Operation weitergereicht
werden [KRW90, BMR96]. Hierbei ist allerdings die Realisierung von Parallelverzweigungen sehr schwierig, da die Instanz nur mithilfe eines Datenobjekts repräsentiert wird.
Somit kann nicht das gleiche Datenobjekt an zwei unterschiedliche Operationen geleitet
werden. Dies wäre durch eine Duplizierung des Datenobjekts zwar möglich, diese Datenobjekte müssten beim Zusammenführen der Parallelverzweigung allerdings aufwändig
vereint werden. Auch die Unterstützung von instanzbasierten Änderungen ist nur sehr
eingeschränkt möglich.
In diesem Kapitel wurden verschiedene Konzepte vorgestellt, Prozesse zu repräsentieren. Hierzu zählen die in dieser Arbeit untersuchten interpretierten Graphstrukturen,
aber auch eine Repräsentation als verteilte Prozesspartikel oder als Regelmenge. Ebenso lassen sich Prozesse direkt auf RDBMS mithilfe von Views repräsentieren. Als letzte
Möglichkeit wurde eine Repräsentation mithilfe von Datenobjekten vorgestellt, welche
zwischen Operationen einer festen Menge weitergereicht werden.
118
7 Zusammenfassung und Ausblick
PMS erlangen aufgrund der anwachsenden Globalisierung in den letzten Jahren immer
mehr Aufmerksamkeit. Die Systeme müssen in Zukunft auch von weltumspannenden
Firmen eingesetzt werden können. Hierzu ist eine performante Verarbeitung von Anfragen, welche an die Systeme gestellt werden, von zentraler Bedeutung. Dies muss auch
gewährleistet sein, wenn viele Prozesse gleichzeitig ablaufen. Neben der eingesetzten Softwarearchitektur spielt hierbei die Repräsentation der Prozessdaten eine zentrale Rolle.
In dieser Arbeit wurden verschiedene Varianten der Repräsentation von Prozessdaten
untersucht. Durch die Untersuchungen wurden optimal geeignete Repräsentationen der
Prozessdaten, welche sich in Schema- und Instanzdaten gliedern, sowohl im Primär- als
auch im Sekundärspeicher ermittelt. Dieses Kapitel fasst die Arbeit zusammen und stellt
Ideen zu weiteren Untersuchungen des Themengebiets vor.
7.1 Zusammenfassung
Im Rahmen dieser Arbeit wurden Varianten zur Repräsentation von Prozessdaten untersucht. Diese gliedern sich in Schemata und Instanzen. Hierbei existieren für diese beiden
jeweils zwei Repräsentationen, welche sowohl qualitativ als auch quantitativ untersucht
wurden.
Nach der Vorstellung der Grundlagen wurden darauf aufbauende Konzepte erläutert.
Hierzu zählt die Partitionierung von Schemata. Mithilfe dieser ist es möglich, nur vom
System benötigte Daten im Primärspeicher zu halten. Momentan nicht benötigte Daten können dynamisch aus dem Sekundärspeicher nachgeladen und in die vorhandenen
Primärspeicherdaten eingegliedert werden. Hierfür wurde zunächst eine konzeptionelle
Umsetzung beschrieben. Im Anschluss wurde die grobe Clusterung vorgestellt, mit deren
Hilfe die Laufzeit der Schemaevolution verringert werden kann. Hierbei werden Instanzen
in einem Clusterbaum gehalten, welcher die grobe Position angibt, an der die Instanz in
ihrer Ausführung steht. Es wurden Algorithmen vorgestellt, mit deren Hilfe die nötige
zustandsbasierte Verträglichkeit allein anhand der Einordnung einer Instanz im Clusterbaum überprüft werden kann. Da die grobe Clusterung den Zustand einer Instanz nicht
genau angibt, können auf dieser Basis nicht alle Instanzen bewertet werden und ein Teil
muss weiterhin mit den bekannten Methoden [Rin04, Jur06] untersucht werden. Dafür
wird bei Einsatz der groben Clusterung nur wenig Speicherplatz benötigt, was das Verfahren praxistauglich macht.
Der konzeptionellen Untersuchung schloss sich eine Umsetzung an, welche die vorgestellten Grundlagen und Konzepte implementiert. Hierbei wurden die verschiedenen ermittelten Repräsentationen mithilfe von Java™ im Primärspeicher umgesetzt. Da auch
eine persistente Speicherung der Prozessdaten in der Praxis notwendig ist, wurden die
verschiedenen Repräsentationen auch als Sekundärspeicherstrukturen umgesetzt. Hierbei
119
7 Zusammenfassung und Ausblick
wurde auf eine XML-Datei-Speicherung und die Persistierung mithilfe verschiedener, am
Markt erhältlicher relationalen Datenbanksysteme zurückgegriffen.
Mithilfe dieser Implementierung wurden Messungen anhand von verschiedenen Anwendungsszenarien durchgeführt. Hierbei wurden neben dem Ein- und Auslagern der Prozessdaten auch Anfragen auf Kollektionen betrachtet. Diese Sekundärspeichermessungen
wurden für die verschiedenen Sekundärspeichervarianten durchgeführt. Deren Ergebnisse
wurden anschließend verglichen und sowohl die optimale Sekundärspeicherrepräsentation,
als auch die optimale Sekundärspeichervariante ermittelt. Es wurden ebenfalls Messungen
der verschiedenen Primärspeicherrepräsentationen durchgeführt. Hierbei wurde ebenfalls
eine optimale Repräsentation anhand eines Anwendungsszenarios sowohl für Schemata
als auch Instanzen ermittelt. Abschließend wurde die Performanz der groben Clusterung
gemessen und überprüft, ob diese den konzeptionellen Erwartungen gerecht wird.
Aufgrund der qualitativen und quantitativen Untersuchungen wurden optimale Repräsentationen der einzelnen Datenstrukturen ermittelt. Mithilfe dieser Repräsentationen
ist eine Implementierung eines PMS’ mit einer optimalen internen Darstellung der Prozessdaten möglich.
7.2 Ausblick
Dieser Abschnitt gibt abschließend einen Ausblick auf Themen, welche in zukünftigen
Arbeiten untersucht werden können.
Es kann die Realisierung benutzerdefinierter Blöcke untersucht werden. Blöcke wurden
im Rahmen dieser Arbeit anhand von Split-Knoten und deren zugehörigen Join-Knoten
definiert. Auf dieser Idee basiert sowohl die Partitionierung von Schemata als auch die
grobe Clusterung von Instanzen. Es ist denkbar, dass Blöcke manuell beim Modellieren des Schemas definiert werden. Setzt man dann auf Basis dieser Blöcke die grobe
Clusterung um, so sind unter Umständen wesentlich bessere Ergebnisse von der groben
Clusterung bei einer Schemaevolution zu erwarten, da der Modellierer die Zusammenhänge von Knoten besser feststellen kann. Bedacht werden muss hierbei allerdings, dass
durch Einführung benutzerdefinierter Blöcke weitere Strukturen im Schema notwendig
werden, die diese Blockstruktur abbilden. Eine implizite Darstellung der Blockstruktur
ist unter Umständen nicht mehr möglich.
Ebenfalls untersucht werden kann, ob instanzbasiert geänderte Instanzen trotz des Widerspruchs in Abschnitt 3.4.4 im Clusterbaum gehalten werden können. Hierbei könnte
eine Art „Clusterdelta“ eingesetzt werden, welches die strukturellen Änderungen der instanzbasiert geänderten Instanz abbildet und zusätzlich zur Instanz vom Clusterbaum
referenziert wird. Hiermit ist es unter Umständen möglich, die Verträglichkeit dieser
instanzbasiert geänderten Instanzen bei einer Schemaevolution auch ohne Einlagerung
festzustellen.
120
A Abbildungsverzeichnis
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Ein Schema mit zwei Instanzen . . . . . . . . . . . . . . . .
Ein ADEPT2-Schema . . . . . . . . . . . . . . . . . . . . .
Die Blockstruktur eines ADEPT2-Schemas . . . . . . . . . .
Knotenzustände und -übergänge (in Anlehnung an [Rei00])
Eine ADEPT2-Instanz während Knoten 10 ausgeführt wird
Die Knoten eines Schemas und deren Knotenattribute . . .
Schemakopie vs. Schemareferenz . . . . . . . . . . . . . . .
Instanz mit impliziter und expliziter Markierung . . . . . .
Clusterung . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Instanzbasierte Änderungen mit Deltaschicht . . . . . . . .
Kapselung bei Einsatz einer strukturellen Deltaschicht . . .
Die ADEPT2-Architektur [RDR+ 09] . . . . . . . . . . . . .
Die verschiedenen Fälle des Weiterschaltens . . . . . . . . .
Beispiel einer Schemaevolution . . . . . . . . . . . . . . . .
Exemplarische Organisation eines Unternehmens . . . . . .
Speicherhierarchie . . . . . . . . . . . . . . . . . . . . . . .
Vertikale Partitionierung eines Schemas . . . . . . . . . . .
Horizontale Partitionierung eines Schemas . . . . . . . . . .
Partitionierungsschwierigkeiten bei Bearbeiterzuordnungen .
Blockstruktur mit Hierarchie . . . . . . . . . . . . . . . . .
Knoten ohne Vorgänger-/Nachfolgerbeziehung . . . . . . . .
Verschiedene Wege vom Start-Knoten zu Knoten 12 . . . .
Schemarepräsentation mit expliziter Blockstruktur . . . . .
Hierarchiedarstellung bei expliziter Blockstruktur . . . . . .
Hierarchie der Blockstruktur und Clusterbaum . . . . . . .
Beispiel für die grobe Clusterung . . . . . . . . . . . . . . .
Beispiel einer Schemaevolution bei grober Clusterung . . . .
Zuordnung von Änderungen zu einer Stelle . . . . . . . . . .
COMPACT und LEAKY . . . . . . . . . . . . . . . . . . .
Änderung im Wurzelblock . . . . . . . . . . . . . . . . . . .
Kapselung bei Einsatz der erweiterten Deltaschicht . . . . .
Deltaschicht mit expliziter Blockinformation . . . . . . . . .
Schemaevolution mit instanzbasierten Änderungen . . . . .
Test- und Database-Schnittstelle . . . . . . . . . . . . . . .
Architektur der Workflow-Testumgebung mit Schnittstellen
Speicherverbrauch von Maps . . . . . . . . . . . . . . . . . .
Template-Schnittstelle . . . . . . . . . . . . . . . . . . . . .
Die Klasse DeltaLayer . . . . . . . . . . . . . . . . . . . . .
Instance-Schnittstelle . . . . . . . . . . . . . . . . . . . . .
Die Klasse Cluster . . . . . . . . . . . . . . . . . . . . . . .
Speichertrennung anhand von Storage-Schnittstellen . . . .
Schemarepräsentationen im Primär- und Sekundärspeicher .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
4
5
6
7
8
9
10
11
11
12
13
16
17
18
19
22
22
24
24
27
27
30
31
33
35
36
37
38
39
42
45
47
50
51
52
54
56
57
58
61
62
vii
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
82
81
84
83
85
86
viii
TemplateStorage-Schnittstelle . . . . . . . . . . . . . . . . . .
TemplateSerialization-Schnittstelle . . . . . . . . . . . . . .
XML-Repräsentationen von Schemata . . . . . . . . . . . . . .
Schemata mit expliziter Blockstruktur im RDBMS . . . . . . .
Schemata mit expliziter Blockstruktur in XML im RDBMS . .
Schemata mit impliziter Blockstruktur im RDBMS . . . . . . .
Schemata mit expliziter Blockstruktur mit JPA . . . . . . . . .
Schemata mit impliziter Blockstruktur mit JPA . . . . . . . . .
InstanceStorage-Schnittstelle . . . . . . . . . . . . . . . . . .
Instanzrepräsentation im Primär- und Sekundärspeicher . . . .
InstanceSerialization-Schnittstelle . . . . . . . . . . . . . .
XML-Repräsentationen von Instanzen . . . . . . . . . . . . . .
Instanzen mit expliziten Knotenzuständen im RDBMS . . . . .
Instanzen mit expl. Knotenzuständen in XML im RDBMS . . .
Instanzen mit impliziten Knotenzuständen im RDBMS . . . . .
Instanzen mit expliziten Knotenzuständen mit JPA . . . . . . .
Instanzen mit impliziten Knotenzuständen mit JPA . . . . . . .
Caching Storage . . . . . . . . . . . . . . . . . . . . . . . . . .
Das einfache Schema für die Messungen . . . . . . . . . . . . .
Das komplexe Schema für die Messungen . . . . . . . . . . . . .
Das „evolve“-Schema für die Messungen . . . . . . . . . . . . . .
De-/Serialisieren eines Blocks . . . . . . . . . . . . . . . . . . .
De-/Serialisieren von Schemata . . . . . . . . . . . . . . . . . .
De-/Serialisieren von Instanzen . . . . . . . . . . . . . . . . . .
Messergebnisse für Schemata bei einer XML-Datei-Speicherung
Messergebnisse für Instanzen bei einer XML-Datei-Speicherung
Anfragen auf Kollektionen bei XML-Datei-Speicherung . . . . .
Messergebnisse von Schemata bei Einsatz von PostgreSQL . . .
Messergebnisse von Instanzen bei Einsatz von PostgreSQL . . .
Anfragen auf Kollektionen bei Einsatz von PostgreSQL . . . . .
Messergebnisse von Schemata bei Einsatz von Oracle . . . . . .
Messergebnisse von Instanzen bei Einsatz von Oracle . . . . . .
Anfragen auf Kollektionen bei Einsatz von Oracle . . . . . . . .
Messergebnisse von Schemata bei Einsatz von DB2 . . . . . . .
Messergebnisse von Instanzen bei Einsatz von DB2 . . . . . . .
Anfragen auf Kollektionen bei Einsatz von DB2 . . . . . . . . .
Geschwindigkeitsfaktoren von Schemata bei Einsatz von JPA .
Geschwindigkeitsfaktoren von Instanzen bei Einsatz von JPA .
Performanzkoeffizienten cSchema der einzelnen Varianten . . . .
Vergleich Ein-/Auslagern von Schemata der versch. Varianten .
Performanzkoeffizienten cInstanz der einzelnen Varianten . . . .
Vergleich Ein-/Auslagern von Instanzen der versch. Varianten .
Vergleich der Anfragen auf Kollektionen der versch. Varianten .
Performanzkoeffizienten cKollektion der einzelnen Varianten . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
63
63
64
66
66
67
67
68
68
69
69
70
71
71
71
71
72
72
79
79
79
81
82
83
84
85
87
88
90
91
92
93
94
96
97
98
99
100
101
102
103
104
105
106
87
88
89
90
91
92
Vereinigte Performanzkoeffizienten der einzelnen Varianten . . .
Primärspeicherverbrauch von Schemata . . . . . . . . . . . . .
Laufzeit beim Weiterschalten (in Tausendstel des Grundwertes)
Primärspeicherverbrauch von Instanzen . . . . . . . . . . . . .
Weiterschalten mit & ohne Clusterung (in Tausendstel) . . . . .
Die untersuchten Änderungen am evolve-Schema . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
107
108
109
110
112
113
ix
x
B Tabellenverzeichnis
1
2
Explizite und implizite Realisierung von Blöcken . . . . . . . . . . . . . . 32
Laufzeit von preEvolveSelection (in Tausendstel) . . . . . . . . . . . . . 113
xi
xii
C Literaturverzeichnis
[Adv09]
Advanced Micro Devices, Inc.: AMD Athlon™ XP Product Information.
Webseite, 2009.
http://www.amd.com/usen/Processors/ProductInformation/
0„30_118_3734_11672,00.html,
abgerufen am 14. Dezember 2009.
[Bau00]
Bauer, T.: Effiziente Realisierung unternehmensweiter WorkflowManagement-Systeme. Dissertation, Universität Ulm, Oktober 2000.
[BMR96]
Barbara, D., Mehrotra, S. und Rusinkiewicz, M.: INCAs: Managing
Dynamic Workflows in Distributed Environments. Journal of Database Management, Special Issue on Miltidatabases, 7(1):5–15, 1996.
[bpm09]
Business Process Modeling Notation (BPMN). Technischer Bericht Version
1.2, OMG, Januar 2009.
[Can09]
Canocical Ltd.: Ubuntu. Webseite, 2009. http://www.ubuntu.com, abgerufen am 14. Dezember 2009.
[Cle06]
Clements, A.: Principles of Computer Hardware.
Press, 4 Auflage, März 2006.
[CLRS09]
Cormen, T.H., Leiserson, C.E., Rivest, R.L. und Stein, C.: Introduction to Algorithms. MIT Press, 3. Auflage, September 2009.
[Cod70]
Codd, E. F.: A relational model of data for large shared data banks. Commun. ACM, 13(6):377–387, 1970.
[DD90]
Domschke, W. und Drexl, A.: Einführung in Operations Research.
Springer, 6. Auflage, Oktober 1990.
[DHL90]
Dayal, U., Hsu, M. und Ladin, R.: Organizing Long-Running Activities
with Triggers and Transactions. In: Proc. ACM SIGMOND Int’l Conf. on
the Management of Data, Seiten 204–214, 1990.
[DKM+ 97]
Das, S., Kochut, K., Miller, J., Sheth, A. und Worah, D.: ORBWork: A Reliable Distributed CORBA-based Worflow Enactment System
for METEOR2 . Technischer Report #UGA-CS-TR-97-001, Department of
Computer Science, University of Georgia, 1997.
[DN67]
Dahl, Ole-Johan und Nygaard, Kristen: Class and Subclass Declarations. In: Buxton, J. N. (Herausgeber): Simulation Programming Languages, Seiten 158–174, 1967.
[For09]
Forschner, A.: Fortschrittliche Datenflusskonzepte für flexible Prozessmodelle. Diplomarbeit, Universität Ulm, Juni 2009.
[GJSB05]
Gosling, J., Joy, B., Steele, G. und Bracha, G.: Java™ Language
Specification, The (3rd Edition). Addison Wesley, 3. Auflage, Juni 2005.
Oxford University
xiii
[HJKW96]
Heimann, P., Joeris, G., Krapp, C.-H. und Westfechtel, B.: DYNAMITE: Dynamic Task Nets for Software Process Management. In: 18.
Int’l Conf. Software Engineering (ICSE’96), Seiten 331–341, März 1996.
[IBM09a]
IBM: DB2. Webseite, 2009. http://www.ibm.com/db2, abgerufen am 4.
September 2009.
[IBM09b]
IBM:
IBM
DB2
Database
for
Linux,
UNIX
and
Windows
Information
Center.
Webseite,
2009.
http://publib.boulder.ibm.com/infocenter/db2luw/v9/, abgerufen am
8. September 2009.
[ISO92]
ISO/IEC 9075:1992: Database Language SQL. International Organization
for Standardization, 1992.
[JL96]
Jones, R. und Lins, R. D.: Garbage Collection: Algorithms for Automatic
Dynamic Memory Management. Wiley, September 1996.
[JMSW04]
Jurisch, M., Mihalca, T., Sauter, P. und Waimer, M.: Verwaltung
von Prozessinstanzen in Workflow Systemen. Praktikum ADEPT2, Institut
für Datenbanken und Informationssysteme, Universität Ulm, April 2004.
[Joe99]
Joeris, G.: Defining Flexible Workflow Execution Behaviors.
In:
Proc. Workshop on Enterprise-Wide and Cross-Enterprise WorkflowManagement: Concepts, Systems, Applications, CEUR Workshop, Band 24,
Seiten 49–55, 1999.
[Jur06]
Jurisch, M.: Konzeption eines Rahmenwerkes zur Erstellung und Modifikation von Prozessvorlagen und -instanzen. Diplomarbeit, Universität Ulm,
März 2006.
[Knu97]
Knuth, D. E.: Art of Computer Programming, Volume 1: Fundamental
Algorithms (3rd Edition). Addison-Wesley Professional, 3. Auflage, Juli
1997.
[KPRR95]
Kappel, G., Pröll, B., Rausch-Schott, S. und Retschitzegger,
W.: Workflow Manahement Based on Objects, Rules and Roles. IEEE Data
Engineering Bulletin, Special Issue on Worflow Systems, 18(1), März 1995.
[KR10]
Kreher, U. und Reichert, M.: Speichereffiziente Repräsentation instanzspezifischer Änderungen in Prozess-Management-Systemen. Ulmer Informatik Berichte (erscheint Januar 2010), Universität Ulm, Ulm, 2010.
[Kre02]
Kreher, U.: Performanzaspekte bei der Programmierung mit Java™ - Eine eingehende Untersuchung von Threads, Garbage Collection und ObjektPools. Diplomarbeit, Universität Ulm, Dezember 2002.
[KRRD09]
Kreher, U., Reichert, M., Rinderle-Ma, S. und Dadam, P.: Effiziente Repräsentation von Vorlagen- und Instanzdaten in Prozess-Management-
xiv
Systemen. Ulmer Informatik Berichte UIB-2009-08, Universität Ulm, Ulm,
2009.
[KRW90]
Karbe, B., Ramsperger, N. und Weiss, P.: Support of Cooperative Work by Electronic Circulation Folders. ACM SIGOIS Bulletin,
11(2+3):109–117, April 1990.
[LA94]
Leymann, F. und Altenhuber, W.: Managing Business Processes as an
Information Resource. IBM Systems Journal, 33(2):326–348, 1994.
[Ley01]
Leymann, F.: Web Services Flow Language (WSFL 1.0). IBM Technical
White Paper, IBM, 2001.
[LRR04]
Lauer, M., Rinderle, S. und Reichert, M.: Repräsentation von
Schema- und Instanzobjekten in adaptiven Prozess–Management–Systemen.
In: Workshop Geschätfsprozessorientierte Architekturen (Informatik ’04),
Band LNI-P51, Seiten 555–560, 2004.
[MB02]
Means, Scott und Bodie, Michael: The Book of SAX: The Simple API
for XML. No Starch Press, 1 Auflage, Juni 2002.
[Mic09]
Micheler, F.: Konzeption, Implementierung und Integration einer Komponente für die Erstellung intelligenter Formulare. Diplomarbeit, Universität Ulm, Juni 2009.
[MWW+ 98] Muth, P., Wodtke, D., Weissenfels, J., Weikum, G. und Kotz Dittrich, A.: Enterprise-Wide Workflow Management Based on State and
Activity Charts. NATO Advanced Science Institutes (ASI), Series F: Computer and Systems Sciences, 164:281–303, 1998.
[Obj06]
Object Management Group: Unified Modeling Language: Infrastructure, Version 2.0, März 2006.
[OOW93]
O’Neil, Elizabeth J., O’Neil, Patrick E. und Weikum, Gerhard:
The LRU-K page replacement algorithm for database disk buffering. SIGMOD Rec., 22(2):297–306, 1993.
[Ora09a]
Oracle: Database. Webseite, 2009. http://www.oracle.com/database, abgerufen am 4. September 2009.
[Ora09b]
Oracle: Oracle Database Documentation Library - 10g Release 2. Webseite, 2009. http://download.oracle.com/docs/cd/B19306_01/index.htm,
abgerufen am 8. September 2009.
[Pos09a]
PostgreSQL. Webseite, 2009. http://www.postgresql.org, abgerufen am
4. September 2009.
[Pos09b]
PostgreSQL: PostgreSQL 8.4.0 Documentation.
Webseite, 2009.
http://www.postgresql.org/docs/8.4/static, abgerufen am 8. September
2009.
xv
[RD98]
Reichert, M. und Dadam, P.: ADEPTflex – Supporting Dynamic Changes of Workflows Without Losing Control. Journal of Intelligent Information
Systems, 10(2):93–129, 1998.
[RDR+ 09]
Reichert, M., Dadam, P., Rinderle-Ma, S., Jurisch, M., Kreher,
U. und Göser, K.: Architectural Principles and Components of Adaptive
Process Management Technology. In: PRIMIUM Process Inoovation for
Enterprise Software, Band P-151 der Reihe Lecture Notes in Informatics Proceedings, Seiten 81–97. Gesellschaft für Informatik, April 2009.
[Red09]
RedHat: Hibernate. Webseite, 2009. http://www.hibernate.org, abgerufen
am 4. September 2009.
[Rei00]
Reichert, M.: Dynamische Ablaufänderungen in Workflow-ManagmentSystemen. Dissertation, Universität Ulm, Mai 2000.
[Rin04]
Rinderle, S.: Schema Evolution in Process Management Systems. Dissertation, Universität Ulm, Oktober 2004.
[Rou02]
Roubtsov, V.: Java Tip 130: Do you know your data size? Webseite,
2002. http://www.javaworld.com/javaworld/javatips/jw-javatip130.html,
abgerufen am 6. September 2009.
[Sch01a]
Schöning, U.: Algorithmik. Spektrum Akademischer Verlag, 1. Auflage,
September 2001.
[Sch01b]
Schöning, U.: Theoretische Informatik - kurz gefasst. Spektrum Akademischer Verlag, 4. Aufl. Auflage, März 2001.
[Sun06]
Sun Microsystems Inc.: JSR 220: Enterprise JavaBeans, Version 3.0 Java Persistence API, Mai 2006.
[Sun09a]
Sun Microsystems Inc.: HotSpot Group.
Webseite, 2009.
http://openjdk.java.net/groups/hotspot, abgerufen am 4. September
2009.
[Sun09b]
Sun Microsystems Inc.: Java Database Connectivity (JDBC). Webseite, 2009. http://java.sun.com/products/jdbc, abgerufen am 4. September
2009.
[Sun09c]
Sun Microsystems Inc.: Java HotSpot VM Options.
Webseite,
2009. http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp, abgerufen am 6. September 2009.
[Sun09d]
Sun Microsystems Inc.: JDK™ 6 Documentation - Reflection, 2009.
http://java.sun.com/javase/6/docs/technotes/guides/reflection, abgerufen
am 8. September 2009.
xvi
[vv04]
van der Aalst, W. und van Hee, K.: Workflow Management: Models,
Methods, and Systems (Cooperative Information Systems). The MIT Press,
März 2004.
[W3C99]
W3C - World Wide Web Consortium: XML Path Language (XPath)
1.0. Webseite, November 1999. http://www.w3.org/TR/1999/REC-xpath19991116, abgerufen am 9. November 2009.
[W3C06]
W3C - World Wide Web Consortium: Extensible Markup Language (XML) 1.1 (Second Edition).
Webseite, September 2006.
http://www.w3.org/TR/2006/REC-xml11-20060816/, abgerufen am 4.
September 2009.
[WWWK96] Wodtke, D., Weißenfels, J., Weikum, G. und Kotz-Dittrich,
A.: The Mentor Project: Steps Towards Enterprise-Wide WorkflowManagement. In: Proc. of the 12. IEEE International Conference on Data
Engineering, Seiten 556–565, März 1996.
xvii
xviii
D Algorithmen
D.1 getNodeRelation
1
g e t N o d e R e l a t i o n ( Node n1 , Node n2 ) {
2
if ( n1 . t o p o l o g i c a l I D == n2 . t o p o l o g i c a l I D )
return EQUAL ;
3
4
5
//
//
//
//
6
7
8
9
Untersuch e n der reduziert e n Wege von n1 und n2 , um den ersten
gemeinsam e n Split - Knoten zu finden :
Wird merken uns den aktuellen Split - Knoten auf dem Weg von n1 und n2
in den jeweilige n Variablen
10
Node splitNode N1 = n1 . splitNode ;
Node splitNode N2 = n2 . splitNode ;
11
12
13
// u . U . muessen spaeter die Zweige untersucht werden , somit merken wir uns
// diese ebenfalls
int l a s t B r a n c h I D N 1 = n1 . branch ;
int l a s t B r a n c h I D N 2 = n2 . branch ;
14
15
16
17
18
while ( splitNodeN 1 . t o p o l o g i c a l I D != splitNode N 2 . t o p o l o g i c a l I D ) {
// in jedem Durchlauf gehen wir einen Schritt " zurueck " auf dem
// reduziert en Weg desjenigen Knotens , welcher topologis c h n a c h g e l a g e r t
// ist
if ( splitNode N 1 . t o p o l o g i c a l I D > splitNode N 2 . t o p o l o g i c a l I D ) {
l a s t B r a n c h I D N 1 = splitNode N 1 . branch ;
splitNodeN 1 = splitNode N 1 . splitNode ;
} else {
l a s t B r a n c h I D N 2 = splitNode N 2 . branch ;
splitNodeN 2 = splitNode N 2 . splitNode ;
}
}
19
20
21
22
23
24
25
26
27
28
29
30
31
if ( l a s t B r a n c h I D N 1 != l a s t B r a n c h I D N 2 )
// wir haben den ersten gemeinsame n Split - Knoten gefunden , kommen
// allerdings von u n t e r s c h i e d l i c h e n Zweigen
return D I F F E R E N T _ B R A N C H ;
32
33
34
35
36
if ( n1 . t o p o l o g i c a l I D > n2 . t o p o l o g i c a l I D )
return SUCCESSOR ;
else
return PREDECESS O R ;
37
38
39
40
41
}
D.2 findPositions
1
2
3
4
5
6
7
8
9
/*
* Stellenty p
*/
enum P o s i t i o n T y p e {
// die Stelle ist nicht passierbar ohne einen geaendert e n Knoten
// ausgefueh r t zu haben
COMPACT ,
// die Stelle kann auch ohne Ausfuehrun g eines geaenderte n Knotens
// passiert werden
xix
D Algorithmen
LEAKY
10
11
}
12
13
14
15
16
17
18
19
20
/*
* Sucht die Stellen der geaendert en Knoten
*
* Liefert eine Map zurueck , die der Stellen ID ein Paar aus Stellentyp und
* Knoten - IDs zuweist . Diese Knoten - IDs sind diejenige n von geaendert e n
* Knoten , welche der e n t s p r e c h e n d e n Stelle zugeordnet sind .
*/
Map < Integer , Pair < PositionType , Set < Integer > > > f i n d P o s i t i o n s ( DeltaLayer delta ) {
21
// I n i t i a l i s i e r u n g des R u e c k g a b e o b j e k t s
Map < Integer , Pair < PositionType , Set < Integer > > > positions =
new HashMap < Integer , Pair < PositionType , Set < Integer > > >() ;
22
23
24
25
// Eine Menge an Knoten , welche geaendert wurden in der gegebenen
// D e l t a s c h i c h t .
// Diese Knoten sind die Ziele von h i n z u g e f u e g t e n und geloescht e n Kanten
// [ Jur06 ]
Set < Integer > c h a n g e d N o d e s = new HashSet < Integer >() ;
c h a n g e d N o d e s . addAll ( delta . g e t A d d e d E d g e s B a c k w a r d s () . keySet () ) ;
c h a n g e d N o d e s . addAll ( delta . g e t D e l e t e d E d g e s B a c k w a r d s () . keySet () ) ;
26
27
28
29
30
31
32
33
// diejenigen Knoten , die im u r s p r u e n g l i c h e n Schema nicht vorhanden sind ,
// koennen nicht " geaendert " sein und muessen somit aus der Menge entfernt
// werden
for ( int nID : new HashSet < Integer >( c h a n g e d N o d e s ) ) {
if ( delta . g e t A d d e d N o d e s () . contains ( nID ) )
c h a n g e d N o d e s . remove ( nID ) ;
34
35
36
37
38
39
40
Node n = template . getNode ( nID ) ;
// sicher gehen , dass dies keine Aenderung im Wurzelblo c k ist
if ( n . getType () == Type . NORMAL &&
n . g e t S p l i t N o d e I D () == Template . S T A R T _ N O D E _ I D )
continue ;
41
42
43
44
45
}
46
47
// Die Stellen der einzelnen geaendert en Knoten werden nun gesucht
// p o s i t i o n s O f C h a n g e ist das Ergebnis dieser Berechnung :
// Einer Stellen - ID sind Knoten - IDs von geaenderte n Knoten zugeordnet
Map < Integer , Set < Integer > > p o s i t i o n s O f C h a n g e s =
new HashMap < Integer , Set < Integer > >() ;
48
49
50
51
52
53
54
for ( int c h a n g e d N o d e I D : c h a n g e d N o d e s ) {
// wir suchen die Stellen ID von c h a n g e d N o d e I D
55
56
57
// splitNode ID ist die ID des zuletzt besuchten Split - Knotens
// der Wert wird auf den Split - Knoten , der dem geaendert e n Knoten
// direkt u e b e r g e o r d n e t ist , i n i t i a l i s i e r t
int splitNode I D = template . getNode ( c h a n g e d N o d e I D ) . g e t S p l i t N o d e I D () ;
58
59
60
61
62
// das Ergebnis unserer Suche : der zuletzt besuchte Split - Knoten
int t o p L e v e l S p l i t N o d e ;
63
64
65
// hole das Objekt des geaenderte n Knotens vom Schema
Node changedNod e = template . getNode ( c h a n g e d N o d e I D ) ;
66
67
68
// Ueberpruefung , ob der geaenderte Knoten selbst ein Split - Knoten ist ,
// welcher einen Block aufspannt , der dem Wurzelblo ck
// direkt u n t e r g e o r d n e t ist . In diesem Fall ist die Block - ID des
69
70
71
xx
D.2 findPositions
//
//
//
//
if
72
73
74
75
76
77
78
79
Knotens ( und somit die Stellen - ID ) nicht ueber
changedNo de . g e t S p l i t N o d e I D () verfuegba r ( dieser Wert ist gleich
Template . S T A R T _ N O D E _ I D ) , sondern die Block - ID ist aequivale n t
zur ID des Split - Knotens selbst
( changedNo d e . g e t S p l i t N o d e I D () == Template . S T A R T _ N O D E _ I D &&
changedNo d e instanceof SplitNode ) {
splitNodeI D = c h a n g e d N o d e I D ;
topLevelSplitNode = changedNodeID;
80
} else if ( changedNo d e . g e t S p l i t N o d e I D () == Template . S T A R T _ N O D E _ I D &&
changedNo d e instanceof JoinNode ) {
// fuer einen Join - Knoten , der dem Wurzelblo c k direkt unter // geordnet ist , ist die Block - ID wie folgt verfuegbar :
splitNodeI D =
(( JoinNode ) changedNo de ) . g e t C o r r e s p o n d i n g S p l i t N o d e I D () ;
topLevelSplitNode =
(( JoinNode ) changedNo de ) . g e t C o r r e s p o n d i n g S p l i t N o d e I D () ;
81
82
83
84
85
86
87
88
89
} else {
// S t a n d a r d f a l l :
// merke in splitNode I D jeweils die ID des naechst " hoeher "
// gelegenen Split - Knotens
while ( splitNode ID != Template . S T A R T _ N O D E _ I D ) {
t o p L e v e l S p l i t N o d e = splitNodeI D ;
splitNode I D = template . getNode ( splitNode I D ) . g e t S p l i t N o d e I D () ;
}
}
90
91
92
93
94
95
96
97
98
99
// in t o p L e v e l S p l i t N o d e steht nun die ID desjenige n Split - Knoten ,
// welcher einen Block aufspannt , der dem Wurzelbloc k direkt
// u n t e r g e o r d n e t ist . Somit ist dies unsere Stellen ID
100
101
102
103
// Die Map muss u . U . i n i t i a l i s i e r t werden
if (! p o s i t i o n s O f C h a n g e s . containsKe y ( t o p L e v e l S p l i t N o d e ) )
p o s i t i o n s O f C h a n g e s . put ( topLevelSpli t No de , new HashSet < Integer >() ) ;
104
105
106
107
p o s i t i o n s O f C h a n g e s . get ( t o p L e v e l S p l i t N o d e ) . add ( c h a n g e d N o d e I D ) ;
108
109
}
110
111
112
113
// Die S t e l l e n t y p e n der einzelnen Stellen muessen bestimmt werden
114
115
116
// fuer alle gefundene n Stellen ...
for ( int positionID : p o s i t i o n s O f C h a n g e s . keySet () ) {
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// wir suchen fuer jeden geaendert e n Knoten der Stelle den reduziert e n
// Weg vom Split - Knoten , der die Stelle aufspannt
// s p l i t B r a n c h e s haelt alle diese Wege
// Inhalt in s p l i t B r a n c h e s sind Tripel : {s , b , Set < n >}
// s = Split - Knoten , b = Zweig - ID , n = naechster Knoten
// Das bedeutet : Wenn man bei s ist , kann man durch Verfolgen des
// Zweigs b zu den Knoten n kommen . Diese sind wieder Split - Knoten
// fuer welche man s p l i t B r a n c h e s untersuche n kann oder ein Knoten ,
// der geaendert wurde . Fuer diese geaendert e Knoten g existiert ebenso
// ein Eintrag in s p l i t B r a n c h e s : {g , -1 , null }. Dies wird fuer die
// A b b r u c h b e d i n g u n g von isFull benoetigt .
Map < Node , Map < Integer , Set < Node > > > s p l i t B r a n c h e s =
new HashMap < Node , Map < Integer , Set < Node > > >() ;
131
132
133
// gehe alle geaendert e n Knoten der Stelle durch ... Die reduziert e n
// Wege werden rueckwaer ts ( vom geaendert e n Knoten aus ) zu
xxi
D Algorithmen
// s p l i t B r a n c h e s h i n z u g e f u e g t
for ( int nID : p o s i t i o n s O f C h a n g e s . get ( positionI D ) ) {
134
135
136
// n = K n o t e n o b j e k t des geaenderte n Knoten
Node n = template . getNode ( nID ) ;
137
138
139
// Fuege Tripel {g , -1 , null } hinzu ( siehe oben )
if (! s p l i t B r a n c h e s . containsK ey ( nID ) )
s p l i t B r a n c h e s . put (n , new HashMap < Integer , Set < Node > >() ) ;
s p l i t B r a n c h e s . get ( n ) . put ( -1 , null ) ;
140
141
142
143
144
// wir Verfolgen die u e b e r g e o r d n e t e n Split - Knoten bis zu
// demjenigen der die Stelle aufspannt ( positionID ist ja
// g l e i c h z e i t i g auch die ID des die Stelle a u f s p a n n e n d e n Split // Knoten !)
while ( n . getID () != positionID ) {
// der u e b e r g e o r d n e t e Split - Knoten
Node splitNode = template . getNode ( n . g e t S p l i t N o d e I D () ) ;
// der Zweig , auf dem sich n befindet . Dieser Zweig beginnt
// bei splitNode
int branchID = n . getBranch ID () ;
145
146
147
148
149
150
151
152
153
154
155
// u . U . muss s p l i t B r a n c h e s i n i t i a l i s i e r t werden
if (! s p l i t B r a n c h e s . containsKe y ( splitNode ) )
s p l i t B r a n c h e s . put ( splitNode ,
new HashMap < Integer , Set < Node > >() ) ;
if (! s p l i t B r a n c h e s . get ( splitNode ) . containsKe y ( branchID ) )
s p l i t B r a n c h e s . get ( splitNode ) . put ( branchID ,
new HashSet < Node >() ) ;
156
157
158
159
160
161
162
163
// merke diesen Schritt des reduzierte n Weges
s p l i t B r a n c h e s . get ( splitNode ) . get ( branchID ) . add ( n ) ;
164
165
166
// fahre in der naechsten Iteration mit dem Split - Knoten fort
n = splitNode ;
167
168
}
169
}
170
171
// Knoten - Objekt des Split - Knotens , der die Stelle aufspannt
// ( positionID ist g l e i c h z e i t i g die ID des a u f s p a n n e n d e n
// Split - Knotens !)
Node splitNode = template . getNode ( positionID ) ;
172
173
174
175
176
// berechne durch Aufruf von isFull , welchen Typ die Stelle hat
PositionType positionType =
isFull ( splitBranches , splitNode ) ?
P o s i t i o n T y p e . COMPACT :
P o s i t i o n T y p e . LEAKY ;
177
178
179
180
181
182
// merke Stelle in der E r g e b n i s m e n g e
positions . put ( positionID ,
new Pair < PositionType , Set < Integer > >(
positionType , p o s i t i o n s O f C h a n g e s . get ( positionID ) ) ) ;
183
184
185
186
}
187
188
return positions ;
189
190
}
191
192
193
194
195
/*
* Berechnet fuer die gegebene Menge an reduziert en Wegen ( splitBranches ,
* siehe f i n d P o s i t i o n s ) , ob der von splitNode a u f g e s p a n n t e Block von einer
* Instanz nicht durchlauf e n werden kann , ohne einen geaendert e n Knoten aus -
xxii
D.2 findPositions
196
197
198
199
200
201
202
203
204
205
* gefuehrt zu haben .
*/
boolean isFull ( Map < Node , Map < Integer , Set < Node > > > splitBranches , Node splitNode ) {
// A b b r u c h b e d i n g u n g . Ist splitNode ein geaendert e r Knoten , existiert in
// s p l i t B r a n c h e s ein Eintrag mit der Zweig - ID -1 ( dies ist keine gueltige
// Zweig - ID und wird somit n o r m a l e r w e i s e nicht benutzt ) . Somit sind wir
// bei einem geaendert e n Knoten auf dem Weg angekommen . Dieser kann nicht
// von einer Instanz passiert werden ohne ausgefueh r t zu werden .
if (! s p l i t B r a n c h e s . get ( splitNode ) . containsK e y ( -1) )
return true ;
206
if ( splitNode . getType () == Type . AND_SPLIT ) {
207
208
// Trivialfa ll : keine Aenderung e n auf den Zweigen des Split - Knotens
if ( s p l i t B r a n c h e s . get ( splitNode ) . isEmpty () )
return true ;
209
210
211
212
// durchsuche alle Zweige des Split - Knotens , auf denen Aenderung e n
// sind
for ( int branchID : s p l i t B r a n c h e s . get ( splitNode ) . keySet () ) {
213
214
215
216
// Untersuch e alle Aenderung en auf dem Zweig branchID von
// splitNode . Um " true " zurueck liefern zu koennen genuegt es , wenn
// eine dieser Aenderung e n u n p a s s i e r b a r fuer eine Instanz ist .
for ( Node nextNode : s p l i t B r a n c h e s . get ( splitNode ) . get ( branchID ) )
if ( isFull ( splitBranches , nextNode ) )
return true ;
217
218
219
220
221
222
}
return false ;
} else {
// splitNode ist ein XOR - Split !
223
224
225
226
227
// bei einem XOR - Split muessen alle Zweige Aenderung e n besitzen , fuer
// die isFull (.) = true gilt !
228
229
230
// Trivialfa ll : Nicht alle Zweige haben Aenderung e n
if ( s p l i t B r a n c h e s . get ( splitNode ) . size () !=
template . g e t N o d e S u c c e s s o r s ( splitNode . getID () ) . size () )
return false ;
231
232
233
234
235
// untersuche alle Zweige ...
for ( int branchID : s p l i t B r a n c h e s . get ( splitNode ) . keySet () ) {
236
237
238
boolean b r a n c h I s F u l l = false ;
// untersuch e alle Aenderung en auf dem aktuellen Zweig branchID
for ( Node nextNode : s p l i t B r a n c h e s . get ( splitNode ) . get ( branchID ) ) {
b r a n c h I s F u l l = isFull ( splitBranches , nextNode ) ;
239
240
241
242
243
// wenn diese Aenderung u n p a s s i e r b a r ist , muessen die
// restlichen Aenderung en des Zweiges nicht untersucht werden
if ( b r a n c h I s F u l l )
break ;
244
245
246
247
}
if (! b r a n c h I s F u l l )
// es wurde keine u n p a s s i e r b a r e Aenderung auf dem Zweig
// gefunden
return false ;
248
249
250
251
252
}
253
254
return true ;
255
}
256
257
}
xxiii
D Algorithmen
D.3 preEvolveSelection
1
2
3
4
5
6
7
8
/*
* Die Kategorien , in die p r e E v o l e S e l e c t i o n die Instanzen einteilt
*/
enum E V O L V E _ C A T E G O R Y {
MIGRATABLE ,
NOT_MIGRATABLE ,
PRECISE_CHECK
};
9
10
11
12
// Weist einer Cluster - ID eine Menge an Instanz - IDs zu , welche sich in
// diesem Cluster befinden
Map < Integer , Set < Integer > > instances ;
13
14
15
// weist einer Cluster - ID die IDs der direkt u n t e r g e o r d n e t e n Cluster zu
Map < Integer , Set < Integer > > subCluster s ;
16
17
18
// weist einer Cluster - ID die ID des direkt u e b e r g e o r d n e t e n Cluster zu
Map < Integer , Integer > topCluster ;
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
* K a t e g o r i s i e r t die Instanzen anhand der Aenderunge n in delta ein .
*/
Map < EVOLVE_CATEGOR Y , Set < Integer > > p r e E v o l v e S e l e c t i o n ( DeltaLaye r delta ) {
// IDs von Instanzen , die nicht migriert werden koennen
Set < Integer > n o t M i g r a t a b l e = new HashSet < Integer >() ;
// IDs von Instantzen , die migriert werden
Set < Integer > migratable = new HashSet < Integer >() ;
// IDs von Instanzen , die genauer untersuch t werden muessen
Set < Integer > p r e c i s e C h e c k = new HashSet < Integer >() ;
32
33
// Zuerst werden alle Aenderunge n im Wurzelbloc k untersuch t
34
35
36
37
38
39
40
41
// Eine Menge an Knoten , welche geaendert wurden in der gegebenen
// D e l t a s c h i c h t .
// Diese Knoten sind die Ziele von h i n z u g e f u e g t e n und geloescht e n Kanten
// [ Jur06 ]
Set < Integer > c h a n g e d N o d e s I n R o o t C l u s t e r = new HashSet < Integer >() ;
c h a n g e d N o d e s I n R o o t C l u s t e r . addAll ( delta . g e t A d d e d E d g e s B a c k w a r d s () . keySet () ) ;
c h a n g e d N o d e s I n R o o t C l u s t e r . addAll ( delta . g e t D e l e t e d E d g e s B a c k w a r d s () . keySet () ) ;
42
43
44
45
46
47
48
49
50
// diejenigen Knoten , die sich nicht im Wurzelbloc k befinden , muessen
// wieder aus der Menge entfernt werden
for ( int nID : new HashSet < Integer >( c h a n g e d N o d e s I n R o o t C l u s t e r ) ) {
Node n = template . getNode ( nID ) ;
if ( n . getType () == Type . NORMAL &&
n . g e t S p l i t N o d e I D () == Template . S T A R T _ N O D E _ I D )
// Dieser Knoten befindet sich im Wurzelblo ck
continue ;
51
c h a n g e d N o d e s I n R o o t C l u s t e r . remove ( nID ) ;
52
53
}
54
55
56
57
58
59
60
for ( int c h a n g e d N o d e I D : c h a n g e d N o d e s I n R o o t C l u s t e r ) {
// untersuche alle direkt u n t e r g e o r d n e t e n Cluster des W u r z e l c l u s t e r s
for ( int s u b C l u s t e r I D : subCluste rs . get ( Template . S T A R T _ N O D E _ I D ) ) {
// s u b C l u s t e r I D ist auch ID des den Cluster a u f s p a n n e n d e n Split // Knotens !
xxiv
D.3 preEvolveSelection
if ( template . g e t N o d e R e l a t i o n ( subClusterID , c h a n g e d N o d e I D ) ==
N o d e R e l a t i o n . SUCCESSOR )
// der U n t e r c l u s t e r ist der Aenderung n a c h g e l a g e r t
// somit sind alle Instanzen nicht v e r t r a e g l i c h !
a d d I n s t a n c e s T o S e t ( subClusterID , n o t M i g r a t a b l e ) ;
61
62
63
64
65
}
66
67
}
68
69
70
// alle Instanzen aus dem W u r z e l c l u s t e r muessen genauer untersucht werden
a d d I n s t a n c e s T o S e t ( Template . START_NODE_ID , p r e c i s e C h e c k ) ;
71
72
73
74
75
76
// instances bereinige n
for ( int cID : instances . keySet () ) {
instances . get ( cID ) . removeAll ( n o t M i g r a t a b l e ) ;
instances . get ( cID ) . removeAll ( p r e c i s e C h e c k ) ;
}
77
78
79
80
81
82
83
// Finde Stellen .
// Mapping von Stellen - ID zu Stellentyp und Menge an Knoten IDs von
// geaendert e n Knoten dieser Stelle
Map < Integer , Pair < PositionType , Set < Integer > > > positions =
f i n d P o s i t i o n s ( delta ) ;
84
85
86
87
88
89
90
91
92
// finde topologis c h erste LEAKY und erste COMPACT Stelle
int s m a l l e s t L E A K Y t o p I D = Integer . MAX_VALUE ;
int s m a l l e s t L E A K Y I D = -1;
int s m a l l e s t C O M P A C T t o p I D = Integer . MAX_VALUE ;
int s m a l l e s t C O M P A C T I D = -1;
// gehe alle Stellen durch ( Stellen - ID = ID des die Stelle a u f s p a n n e n d e n
// Split - Knotens !)
for ( int splitID : positions . keySet () ) {
93
int topID = template . g e t T o p o l o g i c a l I D O f N o d e ( splitID ) ;
94
95
if ( positions . get ( splitID ) . getKey () == P o s i t i o n T y p e . COMPACT ) {
if ( topID < s m a l l e s t C O M P A C T t o p I D ) {
s m a l l e s t C O M P A C T t o p I D = topID ;
s m a l l e s t C O M P A C T I D = splitID ;
}
} else {
if ( topID < s m a l l e s t L E A K Y t o p I D ) {
s m a l l e s t L E A K Y t o p I D = topID ;
s m a l l e s t L E A K Y I D = splitID ;
}
}
96
97
98
99
100
101
102
103
104
105
106
107
}
108
109
110
111
112
113
// topologis c h erste Stelle
int s m a l l e s t C h a n g e P o s i t i o n I D =
( smallestLEAKYtopID < smallestCOMPACTtopID )?
smallestLEAKYID:
smallestCOMPACTID ;
114
115
116
117
118
int s m a l l e s t C h a n g e P o s i t i o n T o p I D =
( smallestLEAKYtopID < smallestCOMPACTtopID )?
smallestLEAKYtopID :
smallestCOMPACTtopID ;
119
120
121
122
// untersuche alle geaendert e Knoten der ersten Stelle
for ( int nID : positions . get ( s m a l l e s t C h a n g e P o s i t i o n I D ) . getValue () ) {
xxv
D Algorithmen
Node n = template . getNode ( nID ) ;
int cID ; // ID des Blocks / Clusters von n
if ( n . getType () == Type . NORMAL )
cID = n . g e t S p l i t N o d e I D () ;
else if ( n instanceof SplitNode )
cID = n . getID () ;
else if ( n instanceof JoinNode )
cID = (( JoinNode ) n ) . g e t C o r r e s p o n d i n g S p l i t N o d e I D () ;
123
124
125
126
127
128
129
130
131
// alle Instanzen aus dem Cluster des geaenderte n Blocks genauer
// untersuch en !
a d d I n s t a n c e s T o S e t ( cID , p r e c i s e C h e c k ) ;
132
133
134
135
// ID des Split Knotens , der Cluster aufspannt = cID !
Node splitNode = template . getNode ( cID ) ;
136
137
138
// untersuche direkt u n t e r g e o r d n e t e Cluster
for ( int s u b C l u s t e r I D : subCluste rs . get ( cID ) ) {
// s u b C l u s t e r I D = ID des den u n t e r g e o r d n e t e n Cluster
// a u f s p a n n e n d e n Split - Knoten !
N o d e R e l a t i o n relation =
template . g e t N o d e R e l a t i o n ( subClusterID , nID ) ;
if ( relation == N o d e R e l a t i o n . SUCCESSOR )
// u n t e r g e o r d n e t e r Cluster ist Aenderung n a c h g e l a g e r t
a d d I n s t a n c e s T o S e t ( subClusterID , n o t M i g r a t a b l e ) ;
139
140
141
142
143
144
145
146
147
148
if ( relation == N o d e R e l a t i o n . D I F F E R E N T _ B R A N C H &&
splitNode . getType () == Type . AND_SPLIT )
// u n t e r g e o r d n e t e r Cluster liegt auf anderem Zweig und
// Split - Knoten ist ein AND - Split -> p r e c i s e C h e c k
a d d I n s t a n c e s T o S e t ( subClusterID , p r e c i s e C h e c k ) ;
149
150
151
152
153
}
154
155
156
// untersuche alle u e b e r g e o r d n e t e n Cluster
// Dies wird mithilfe einer B r e i t e n s u c h e [ CLRS09 ] geloest
Set < Integer > c l u s t e r s C h e c k e d = new HashSet < Integer >() ;
c l u s t e r s C h e c k e d . add ( cID ) ;
Queue < Integer > c l u s t e r s T o C h e c k = new LinkedList < Integer >() ;
c l u s t e r s T o C h e c k . add ( topCluste r . get ( cID ) ) ;
157
158
159
160
161
162
163
while (! c l u s t e r s T o C h e c k . isEmpty () ) {
int checkID = c l u s t e r s T o C h e c k . poll () ;
164
165
166
if ( c l u s t e r s C h e c k e d . contains ( checkID ) )
continue ;
c l u s t e r s C h e c k e d . add ( checkID ) ;
167
168
169
170
// fuege unter - und u e b e r g e o r d n e t e Cluster als " zu pruefen " hinzu
c l u s t e r s T o C h e c k . addAll ( subCluste r s . get ( checkID ) ) ;
c l u s t e r s T o C h e c k . add ( topCluster . get ( checkID ) ) ;
171
172
173
174
if ( template . g e t N o d e R e l a t i o n ( checkID , nID ) !=
N o d e R e l a t i o n . PREDECESSO R )
// Cluster ist nicht Vorgaenger der Aenderung -> p r e c i s e C h e c k
p r e c i s e C h e c k . addAll ( instances . get ( checkID ) ) ;
175
176
177
178
}
179
180
}
181
182
183
184
// Bereinigen von p r e c i s e C h e c k
p r e c i s e C h e c k . removeAll ( n o t M i g r a t a b l e ) ;
xxvi
D.3 preEvolveSelection
185
186
187
188
189
190
// instances bereinige n
for ( int cID : instances . keySet () ) {
instances . get ( cID ) . removeAll ( n o t M i g r a t a b l e ) ;
instances . get ( cID ) . removeAll ( p r e c i s e C h e c k ) ;
}
191
192
193
194
195
196
197
// Instanzen , welche sich immer noch in Clustern der ersten Stelle
// befinden sind der Aenderung vorgelage r t und somit migrierbar !
// Wieder per B r e i t e n s u c h e
Queue < Integer > s u b C l u s t e r s T o C h e c k = new LinkedList < Integer >() ;
s u b C l u s t e r s T o C h e c k . add ( s m a l l e s t C h a n g e P o s i t i o n I D ) ;
198
199
200
while (! s u b C l u s t e r s T o C h e c k . isEmpty () ) {
int s u b C l u s t e r I D = s u b C l u s t e r s T o C h e c k . poll () ;
201
s u b C l u s t e r s T o C h e c k . addAll ( subCluste r s . get ( s u b C l u s t e r I D ) ) ;
202
203
migratable . addAll ( instances . get ( s u b C l u s t e r I D ) ) ;
204
205
// instances bereinigen
instances . get ( s u b C l u s t e r I D ) . clear () ;
206
207
208
}
209
210
211
212
213
214
215
216
217
218
if ( s m a l l e s t C h a n g e P o s i t i o n I D == s m a l l e s t L E A K Y I D &&
s m a l l e s t C O M P A C T I D != -1) {
// die erste Stelle ist LEAKY und es folgt eine COMPACT Stelle
// es muessen alle Instanzen der COMPACT Stelle als genauer zu
// untersuch en markiert werden
// wieder per B r e i t e n s u c h e
s u b C l u s t e r s T o C h e c k = new LinkedList < Integer >() ;
s u b C l u s t e r s T o C h e c k . add ( s m a l l e s t C O M P A C T I D ) ;
219
while (! s u b C l u s t e r s T o C h e c k . isEmpty () ) {
int s u b C l u s t e r I D = s u b C l u s t e r s T o C h e c k . poll () ;
220
221
222
s u b C l u s t e r s T o C h e c k . addAll ( subCluste r s . get ( s u b C l u s t e r I D ) ) ;
223
224
p r e c i s e C h e c k . addAll ( instances . get ( s u b C l u s t e r I D ) ) ;
225
226
// instances bereinigen
instances . get ( s u b C l u s t e r I D ) . clear () ;
227
228
}
229
230
}
231
232
233
234
235
// resltiche Instanzen einteilen
for ( int cID : instances . keySet () ) {
236
237
238
239
240
if ( instances . get ( cID ) . isEmpty () )
// wenn keine Instanzen im Cluster sind kann dieser u e b e r s p r u n g e n
// werden
continue ;
241
242
243
// cID = ID des Clusters = ID des den Cluster a u f s p a n n e n d e n
// Split - Knotens
244
245
246
if ( template . g e t N o d e R e l a t i o n ( cID , s m a l l e s t C h a n g e P o s i t i o n I D ) ==
N o d e R e l a t i o n . PREDECESS OR )
xxvii
D Algorithmen
// Cluster ist erster Aenderung vorgelage r t
migratable . addAll ( instances . get ( cID ) ) ;
else if ( s m a l l e s t C O M P A C T I D != -1 &&
template . g e t N o d e R e l a t i o n ( cID , s m a l l e s t C O M P A C T I D ) ==
N o d e R e l a t i o n . SUCCESSOR )
// Es existiert eine COMPACT Stelle und Cluster ist dieser
// n a c h g e l a g e r t -> nicht migrierbar !
n o t M i g r a t a b l e . addAll ( instances . get ( cID ) ) ;
else {
// der Cluster ist nach der ersten Stelle ( welche LEAKY ist )
// aber vor der ersten COMPACT Stelle , falls eine solche
// existiert -> genauer untersuche n !
p r e c i s e C h e c k . addAll ( instances . get ( cID ) ) ;
}
// instances bereinigen
instances . get ( cID ) . clear () ;
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
}
263
264
// Ergebnis aufbereit e n
Map < EVOLVE_CATEGO RY , Set < Integer > > res =
new HashMap < EVOLVE_CATEGOR Y , Set < Integer > >() ;
res . put ( E V O L V E _ C A T E G O R Y . MIGRATABLE ,
new HashSet < Integer >( migratabl e ) ) ;
res . put ( E V O L V E _ C A T E G O R Y . NOT_MIGRATABLE ,
new HashSet < Integer >( n o t M i g r a t a b l e ) ) ;
res . put ( E V O L V E _ C A T E G O R Y . PRECISE_CHECK ,
new HashSet < Integer >( p r e c i s e C h e c k ) ) ;
265
266
267
268
269
270
271
272
273
274
return res ;
275
276
}
277
278
279
280
281
282
283
284
285
286
/*
* fuegt alle Instanzen des Clusters oder einer seiner direkt oder indirekt
* u n t e r g e o r d n e t e n Cluster zum angegeben en Set hinzu
*/
void a d d I n s t a n c e s T o S e t ( int clusterID , Set < Integer > set ) {
set . addAll ( instances . get ( clusterID ) ) ;
for ( int s u b C l u s t e r I D : subCluste r s . get ( clusterID ) )
a d d I n s t a n c e s T o S e t ( subClusterID , set ) ;
}
xxviii
Danksagung
Zuerst möchte ich mich bei meiner Familie ganz herzlich bedanken. Ihr habt mich immer unterstützt und es mir durch eure Kraft und euren Zuspruch ermöglicht, diesen,
meinen Weg zu finden und zu gehen. Wann immer ich ein Problem habe, kann ich mich
vertrauensvoll an euch wenden. Danke!
Besonders bedanken möchte ich mich auch bei meinem Betreuer Ulrich Kreher. Du hast
mir beim Erstellen und der Korrektur dieser Arbeit sehr geholfen. Deine hohen Ansprüche
haben mich angespornt und deine konstruktive Kritik hat mich immer noch ein Stückchen
weiter gebracht. Danke!
Ebenfalls großer Dank geht an meine beiden Korrekturleser Florian Micheler und Wolfgang Holoch. Vielen Dank für eure Hilfe, meine ungefilterten Worte zu verstehen und
mir zu helfen, diese in eine lesbare Form zu bringen. Danke!
Erklärung
Hiermit erkläre ich, dass ich die vorliegende Arbeit selbstständig verfasst und keine anderen als die angegebenen Quellen und Hilfsmittel verwendet habe.
Ulm, 23. Dezember 2009
Bastian Philipp Glöckle, Matrikel-Nr. 541471
Herunterladen