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