7. Framework
In den vorangegangenen Kapiteln wurde ein grober Entwurf für die Funktionalität von
WikiWord entwickelt (.3) und die zu bewältigenden Aufgaben sowie die zu berücksichtigenden
Rahmenbedingungen spezifiziert (.5). Dieses Kapitel gibt nun einen Überblick über die zu diesem
Zweck entwickelten Softwarekomponenten (siehe Anhang G für Zugang zum Quellcode). Eine
Beschreibung der Kommandozeilenschnittstelle für WikiWord findet sich in Anhang B.
7.1. Import-Architektur
Dieser Abschnitt gibt eine Übersicht über die wichtigsten Klassen und Komponenten von
WikiWord. Im Zentrum der Betrachtung steht dabei ImportConcepts, das Programm zur
Extraktion der Thesaurusdaten aus dem Wiki-Text.
Die Programme, die die Kommandozeilenschnittstelle vonWikiWord bilden, sowie die Parameter
und Optionen für ihren Aufruf sind in Anhang B beschrieben. Weitere Möglichkeiten zur
Konfiguration bieten die sogenannten Tweak-Werte, siehe .B.5. Eine Auflistung der relevanten
Klassen und Packages ist in .G gegeben.
Die Basis für alle Programme der Kommandozeilenschnittstelle von WikiWord ist
die Klasse CliApp: Sie stellt Basisfunktionalität unter anderem zur Verarbeitung von
Kommandozeilenparametern und zur Ausgabe von Log-Nachrichten bereit. Alle in .B.1 erwähnten
Import-Programme mit Ausnahme von ExportSkos, bauen auf der von CliApp
abgeleiteten Klasse ImportApp auf: dieses bietet Unterstützung für die Verwendung des Agenda-
Systems zur Ablaufkontrolle (siehe Anhang B.6) sowie für die Erzeugung eines geeigneten
Datenzugriffsobjektes für die Interaktion mit der Datenbank (siehe .C.3).
Eine genauere Betrachtung verdient die Klasse ImportDump, auf der auch ImportConcepts basiert,
welches der Einstiegspunkt für einen Großteil der Extraktionslogik ist. ImportDump ist von
ImportApp abgeleitet und bildet das Framework für eine Consumer-Producer-Architektur für
den Import von Daten: Eine Implementation des Interfaces ImportDriver liest „Seiten“ aus einer
Datenquelle und übergibt sie, eine nach der anderen, an eine Implementation des Interfaces
WikiWordImporter, die die Daten dann weiter verarbeitet. Die Klasse AbstractImporter
bildet die Basis für Implementationen dieses Interfaces und bietet Funktionen insbesondere
für die Ablaufsteuerung über das Agenda-Objekt sowie für die Umsetzung allgemeiner
Kommandozeilenparameter.
Im Normalfall sollen die Wiki-Seiten aus einem Wikipedia-Dump gelesen werden MW:XML.
Dies wird von der Klasse DumpImportDriver umgesetzt (einer Implementation des Interfaces
ImportDriver), die ihrerseits auf die MWDumper-Bibliothek von Wikimedia zurückgreift
MW:DUMPER: Sie verarbeitet die XML-Struktur und ruft für jede enthaltene Wiki-Seite die
Methode handlePage auf dem WikiWordImporter auf. Dabei wird die XML-Datei automatisch
dekomprimiert, sollte dies erforderlich ein1.
1Unterstützt werden die weit verbreiteten Formate GZip und BZip2.
7. Framework 38
Für die Analyse des Wiki-Textes und den Import der so gewonnenen Ressourcen in das lokale
Datenmodell verwendet das Programm ImportConcepts die Klasse ConceptImporter als
Implementation von WikiWordImporter. Diese überführt den rohen Wiki-Text zunächst unter
Verwendung von WikiTextAnalyzer in das Ressourcenmodell (siehe .4.1, .8, .D.1), interpretiert
die Eigenschaften der Ressource (.4.2, .9.1) und schreibt das Ergebnis dann in das lokale
Datenmodell, ein Datenzugriffsobjekt des Typs LocalConceptStoreBuilder (siehe und .C.4).
Die Interaktion von DumpImportDriver, ConceptImporter, WikiTextAnalyzer und
LocalConceptStoreBuilder wird im nächsten Abschnitt genauer erläutert.
7.2. Ablauf und Parallelisierung (Pipeline)
main:DumpImportDriver read read read
imperter:ConceptImporter handlePage
handle
(queue)
handlePage
handle
(queue)
handlePage
handle
(queue)
flusher:DatabaseLocalStoreBuilder
store
(buffer)
flush
store store
(buffer)
Abb. 7.1: Parallelisierung: die Import-Pipeline
Die Aufgabe von ImportConcepts lässt sich in zwei Abschnitte gliedern: den eigentlichen
Datenimport, also das Lesen, Interpretieren und Speichern der Daten, und die Nachbereitung, also
die Konsolidierung der Daten. Der Datenimport ist selbst in mehrere Schritte gegliedert, von denen
einige parallel ausgeführt werden können—diese Parallelisierung soll hier näher beschrieben
werden.
Die am Datenimport beteiligten Komponenten sind DumpImportDriver,
ConceptImporterWikiTextAnalyzer und LocalConceptStoreBuilder bzw.
DatabaseLocalConceptStoreBuilder. Diese Komponenten arbeiten zum Teil parallel
(siehe Abb. 7.1) und sind wie folgt gekoppelt:
Im Haupt-Thread des ImportConcepts-Programms wird die runImport-Methode von
DumpImportDriver aufgerufen — sie ist der Einstiegspunkt für den gesamten Datenimport.
DumpImportDriver extrahiert mit Hilfe der MWDumper-Bibliothek den Wiki-Text der einzelnen
Seiten aus der XML-Struktur der Dump-Datei. Der Wiki-Text jeder Seite wird dann,
zusammen mit der zugehörigen Metainformation wie dem Seitentitel, in eine Warteschlange
eingefügt, aus der er, gemäß dem Consumer-Producer-Muster, von einem anderen Thread
herausgenommen wird, der ihn dann an die Methode handlePage von ConceptImporter
übergibt. Die Warteschlange ist auf n Einträge beschränkt: wenn sich schon n unbearbeitete
Einträge in der Warteschlange befinden, wenn der Producer (DumpImportDriver) einen Eintrag
7. Framework 39
hinzufügen will, so wird dieser blockiert, bis der Consumer (ConceptImporter) einen Eintrag
aus der Warteschlange entnommen hat. Die Länge der Warteschlange ist per Default 8 und
kann über den Tweak-Wert dumpdriver.pageImportQueue konfiguriert werden — die Länge
ist aber relativ unerheblich, da die Warteschlange in der Regel voll und der Producer-Thread
blockiert ist. Die Warteschlange dient also nicht als Puffer, sondern lediglich der Entkopplung
der beiden Aktivitäten, die eine parallele Ausführung erlaubt; eine Entkopplung nach dem
Rendezvous-Muster wäre hier ebenfalls ausreichend. Wird dumpdriver.pageImportQueue auf
0 gesetzt, so wird der Import „durchgekoppelt“ und keine Warteschlange verwendet — in diesem
Fall ruft der DumpImportDriver direkt die Methode handlePage von ConceptImporter auf.
Wie oben beschrieben, wird der ConceptImporter in einem eigenen Thread betrieben und
aus einer Warteschlange bedient. Für jeden Eintrag aus der Warteschlange wird die Methode
handlePage mit dem Wiki-Text und den zugehörigen Metadaten wie dem Seitentitel aufgerufen.
Diese Methode verwendet nun zunächst den WikiTextAnalyzer, um den Wiki-Text in das
Ressourcenmodell, implementiert durch WikiTextAnalyzer.WikiPage (siehe .D.1), zu überführen,
das heißt, den Text zu parsen und die relevanten Features (.4.1) wie Wiki-Links, verwendete
Vorlagen etc. zu extrahieren. Die so bestimmten Eigenschaften werden dann interpretiert
und entsprechende Informationen werden zur Speicherung an die Datenzugriffsschicht übergeben
(siehe .9.1 und .C.4).
Die datenbankbasierte Implementation der Datenzugriffsschicht (DAO),
DatabaseLocalConceptStoreBuilder, verwendet intern für jede Datenbanktabelle einen
Puffer, in dem neue Einträge zwischengespeichert werden. Ist ein Puffer voll, so wird er in eine
Warteschlange gelegt und ein neuer Puffer für die betreffende Tabelle verwendet. Ein separater
Thread arbeitet im Hintergrund die Puffer aus der Warteschlange ab, indem er einen nach
dem anderen entnimmt und an die Datenbank weiterleitet. Das entspricht einer asynchronen
Flush-Funktion für die Puffer. Bei einem expliziten Aufruf der Methode flush werden die Puffer
aller Tabellen in die Warteschlange des Flush-Threads gelegt und dann gewartet, bis alle Daten in
die Datenbank geschrieben wurden.
Die Länge der Warteschlange für die asynchronen Flush-Funktion ist per Default 4 und kann über
den Tweak-Wert dbstore.backgroundFlushQueue konfiguriert werden — die Länge ist aber
relativ unerheblich, da die Warteschlange in der Regel leer sein sollte, da nur recht selten Einträge
in ihr abgelegt werden. Sollte die Warteschlange häufig voll sein und damit den Arbeitsthread
blockieren, sollte die Größe der Einfügepuffer erhöht werden — das kann über den Tweak-
Wert dbstore.insertionBufferFactor geschehen. Dabei ist allerdings zu beachten, dass die
Größe jedes einzelnen Puffers durch die MySQL-Konfigurationsvariable max_allowed_packet
begrenzt ist — gegebenenfalls muss auch diese erhöht werden. Die Warteschlange dient also
vornehmlich der Entkopplung der langwierigen Flush-Operation, so dass diese parallel zur
Analyse des Wiki-Textes ausgeführt werden kann. Wird dbstore.backgroundFlushQueue
auf 0 gesetzt, so wird die Flush-Operation „durchgekoppelt“ und keine Warteschlange verwendet
— in diesem Fall muss der Arbeitsthread warten, während volle Einfügepuffer in
die Datenbank übertragen werden. Werden die Tweak-Werte dbstore.useEntityBuffer und
dbstore.useRelationBuffer auf false gesetzt, so wird für das Einfügen in die Datenbank
gar kein Puffer benutzt, jeder Eintrag wird sofort übertragen. Dieser Modus ist allerdings sehr
langsam und nur für die Fehlersuche sinnvoll.
In den vorangegangenen Kapiteln wurde ein grober Entwurf für die Funktionalität von
WikiWord entwickelt (.3) und die zu bewältigenden Aufgaben sowie die zu berücksichtigenden
Rahmenbedingungen spezifiziert (.5). Dieses Kapitel gibt nun einen Überblick über die zu diesem
Zweck entwickelten Softwarekomponenten (siehe Anhang G für Zugang zum Quellcode). Eine
Beschreibung der Kommandozeilenschnittstelle für WikiWord findet sich in Anhang B.
7.1. Import-Architektur
Dieser Abschnitt gibt eine Übersicht über die wichtigsten Klassen und Komponenten von
WikiWord. Im Zentrum der Betrachtung steht dabei ImportConcepts, das Programm zur
Extraktion der Thesaurusdaten aus dem Wiki-Text.
Die Programme, die die Kommandozeilenschnittstelle vonWikiWord bilden, sowie die Parameter
und Optionen für ihren Aufruf sind in Anhang B beschrieben. Weitere Möglichkeiten zur
Konfiguration bieten die sogenannten Tweak-Werte, siehe .B.5. Eine Auflistung der relevanten
Klassen und Packages ist in .G gegeben.
Die Basis für alle Programme der Kommandozeilenschnittstelle von WikiWord ist
die Klasse CliApp: Sie stellt Basisfunktionalität unter anderem zur Verarbeitung von
Kommandozeilenparametern und zur Ausgabe von Log-Nachrichten bereit. Alle in .B.1 erwähnten
Import-Programme mit Ausnahme von ExportSkos, bauen auf der von CliApp
abgeleiteten Klasse ImportApp auf: dieses bietet Unterstützung für die Verwendung des Agenda-
Systems zur Ablaufkontrolle (siehe Anhang B.6) sowie für die Erzeugung eines geeigneten
Datenzugriffsobjektes für die Interaktion mit der Datenbank (siehe .C.3).
Eine genauere Betrachtung verdient die Klasse ImportDump, auf der auch ImportConcepts basiert,
welches der Einstiegspunkt für einen Großteil der Extraktionslogik ist. ImportDump ist von
ImportApp abgeleitet und bildet das Framework für eine Consumer-Producer-Architektur für
den Import von Daten: Eine Implementation des Interfaces ImportDriver liest „Seiten“ aus einer
Datenquelle und übergibt sie, eine nach der anderen, an eine Implementation des Interfaces
WikiWordImporter, die die Daten dann weiter verarbeitet. Die Klasse AbstractImporter
bildet die Basis für Implementationen dieses Interfaces und bietet Funktionen insbesondere
für die Ablaufsteuerung über das Agenda-Objekt sowie für die Umsetzung allgemeiner
Kommandozeilenparameter.
Im Normalfall sollen die Wiki-Seiten aus einem Wikipedia-Dump gelesen werden MW:XML.
Dies wird von der Klasse DumpImportDriver umgesetzt (einer Implementation des Interfaces
ImportDriver), die ihrerseits auf die MWDumper-Bibliothek von Wikimedia zurückgreift
MW:DUMPER: Sie verarbeitet die XML-Struktur und ruft für jede enthaltene Wiki-Seite die
Methode handlePage auf dem WikiWordImporter auf. Dabei wird die XML-Datei automatisch
dekomprimiert, sollte dies erforderlich ein1.
1Unterstützt werden die weit verbreiteten Formate GZip und BZip2.
7. Framework 38
Für die Analyse des Wiki-Textes und den Import der so gewonnenen Ressourcen in das lokale
Datenmodell verwendet das Programm ImportConcepts die Klasse ConceptImporter als
Implementation von WikiWordImporter. Diese überführt den rohen Wiki-Text zunächst unter
Verwendung von WikiTextAnalyzer in das Ressourcenmodell (siehe .4.1, .8, .D.1), interpretiert
die Eigenschaften der Ressource (.4.2, .9.1) und schreibt das Ergebnis dann in das lokale
Datenmodell, ein Datenzugriffsobjekt des Typs LocalConceptStoreBuilder (siehe und .C.4).
Die Interaktion von DumpImportDriver, ConceptImporter, WikiTextAnalyzer und
LocalConceptStoreBuilder wird im nächsten Abschnitt genauer erläutert.
7.2. Ablauf und Parallelisierung (Pipeline)
main:DumpImportDriver read read read
imperter:ConceptImporter handlePage
handle
(queue)
handlePage
handle
(queue)
handlePage
handle
(queue)
flusher:DatabaseLocalStoreBuilder
store
(buffer)
flush
store store
(buffer)
Abb. 7.1: Parallelisierung: die Import-Pipeline
Die Aufgabe von ImportConcepts lässt sich in zwei Abschnitte gliedern: den eigentlichen
Datenimport, also das Lesen, Interpretieren und Speichern der Daten, und die Nachbereitung, also
die Konsolidierung der Daten. Der Datenimport ist selbst in mehrere Schritte gegliedert, von denen
einige parallel ausgeführt werden können—diese Parallelisierung soll hier näher beschrieben
werden.
Die am Datenimport beteiligten Komponenten sind DumpImportDriver,
ConceptImporterWikiTextAnalyzer und LocalConceptStoreBuilder bzw.
DatabaseLocalConceptStoreBuilder. Diese Komponenten arbeiten zum Teil parallel
(siehe Abb. 7.1) und sind wie folgt gekoppelt:
Im Haupt-Thread des ImportConcepts-Programms wird die runImport-Methode von
DumpImportDriver aufgerufen — sie ist der Einstiegspunkt für den gesamten Datenimport.
DumpImportDriver extrahiert mit Hilfe der MWDumper-Bibliothek den Wiki-Text der einzelnen
Seiten aus der XML-Struktur der Dump-Datei. Der Wiki-Text jeder Seite wird dann,
zusammen mit der zugehörigen Metainformation wie dem Seitentitel, in eine Warteschlange
eingefügt, aus der er, gemäß dem Consumer-Producer-Muster, von einem anderen Thread
herausgenommen wird, der ihn dann an die Methode handlePage von ConceptImporter
übergibt. Die Warteschlange ist auf n Einträge beschränkt: wenn sich schon n unbearbeitete
Einträge in der Warteschlange befinden, wenn der Producer (DumpImportDriver) einen Eintrag
7. Framework 39
hinzufügen will, so wird dieser blockiert, bis der Consumer (ConceptImporter) einen Eintrag
aus der Warteschlange entnommen hat. Die Länge der Warteschlange ist per Default 8 und
kann über den Tweak-Wert dumpdriver.pageImportQueue konfiguriert werden — die Länge
ist aber relativ unerheblich, da die Warteschlange in der Regel voll und der Producer-Thread
blockiert ist. Die Warteschlange dient also nicht als Puffer, sondern lediglich der Entkopplung
der beiden Aktivitäten, die eine parallele Ausführung erlaubt; eine Entkopplung nach dem
Rendezvous-Muster wäre hier ebenfalls ausreichend. Wird dumpdriver.pageImportQueue auf
0 gesetzt, so wird der Import „durchgekoppelt“ und keine Warteschlange verwendet — in diesem
Fall ruft der DumpImportDriver direkt die Methode handlePage von ConceptImporter auf.
Wie oben beschrieben, wird der ConceptImporter in einem eigenen Thread betrieben und
aus einer Warteschlange bedient. Für jeden Eintrag aus der Warteschlange wird die Methode
handlePage mit dem Wiki-Text und den zugehörigen Metadaten wie dem Seitentitel aufgerufen.
Diese Methode verwendet nun zunächst den WikiTextAnalyzer, um den Wiki-Text in das
Ressourcenmodell, implementiert durch WikiTextAnalyzer.WikiPage (siehe .D.1), zu überführen,
das heißt, den Text zu parsen und die relevanten Features (.4.1) wie Wiki-Links, verwendete
Vorlagen etc. zu extrahieren. Die so bestimmten Eigenschaften werden dann interpretiert
und entsprechende Informationen werden zur Speicherung an die Datenzugriffsschicht übergeben
(siehe .9.1 und .C.4).
Die datenbankbasierte Implementation der Datenzugriffsschicht (DAO),
DatabaseLocalConceptStoreBuilder, verwendet intern für jede Datenbanktabelle einen
Puffer, in dem neue Einträge zwischengespeichert werden. Ist ein Puffer voll, so wird er in eine
Warteschlange gelegt und ein neuer Puffer für die betreffende Tabelle verwendet. Ein separater
Thread arbeitet im Hintergrund die Puffer aus der Warteschlange ab, indem er einen nach
dem anderen entnimmt und an die Datenbank weiterleitet. Das entspricht einer asynchronen
Flush-Funktion für die Puffer. Bei einem expliziten Aufruf der Methode flush werden die Puffer
aller Tabellen in die Warteschlange des Flush-Threads gelegt und dann gewartet, bis alle Daten in
die Datenbank geschrieben wurden.
Die Länge der Warteschlange für die asynchronen Flush-Funktion ist per Default 4 und kann über
den Tweak-Wert dbstore.backgroundFlushQueue konfiguriert werden — die Länge ist aber
relativ unerheblich, da die Warteschlange in der Regel leer sein sollte, da nur recht selten Einträge
in ihr abgelegt werden. Sollte die Warteschlange häufig voll sein und damit den Arbeitsthread
blockieren, sollte die Größe der Einfügepuffer erhöht werden — das kann über den Tweak-
Wert dbstore.insertionBufferFactor geschehen. Dabei ist allerdings zu beachten, dass die
Größe jedes einzelnen Puffers durch die MySQL-Konfigurationsvariable max_allowed_packet
begrenzt ist — gegebenenfalls muss auch diese erhöht werden. Die Warteschlange dient also
vornehmlich der Entkopplung der langwierigen Flush-Operation, so dass diese parallel zur
Analyse des Wiki-Textes ausgeführt werden kann. Wird dbstore.backgroundFlushQueue
auf 0 gesetzt, so wird die Flush-Operation „durchgekoppelt“ und keine Warteschlange verwendet
— in diesem Fall muss der Arbeitsthread warten, während volle Einfügepuffer in
die Datenbank übertragen werden. Werden die Tweak-Werte dbstore.useEntityBuffer und
dbstore.useRelationBuffer auf false gesetzt, so wird für das Einfügen in die Datenbank
gar kein Puffer benutzt, jeder Eintrag wird sofort übertragen. Dieser Modus ist allerdings sehr
langsam und nur für die Fehlersuche sinnvoll.