Im letzten Blogartikel hatte ich die neue Datenbankstrategie erklärt, und auch den gigantisch grossen Transfer angeteasert. Um diesen soll es nun gehen: Wie wurden die Daten transferiert? Wie lange hat dies gedauert? Wieso musste der Server aufgerüstet werden?

Kurzer Rekapitulation aus dem letzten Blogartikel zur Situation:

Der Zugang zur Petrucci-API wurde mir verwehrt. Ein öffentlicher Zugang zu einer grossen Sammelliste aus Stücken der Petrucci-Datenbank besteht jedoch. Erste Berechnungen ergaben rund 115’000’00 Listen-Einträge. Die Vorbereitungen waren bereits aufwändig und das Einlesen der öffentlich zugänglichen API verlief mässig schnell. Optimierungen am Code und Server, sowie Neustarts während der Operation waren nötig (mehr zur Serveroptimierung im Artikel). Um Aufschluss über den aktuellen Status des Transfers zu erhalten, habe ich mir zudem eine kleine Statusanzeige programmiert. Der Transfer dauerte 3½ Tage.

Das Ergebnis des Transfers (nachdem er endlich zum Abschluss kam) war… ernüchternd. Die Daten aus der Liste wiederholten sich sporadisch. Ich habe beispielsweise insgesamt 700’000 Stücke von Mozart registrieren können, wobei dieser nur (rund) 700 komponiert hat. Das Ziel wurde in dem Fall somit um’s 1000-fache verfehlt… Ausserdem wurde die Datenbank unerträglich langsam (trotz eines leistungsstarken Servers) und zudem habe ich von diesem Transfer lediglich den Titel der Stücke (und die Komponisten) erhalten… Sehr dürftig, sehr enttäuschend. Viel Aufwand für nichts… ?

Nun ja, ich hatte nun zwei Optionen: Aufgeben und den Nutzern der App die mühsame Aufgabe des Eintippens neuer Stücke auferlegen oder die Mühe auf mich selbst nehmen und noch mehr Zeit für die Problemlösung investieren. Ich habe mich für letzteres entschieden und folgenden Entschluss gefasst: Ich erstelle ein neues Skript (einen Parser), welcher anhand der bisher gewonnen Informationen (Stücktitel) eine neue Daten-Tabelle erstellt. Ich erlaube nur einzigartige Kombinationen aus Stücktiteln und Komponisten (sodass Duplikate verhindert werden). Dadurch erhalte ich eine deutlich schlankere Datentabelle (statt 700’000 doppelte Einträge im Falle Mozarts, erhalte ich 700 einzigartige, aufschlussreiche und wichtige Informationen wie Datum der Komposition, Sätze des Werks etc.). Zudem habe ich mich entschlossen mit „nur“ 25 der wichtigsten / populärsten Komponisten anzufangen, um eine erneute Lähmung der Datenbank zu verhindern und reaktionsfähiger auf Fehler und Probleme zu verbleiben. Dieser Transfer (später auch „Op. 2“ genannt) dauerte zwei Stunden. Weniger als drei Tage, dennoch viel, für nur 25 Komponisten.

Doch nun zurück zu den Einstiegs-Fragen: Wie wurden die Daten transferiert? Wie lange hat dies gedauert? Wieso musste der Server aufgerüstet werden? Zuerst werde ich die Technik hinter der Datenübertragung (Transfer) erklären – keine Sorge, Kurzfassung.

Die Technik

Der Server startet ein PHP-Skript* (Backend-Programmiersprache), welches anschliessend mit dem „Parsing1 beginnt. Hierbei besucht der Server die von mir definierte Webseite, sucht für spezielle Begriffe und Elemente (die ich definiert habe) 2, lädt sie dann auf meinen Server, wo diese analysiert, zugeschnitten und bearbeitet werden 3, sodass ich am Ende die puren, rohen Daten in meine MySql-Datenbank speichern kann 4.

1
PHP Parser Anfrage
2
Grober Daten-Retrieve
3
Processing
4
Speichern in MySql-DB

Das Vorgehen scheint auf den ersten Blick plausibel und einfach – leider ist dem nicht so. Das Processing war eine richtige Qual (und ich habe im Schaubild nicht übertrieben: Es brauchte mindestens so viele Zahnräder und Werkzeuge… mindestens! 😉 ). Wieso? Die Einträge zu den Stücken auf der Petrucci Webseite sind nicht einheitlich. Beispielsweise werden die Sätze eines Musikstücks mal per Leerzeichen, mal per <br> Tag (Zeilenumschlag), aber auch durch Listenelemente <li> und Tabellenzellen <dd> aufgelistet (ich musste all diese Tags übrigens durch Testläufe und Stichproben finden, um überhaupt mit dem Transfer anzufangen). Anschliessend habe ich den Parser auf all diese Erkennungszeichen ausgerichtet und jeweils mit individuellen Funktionen zur Analyse, Identifizierung und Verarbeitung ausgestattet. Eine Menge Arbeit, Geduld und Nerven sind von Nöten gewesen…

Nach zahlreichen Testläufen und Optimierungsphasen des Skripts war ich schlussendlich bereit. Ein 320 Zeilen umfassendes Programm (das sind 16’000+ Zeichen, die von komplett Hand geschrieben wurden) war starklar. Während den ersten ernsthaften Transfer-Versuchen wurde mir jedoch schnell klar, dass der Server zu langsam ist und Kapazitäten fehlten. Daher habe ich die Serverleistung und Kapazität deutlich erhöht, um die Operation zu ermöglichen. Nötig waren:

8 CPU Kerne

16 GB RAM

220 GB SSD-Speicher

3 ½ Tage (Op. 1)
+ 2h (Op. 2)

Eine ordentliche Portion Leistung hat die Maschinen nun (vor allem für einen Server!). Doch es wäre möglich gewesen einen noch besseren Server für einige Tage zu ordern, wieso habe ich das nicht getan?

Ab einem gewissen Zeitpunkt erreicht man eine Schwelle, an welcher Software kaum mehr durch Hardware-Upgrades zu verbessern (beschleunigen) ist. Ein Parser ist in der Hinsicht tatsächlich ziemlich langsam, denn die Schnelligkeit der Hardware kommt erst dann ins Spiel, wenn die Petrucci-Webseite geladen wurde. Dies ist ein externer Prozess, den ich zwar durch spezifische Adresseingaben optimiert habe, stark verbessern kann ich die Ladezeit jedoch nicht. Die Antwortzeit des Petrucci-Servers liegt nicht in meiner Hand. Ausserdem ist die Parsing-Technik an sich ziemlich langsam, da der Code zahlreiche Male auf mehrere Tags (<li>, <dd> etc.) geprüft werden muss. Dieser Vorgang kostet Zeit und ist ab einem gewissen Zeitpunkt durch Hardwareoptimierung nicht mehr spürbar zu verbessern.

Ein ungefährer Verlauf der mehrtägigen Operation wird in den Statistiken des Servers sichtbar. (Leider stimmen die Relationen der Daten nach dem Serverupgrade nicht mehr überein, dennoch wird die Servertätigkeit sichtbar). Unvorstellbar werden die Graphen, wenn man bedenkt, dass es sich hierbei nicht um Bilder-Downloads oder Video-Streaming handelt. Bei den Daten die übertragen werden, handelt es sich um reinen Text! Eine Arbeitsspeicherbelastung von 60%, Downloadgeschwindigkeiten von bis zu 105 MB/s (!) – nur für puren Text.

Das Ergebnis

Nach zwei Operationen (die erste dauerte 3 ½ Tage an, die zweite 2h) hatte auch der Server seinen Teil der Arbeit geleistet und es war nun wieder an mir, herauszufinden, ob sich dieser gigantische Aufwand schlussendlich dennoch gelohnt hat. Die Antwort ist: Ja.

Die Daten aus der zweiten Transfer-Operation sind äusserst wertvoll: Durch die Kategorisierung kann ich diese nämlich weiterverarbeiten. Um die Sätze eines Stückes in der App darzustellen ist dies nämlich notwendig. Der Server erhält eine Anfrage der App: „Zeige mir alle Sätze des Stücks X von Komponist Mozart“. Der Server antwortet: „Satz 1: Titel 1 | Satz 2: Titel 2 | etc.“.

Petrucci’s Webseiten muss man sich hingegen wie einen Fliesstext vorstellen. Ich könnte den Server nur nach allgemeinen Informationen fragen und würde NUR Fliesstexte (keine kategorisierten Informationen!) zurückerhalten, die mir nichts bringen, der App nicht helfen und die der User nicht braucht.

Genauer auf den Code werde ich in diesem Blogartikel nicht eingehen (speziell die Backend-Strukturen sind heikel: Somit bleibt die PHP Schnittstelle App <—> Server unter Verschluss). Dennoch habe ich den kompletten Parser online gestellt (abgesehen von kritischen Datenbank-Informationen). Den Code habe ich durch Kommentare ergänzt um ihn verständlicher zu machen. Zu beachten ist jedoch, dass das Skript lediglich einer einmaligen Operation diente. Ausserdem musste ich viel experimentieren, da die Petrucci Webseiten-Struktur nicht einheitlich strukturiert war (wie zuvor im Artikel erwähnt). Der Code ist daher etwas wirr und nicht objektorientiert. Somit wäre er für eine längerfristige Verwendung ungeeignet. Für den einmaligen Transfer (unter Zeitdruck) war er mir jedoch gut genug, da er mich bestens ans Ziel gebracht hat.

PHP Transfer Retrieve Parser (Gist)

Im nächsten Blogartikel werde ich zeigen, inwiefern die Daten innerhalb der App bereits genutzt werden. Ich stehe ausserdem kurz vor dem Release der Alpha-Version, eine Update dazu ist somit bald zu erwarten! 🙂

Anhang

* Alle, die selbst intensives Parsing oder einen grossen Transfer wie diesen mit PHP durchführen wollen, werden merken, dass der Browser das Spielchen nicht mitmacht. Nach 30s – 60s meldet dieser einen Timeout und spätestens nach einigen Minuten wird die Anfrage an den Server terminiert. Daher kann der Weg über die Webadresse (& Webbrowser) nicht gewählt werden. Das Skript muss direkt von der Konsole (SSH) aus gestartet werden. Der Server wird der Operation automatisch einen Prozess zuweisen, welcher erst bei Operationsende terminiert wird.

Mehr Informationen findet man in diesem äusserst hilfreichen Blogartikel.
Die nötige Codezeile zum Starten des Skripts:

print `echo /usr/bin/php -q langAndauerndesSkript.php | at now`;