Steigerung der Skalierbarkeit und Performance von Webanwendungen durch den Einsatz einer Redis Datenbank Abschlussarbeit zur Erlangung des akademischen Grades Bachelor of Science (B.Sc.) an der Hochschule für Technik und Wirtschaft Berlin Fachbereich Wirtschaftswissenschaften II Studiengang Angewandte Informatik 1. Prüfer: Prof. Dr. Christin Schmidt 2. Prüfer: Dipl.-Kfm. Andreas Richter Eingereicht von Fabian Kirstein Berlin, 13. August 2012 Kontakt: [email protected], www.fabiankirstein.de Zusammenfassung Eine der größten Herausforderung im Zeitalter des Web 2.0 ist es, mit immer größeren Datenmengen und Nutzerzahlen umzugehen. Betreiber von Webanwendungen müssen für diese Herausforderung Lösungen finden und diese in kürzester Zeit umsetzen. Diese Bachelorarbeit evaluiert, ob und in welchem Maße die Performance und Skalierbarkeit einer Webanwendung unter Einsatz einer Redis Datenbank gesteigert werden kann. Grundlage stellt eine bestehende Webanwendung dar, welche eine MySQL Datenbank einsetzt. Die Anwendung wurde hinsichtlich ihrer Qualität bezüglich Performance und Skalierbarkeit bewertet. Im Anschluss wurde die Anwendung um eine Datenhaltungsschicht auf Basis der Key-ValueDatenbank Redis erweitert, welche als generische Cache-Ebene parallel zur MySQL Datenbank agiert. Die Schnittstellen der Datenhaltungsschicht blieben dabei unverändert. Die Performance und Skalierbarkeit der erweiterten Fassung der Anwendung wurde dann ebenfalls bewertet und mit dem Ausgangszustand verglichen. Sowohl Performance, als auch Skalierbarkeit konnten durch den Einsatz der Redis Datenbank gesteigert werden. In der Testumgebung hat sich der Durchsatz um ein Viertel erhöht und die Anwendung war besser in der Lage mit einer steigenden Anzahl gleichzeitiger Nutzer umzugehen. Zusätzlich zur Steigerung dieser Werte, wurde durch die Redis Datenbank auch die Grundlage gelegt, die Performance und Skalierbarkeit mit wenig Aufwand weiter zu steigern. Durch die einfache Struktur und das simple Key-Value Prinzip der Redis Datenbank, fällt eine horizontale Skalierung besonders leicht. Schlüssel können mit Hilfe von Hashwerten einfach auf mehrere Server verteilt werden. Eine horizontale Skalierung einer MySQL Datenbank durch Replikation oder Sharding ist dagegen für die überschaubare Beispielanwendung aufwendiger umzusetzen. Die Performance und Skalierbarkeit einer Webanwendung kann unter Einsatz einer Redis Datenbank in jedem Fall gesteigert werden, wenn die Datenstrukturen der Anwendung überschaubar sind und eine unbedingte Konsistenz der Daten nicht notwendig ist. I Inhaltsverzeichnis 1 Einleitung 1 1.1 Betriebliches Umfeld . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 Projekt Understandr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.3 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.4 Aufgabenbeschreibung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.5 Inhalt und Aufbau der Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2 Grundlagen 6 2.1 Web 2.0 und Soziale Netzwerke . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.2 Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.3 Skalierbarkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.4 Relationale Datenbanken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.4.1 Performance von MySQL . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.4.2 Skalierbarkeit von MySQL . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 NoSQL Datenbanken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.5.1 Definition und Kategorien . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.5.2 Redis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.6 Consistent Hashing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.7 Objekt-relationales Mapping 14 2.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Analyse und Vorgehen 16 3.1 Abgrenzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 3.2 Vorgehen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 3.3 Technisches Umfeld . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.4 Anforderungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 II INHALTSVERZEICHNIS 3.5 3.6 3.7 3.4.1 Funktionale Anforderungen . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.4.2 Nicht-funktionale Anforderungen . . . . . . . . . . . . . . . . . . . . . . . 19 3.4.3 Technische Anforderungen . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Testdesign und Kapazitätsplanung . . . . . . . . . . . . . . . . . . . . . . . . . . 20 3.5.1 Use Cases und Verteilung . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.5.2 Entwurf der Testdaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 3.5.3 Page Views und Nutzerzahlen . . . . . . . . . . . . . . . . . . . . . . . . . 24 3.5.4 Testdesign im Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Testumgebung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3.6.1 Software für die Messung . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3.6.2 Datenbank befüllen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 3.6.3 Testaufbau, Qualität und Messgrößen . . . . . . . . . . . . . . . . . . . . 28 3.6.4 Test der Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.6.5 Test der Skalierbarkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.6.6 Profiling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Integrations- und Unittests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 4 Analyse und Test des Ist-Zustandes 31 4.1 Profiling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 4.2 Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 4.3 Skalierbarkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 4.4 Testfälle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 4.5 Beurteilung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 5 Entwurf 35 5.1 Das PlistaModel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 5.2 Datenhaltung und Berechnungen . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 5.3 Verwendung von Redis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 5.4 Generische Cache-Ebene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 5.5 Redis-Datenmodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 5.6 Denormalisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 5.7 Sonderfall Suche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 III INHALTSVERZEICHNIS 6 Implementierung 45 6.1 Cache-Ebene verwenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 6.2 Datenverarbeitung im Detail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 6.3 Die Models im Detail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 6.4 Transparenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 6.5 Probleme und Schwierigkeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 6.6 Weitere Implementierungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 7 Analyse und Test der neuen Version 50 7.1 Profiling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 7.2 Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 7.3 Skalierbarkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 7.4 Testfälle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 7.5 Beurteilung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 8 Zusammenfassung und Ergebnis 54 8.1 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 8.2 Allgemeingültigkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 8.3 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 A Stack Overflow SQL Abfragen 61 B Beispielhafter Aufbau einer XML-Konfigurationsdatei 63 C Übersicht und Ergebnisse der Tests 64 C.1 PHPUnit Tests der Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 C.2 Selenium Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 C.3 PHPUnit Tests der Erweiterung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 D Inhalt der CD-ROM 67 IV Abkürzungen CPU Central Processing Unit CSS Cascading Style Sheets DOM Document Object Model HTML Hypertext Markup Language HTW Hochschule für Technik und Wirtschaft I/O Input/Output MVC Model View Controller RDBMS Relationale Datenbankmanagementsysteme SQL Structured Query Language V Abbildungsverzeichnis 1.1 Ausschnitt der Understandr Oberfläche . . . . . . . . . . . . . . . . . . . . . . . 3 2.1 Hinzufügen und Entfernen von Servern aus Edlich et al. 2010, S. 37 . . . . . . . . 14 3.1 Deployment-Diagramm des technischen Umfelds . . . . . . . . . . . . . . . . . . 18 3.2 Use Cases der Anwendung Understandr . . . . . . . . . . . . . . . . . . . . . . . 21 3.3 Traffic für 2 Tage des DE-CIX Internetknoten (de-cix.net, 03.07.2012) . . . . . . 25 3.4 Testdaten Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.5 Testanfragen Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.6 Screenshot JMeter mit Testplan . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 4.1 Anwortzeit gegenüber Anzahl gleichzeitiger Nutzer im Ist-Zustand . . . . . . . . 33 4.2 Durchsatz gegenüber Anzahl gleichzeitiger Nutzer im Ist-Zustand . . . . . . . . . 33 5.1 Grundsätzlicher Aufbau einer MVC-Anwendung (CakePHP 2012) 35 5.2 Klassendiagramm der Plista Klassenbibliothek für Objekt-relationales Mapping . . . . . . . . mit den wichtigsten Klassen und Methoden . . . . . . . . . . . . . . . . . . . . . 36 5.3 Entity-Relationship-Modell des bestehenden MySQL-Implementierung . . . . . . 38 5.4 Klassendesign der Redis Cache Ebene . . . . . . . . . . . . . . . . . . . . . . . . 44 7.1 Anwortzeit gegenüber Anzahl gleichzeitiger Nutzer im Vergleich zwischen alter und neuer Version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2 52 Durchsatz gegenüber Anzahl gleichzeitiger Nutzer im Vergleich zwischen alter und neuer Version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . VI 52 Tabellenverzeichnis 2.1 Beispieltabelle Personen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.2 Auszug aus der Beispieltabelle Personen nach der Manipulation . . . . . . . . . . 15 3.1 Verteilung der sieben Performance-Test Aktivitäten über die Kapitel dieser Arbeit 17 3.2 Übersicht der nicht-funktionalen Anforderungen . . . . . . . . . . . . . . . . . . . 19 3.3 Verteilung der Anzahl an Antworten auf Stack Overflow . . . . . . . . . . . . . . 23 3.4 Verteilung der Stimmanzahl auf Stack Overflow . . . . . . . . . . . . . . . . . . . 24 4.1 Ergebnis des Profiling des Ist-Zustandes . . . . . . . . . . . . . . . . . . . . . . . 31 4.2 Ergebnisse des Performance-Tests des Ist-Zustandes . . . . . . . . . . . . . . . . 32 6.1 Elemente einer XML-Konfigurationsdatei der Redis DataSource . . . . . . . . . . 45 7.1 Ergebnis des Profiling mit Redis-Cache . . . . . . . . . . . . . . . . . . . . . . . . 50 7.2 Ergebnisse des Performance-Tests mit Redis-Cache . . . . . . . . . . . . . . . . . 51 VII Kapitel 1 Einleitung Die nachfolgende Bachelorarbeit beschäftigt sich mit der Möglichkeit und der Durchführung, die Skalierbarkeit und Performance einer Webanwendung durch den Einsatz der nicht-relationalen Datenbank Redis zu steigern. Dafür wird eine bereits bestehende Webanwendung, welche die relationale Datenbank MySQL zur Datenhaltung einsetzt, als Grundlage verwendet. Diese Anwendung wird hinsichtlich ihrer Qualität in Performance und Skalierbarkeit untersucht. Im nächsten Schritt wird eine Erweiterung der Anwendung auf Basis einer Redis Datenbank modelliert und funktionsfähig umgesetzt. Abschließend wird die neue Version der Anwendung mit der ursprünglichen Fassung verglichen und eine Bewertung der Anforderungen durchgeführt. Insbesondere wird das Ergebnis hinsichtlich traditionelleren Verfahren zur Erhöhung der Performance und Skalierbarkeit einer Webanwendung auf Basis einer MySQL Datenbank bewertet. Das in dieser Arbeit beschriebene Vorgehen soll dabei nicht nur Gültigkeit für die Beispielanwendung haben, sondern auch als Ideenanstoß für die Verbesserung der Performance und Skalierbarkeit von Webanwendungen durch die Verwendung einer Redis Datenbank, aber auch ähnlicher, nicht-relationaler Datenbanken, dienen. 1.1 Betriebliches Umfeld Die Anfertigung dieser Abschlussarbeit wurde bei der plista GmbH in Berlin durchgeführt. Das Unternehmen ist Betreiber eines online Werbenetzwerkes im deutschen Raum mit über 40 Millionen Besuchern auf tausenden Webseiten.1 Die plista GmbH wurde im Jahr 2008 gegründet und hat 70 Mitarbeiter in den Bereichen IT, Marketing, Personalwesen und Management.1 Die Schlüsseltechnologie Cross-Domain Collaborative Filtering2 ermöglicht eine leistungsfähige Auslieferung von Werbekampagnen. (plista GmbH 2012) Der Gegenstand dieser Arbeit steht nicht unmittelbar mit dieser Kerntätigkeit in Verbindung, sondern hat vielmehr ein gänzlich neues Projekt des Unternehmens als Grundlage. 1 Stand: April 2012 2 Werbeanzeigen und Empfehlungen werden über Domaingrenzen hinweg ausgeliefert und die individuellen Themen der Anzeigen werden aus den Interessen aller Benutzer gefiltert. 1 KAPITEL 1. EINLEITUNG 1.2 Projekt Understandr Die plista GmbH plant als Nebenprojekt die Veröffentlichung einer interaktiven und kollaborativen Internetplattform. Ziel des Projektes ist es, die Meinungsbildung und den Meinungsaustausch der Menschen zu fördern. Das Projekt verfolgt dabei keine wirtschaftlichen Interessen und soll zukünftig als eigenständiges Open-Source Projekt publiziert werden. Die Anwendung heißt Understandr und stellt im Wesentlichen eine Transformation der klassischen Idee einer Pro- und Contraliste in eine soziale Anwendung dar. Nutzer können eine kurze Aussage bzw. ein Statement1 erstellen, welches durch alle Nutzer mit Argumenten und Gegenargumenten unterstützt bzw. abgelehnt wird. Den Argumenten kann einzeln von jedem Nutzer zugestimmt werden, wodurch den Argumenten mehr Gewicht verliehen wird. Aus diesen Stimmen wird die Meinung der Nutzer zu einer Aussage ermittelt. Jedem Nutzer wird zusätzlich seine persönliche Meinung zu einer Aussage präsentiert, welche sich aus der Anzahl seiner Stimmen zusammensetzt. Die Meinung wird dabei in Prozent für jeweils pro und contra angegeben. Um den Nutzer direkt in seiner Meinungsbildung zu unterstützen, werden zusätzlich verschiedene Statistiken präsentiert. Dazu zählen zunächst generelle Analysen, wie Auflistungen von kontroversen oder viel diskutierten Aussagen, aber auch persönliche Empfehlungen. Dem Nutzer werden aufgrund seines Lese- und Stimmverhaltens relevante Diskussionen empfohlen, um ihn dadurch zu weiteren Aktivitäten auf der Plattform zu animieren. Es existiert bereits ein lauffähiger Prototyp (siehe Abbildung 1.1), welcher Gegenstand und Grundlage dieser Arbeit darstellt. Dieser Prototyp wurde vom Autor dieser Arbeit im Rahmen eines dreimonatigen Praktikums implementiert. Die beschriebenen Grundfunktionalitäten sind bereits vollständig integriert. Darüber hinaus verfügt der Prototyp über weitere Merkmale. Dazu zählt insbesondere ein Schlagwortsystem2 , mit dessen Hilfe die Aussagen kategorisiert und sortiert werden und der Nutzer einen schnellen Zugriff auf bestimmte Themengebiete erhält. Eine Suchfunktion vervollständigt die Möglichkeiten Aussagen zu entdecken und den Nutzer zu aktivieren. Weiterführende Informationen befinden sich insbesondere in den Kapiteln 3.5.1 und 3.3. 1.3 Motivation Die plista GmbH verfolgt bei der Entwicklung von Understandr mehrere Ziele. Zum einen soll eine übersichtliche Möglichkeit geschaffen werden, die Meinung zu einer konkreten Fragestellung zu erfahren. Dem Konzept stehen dabei klassische Umsetzungen, wie zum Beispie OnlineDiskussionsforen gegenüber. Bei einem Forum ist es in der Regel notwendig, sich mehrere Beiträge zu einer Fragestellung durchzulesen, um eine passende oder korrekte Antwort zu finden. Dagegen ermöglicht das Pro-Contra-Konzept besonders schnell die Meinung der beteiligten Nutzer abzulesen. Konkrete Aussagen, in Form der Argumente, können dann zusätzlich studiert werden. Ein weiteres Ziel ist es, die Nutzer aktiv bei Ihrer persönlichen Meinungsbildung zu unterstützen. 1 Im Laufe dieser Ausarbeitung werden die Begriffe Statement und Aussage im Zusammenhang mit der Beispielanwendung gleichbedeutend verwendet 2 Der Begriff Schlagwort wird im folgenden gleichbedeutend mit dem Begriff Tag verwendet. 2 KAPITEL 1. EINLEITUNG Abbildung 1.1: Ausschnitt der Understandr Oberfläche Dafür sollen in erster Linie die Empfehlungen und Verlinkungen auf andere Aussagen verantwortlich sein, welche unter anderem durch kollaboratives Filtern ermittelt werden. Der Nutzer wird dadurch auf neue Problemstellungen aufmerksam und erfährt zusätzlich welche Argumente und Gegenargumente es bereits gibt. Die gesamte Plattform ist demzufolge hoch interaktiv und lebt ausschließlich von Inhalten, die die Nutzer selbst generieren. Dieser Aspekt des Projektes ist charakteristisch für eine Web 2.0 Anwendung (Shuen 2008, S. 1) und führt zu weiteren Schlussfolgerungen bezüglich der zukünftigen Nutzung von Understandr. Die Art der Plattform hat das Potential, einen positiven, direkten Netzwerkeffekt zu generieren. Der Mehrwert einer Web 2.0 Plattform erhöht sich umso mehr Nutzer die Plattform verwenden, was wiederum die Attraktivität für neue Nutzer erhöht. (Shuen 2008, S. 32) Die plista GmbH rechnet daher mit der Möglichkeit eines schnellen Anstiegs der Nutzerzahlen und damit einhergehend einer hohe Anzahl an gleichzeitigen Zugriffen auf den Dienst. Daher soll bereits vor der Veröffentlichung die Qualität der Anwendung im Umgang mit solchen Szenarien ermittelt werden. Im betrieblichen Umfeld werden bereits erfolgreich Redis Datenbanken bei der Auslieferung von Werbeanzeigen verwendet. Daher soll geprüft werden, ob die geforderte Qualität durch den Einsatz einer solchen Datenbank erreicht werden kann und inwieweit die Nutzung praktikabel und sinnvoll ist. 3 KAPITEL 1. EINLEITUNG 1.4 Aufgabenbeschreibung Die Aufgabe der Bachelorarbeit leitet sich unmittelbar von den Anforderungen der plista GmbH an die neue Webplattform ab. Die bestehende prototypische Implementierung soll bezüglich Performance und Skalierbarkeit evaluiert werden, um den konkreten Bedarf und Umfang einer Umgestaltung zu ermitteln. Eine Steigerung der Performance und Skalierbarkeit soll dann durch den Einsatz einer Redis Datenbank erfolgen, welche parallel zur bereits bestehenden Datenbank MySQL verwendet werden soll. Das Unternehmen geht davon aus, dass die geforderte Qualität dadurch erreicht werden kann. Ein zentraler Aspekt der Aufgabe ist dabei, dass die Implementierung der Erweiterung transparent1 zu erfolgen hat. Nur relevante Teile sollen umgestaltet werden und sämtliche Schnittstellen sollen nach Möglichkeit erhalten bleiben und können somit weiterhin identisch verwendet werden. 1.5 Inhalt und Aufbau der Arbeit Diese Bachelorarbeit teilt sich in sieben Abschnitte. Im ersten Abschnitt der Arbeit werden die notwendigen theoretischen Grundlagen für das Verständnis und die Analyse der Aufgabenstellung gelegt. Im Mittelpunkt steht dabei die klare Definition der Begriffe Performance und Skalierbarkeit, sowie die gegenseitige Abgrenzung. Es werden problembezogene Informationen zum Thema MySQL vermittelt und eine Einführung in das Gebiet der nicht-relationalen Datenbanken mit Schwerpunkt auf Redis Datenbanken gegeben. Auch auf speziellere Themen wie Consistent Hashing und Objekt-relationale Mapper wird eingegangen, da sie ebenfalls Grundlagen für die nachfolgenden Kapitel darstellen. Darüber hinaus werden die allgemeinen und technischen Herausforderungen von Web 2.0 Anwendungen und sozialen Netzwerken betrachtet, um eine Verständnis für die Motivation dieser Arbeit zu schaffen. Im nächsten Abschnitt wird die Aufgabe analysiert. Dazu gehört das technische Umfeld und die funktionalen und nicht-funktionalen Anforderungen festzuhalten. Der Schwerpunkt dieses Abschnittes liegt bei der Ausarbeitung eines hinreichenden Analyseverfahrens, um die Ausgangsbedingungen quantitativ messen zu können. Dafür enthält der Abschnitt eine detaillierte Kapazitätsplanung, Beschreibung von Testaufbau und Testverfahren und ein Konzept zur Qualitätssicherung. Der dritte Abschnitt greift das erarbeitete Testverfahren auf und führt es an der bestehenden Version durch. Die Performance und Skalierbarkeit von Understandr wird in Zahlen gefasst und damit der Ist-Zustand für einen späteren Vergleich festgehalten. Der nächste Abschnitt beschäftigt sich mit dem Entwurf der Erweiterung. Dabei wird die bestehende Implementierung analysiert und verschiedene Ansätze, das existierende System mit einer Redis Datenbank zu erweitern, diskutiert. Schwerpunkt dabei ist, die Erweiterung möglichst transparent zu gestalten und eine Steigerung der Performance und Skalierbarkeit auch durch ein geeignetes Datenmodell zu unterstützen. Eine konkrete Lösung wird anschließend entworfen, bewertet und in ein Klassendesign umgesetzt. 1 Der Begriff Transparenz wird in dieser Arbeit in seiner informationstechnischen Bedeutung verwendet. Er beschreibt, dass der interne Aufbau eines Systems von außen nicht sichtbar ist. 4 KAPITEL 1. EINLEITUNG Der vierte Abschnitt beschreibt die tatsächliche Implementierung des vorangegangenen Entwurfs. Dabei wird aufgezeigt, inwieweit der Entwurf praktikabel ist und an welchen Stellen sich Probleme ergeben haben. Im nächsten Abschnitt wird eine Analyse der erweiterten Version der Anwendung durchgeführt und ihre Leistung bezüglich Performance und Skalierbarkeit in Zahlen gefasst. Darüber hinaus wird auf die funktionale Qualität der neuen Version eingegangen. Im letzten Abschnitt werden die Ergebnisse zusammengefasst und bewertet. Darüber hinaus wird geprüft, ob das beschriebene Verfahren allgemeingültig anwendbar ist. 5 Kapitel 2 Grundlagen 2.1 Web 2.0 und Soziale Netzwerke Für den Begriff des Web 2.0 existiert keine einheitliche und einfache Definition. (Governor et al. 2009, S. 1) Im September 2005 veröffentlichte Tim O’Reilly1 ein Paper, um den Begriff Web 2.0 genauer zu definieren. Bis heute ist es die am weitesten verbreitete Erklärung des Begriffes. Dabei gibt O’Reilly keine feste Definition vor, sondern erläutert den Begriff anhand von Beispielen von existierenden Webseiten und Technologien. (Governor et al. 2009, S. 2) Daraus abgeleitet sind Web 2.0-Entwurfsmuster entstanden, welche wesentliche Merkmale einer Web 2.0 Anwendung beschreiben. Für diese Ausarbeitung besonders relevant sind dabei Users Add Value (engl. für Nutzer erhöhen Mehrwert) und Data is the Next Intel Inside (engl. für Daten sind das nächste Intel Inside). Aus den Titeln dieser Muster lässt sich bereits erkennen, dass Daten einen zentralen Aspekt einer Web 2.0 Anwendung darstellen. (O’Reilly 2005) Bekannten Web 2.0 Vertretern wie Flickr, YouTube oder Facebook haben alle gemeinsam, dass sie große Datenmengen verarbeiten und diese einer sehr großen Menge an Nutzern gleichzeitig zur Verfügung stellen. Einige Zahlen der populären Web 2.0 Anwendung Facebook veranschaulichen die enormen Datenmengen und Nutzerzahlen. Im Juni 2010 hatte Facebook 400 Millionen aktive Nutzer, welche jede Woche 5 Milliarden Inhalte (Status Updates, Kommentare, Uploads, Nachrichten und weiteres) teilen und 3 Milliarden Fotos im Monat hochladen. Tausende Cache-Server mit jeweils mehreren Terabyte Speicher ermöglichen diese hohen Kapazitäten. (highscalability.com 2012) Diese Zahlen zeigen, dass bei populären Webseiten mit vom Nutzer generierten Inhalten und einem positiven Netzwerkeffekt die Datenverarbeitung eine ganz neue Herausforderung darstellt. Die betroffenen Unternehmen müssen teilweise gänzlich neue Wege für die Entwicklung ihrer technischen Infrastruktur finden. So entwickelte Facebook 2008 das verteilte und skalierbare Datenbanksytem Cassandra oder den PHP-Compiler HipHop, um die Ausführungsgeschwindigkeit ihrer Software zu erhöhen. Etablierte technische Lösungen schienen, den neuen und erhöhten Anforderungen nicht gerecht zu werden. Entwickler einer neuen Web 2.0 Plattform sollten sich also diesen neuen Herausforderungen bewusst sein und ihre Web 2.0 Anwendungen für die Verarbeitung großer Nutzerzahlen und Datenmengen zu optimieren. Darüber hinaus muss aufgrund eines möglichen 1 Softwareentwickler und Gründer des O’Reilly Verlages 6 KAPITEL 2. GRUNDLAGEN Netzwerkeffektes immer von einem schnellen Anstieg der Werte ausgegangen werden. Auch darauf sollte eine moderne Web 2.0 Anwendung in jedem Fall vorbereitet sein. 2.2 Performance Der Begriff Performance ist in der Informationstechnik gleichzusetzen mit Leistungsfähigkeit. Es ist möglich, Performance zu messen und zu vergleichen. Die Kriterien zur Bewertung von Performance ergeben sich aus dem zu bewertenden System. (ITWissen 2012) Verschiedene Kennzahlen können bei der Bewertung verwendet werden. Diese Ausarbeitung beschäftigt sich mit der Performance einer Webanwendung. Dabei sind besonders die Kennzahlen Antwortzeit, Durchsatz und Ressourcenauslastung relevant. (Meier 2007, S. 24) Antwortzeit Die Zeit, die ein System benötigt um auf eine Anfrage zu reagieren wird als Antwortzeit bezeichnet. Sie wird häufig in Sekunden angegeben. Ein gängiges Beispiel ist der Aufruf einer beliebigen Webseite. Die Zeit, die vergeht bis die Seite im Browser erscheint, ist in diesem Fall die Antwortzeit. Die Angabe dieser Zeit kann in mehrere Komponenten zerlegt werden. Im Fall einer Webseite wäre das die Antwortzeit des Browsers, die Netzwerkzeit und die Zeit, die der Server zum Bearbeiten der Anfrage benötigt. (Menascé et al. 2004, S. 13) Die Antwort wird häufig als wichtiges Qualitätskriterium für eine Webanwendung gesehen, da Nutzer bei langen Antwortzeiten die Anfrage abbrechen. Durchsatz Die Anzahl an Arbeitseinheiten, die in einer bestimmen Zeiteinheit von einem System verarbeitet werden kann, wird als Durchsatz bezeichnet. Beispiele sind Anfragen pro Sekunde, Aufrufe am Tag oder Hits pro Sekunde.(Meier 2007, S. 26) Die verwendete Angabe ergibt sich aus dem Kontext. Im Fall von Webanwendungen wird häufig von Anfragen auf die Webseite pro Sekunde oder Minute gesprochen. (Menascé et al. 2004, S. 14) Ressourcenauslastung Welche Ressourcen eines Systems in welchem Maße bei einer Anfrage verwendet werden wird als Ressourcenauslastung bezeichnet. Die wichtigsten Ressourcen dabei sind Prozessor, Arbeitsspeicher, Festplatten Input/Output (I/O) und Netzwerk I/O. (Meier 2007, S. 25) 2.3 Skalierbarkeit Ein System wird als skalierbar bezeichnet, wenn es in der Lage ist, mit einer erhöhten Auslastung umzugehen, ohne dass die Performance negativ beeinflusst wird. (Meier 2007, S. 26) Cal Henderson (2006, S. 246) beschreibt eine skalierbares System genau mit drei Charakteristiken: • Ein System kann sich an eine erhöhte Nutzung anpassen • Ein System kann sich an einen erhöhten Datenbestand anpassen • Ein System ist wartbar 7 KAPITEL 2. GRUNDLAGEN Der Begriff der Skalierbarkeit muss ganz klar vom Begriff Performance getrennt werden. Ein System mit sehr guter Performance muss nicht zwangsläufig auch gut skalierbar sein. Beispielsweise ist eine Webanwendung mit einer sehr guten Antwortzeit mit 1000 Nutzern und 1 GB an Daten nur dann skalierbar, wenn sie die Antwortzeit bei der zehnfachen Anzahl an Nutzern halten kann. (Henderson 2006, S. 246) Vertikale Skalierung Durch das Hinzufügen von Ressourcen zu einem System, kann die Skalierbarkeit dieses erhöht werden. Dieses Verfahren wird als vertikale Skalierung bezeichnet. Gängige Ressourcen sind Arbeitsspeicher oder Prozessor. Zum Beispiel kann ein Webserver, dessen Kapazitäten an die Grenzen gestoßen sind, durch einen leistungsfähigeren Server ersetzt werden. Sollten die Kapazitäten des neuen Servers ebenfalls nicht mehr ausreichen, würde dieser wieder ersetzt werden. Der offensichtliche Nachteil dieses Verfahrens ist, dass der Skalierung eine Obergrenze gesetzt ist, wenn keine bessere Hardware auf dem Markt verfügbar ist. Darüber hinaus sind die Kosten für Hardware nicht linear, sondern exponentiell. Etwas leistungsfähigere Hardware kann oft ein Vielfaches an Mehrkosten verursachen. Die Stärke der vertikalen Skalierung liegt beim einfachen Softwaredesign. Die Software kann einfach auf die neue Hardware portiert werden, ohne die Implementierung anzupassen. (Henderson 2006, S. 248-250) Horizontale Skalierung Das zweite Konzept, ein System zu skalieren, basiert ebenfalls auf dem Hinzufügen von Hardware. Im Gegensatz zur vertikalen Skalierung wird bestehende Hardware nicht modifiziert, sondern einfach um weitere Hardware ergänzt. Im Fall eines einzelnen Webservers, würde im Zuge eines Wachstums, ein zweiter Server dem System hinzugefügt werden. Der Vorteil dieser Methode ist, dass es möglich ist, reguläre Hardware für das System zu verwenden. Man ist nicht auf leistungsfähige Serverhardware angewiesen. Dadurch ist es theoretisch möglich die Hardwarekosten zu senken. Dem gegenüber steht allerdings der erhöhte Administrationsaufwand für die Wartung und Installation mehrerer Rechner. Allerdings ist der Aufwand nicht linear, da man davon ausgehen kann, dass alle Rechner mit der gleichen Basissoftware ausgestattet sind. Nachteil und Herausforderung der horizontalen Skalierung ist die Software, welche in der Lage sein muss, mit einem System, bestehend aus vielen Rechnern, umzugehen. Die Software muss in der Lage sein, mit der Hardware zu skalieren. (Henderson 2006, S. 248-250) 2.4 Relationale Datenbanken Das relationale Datenbankmodell wurde 1970 von Edgar Codd entwickelt und hat sich bis heute als das Wichtigste, noch im Einsatz befindliche, Datenbankmodell etabliert. Grundlage ist die Darstellung von Daten in Tabellen und die Beziehung der Tabellen zueinander. Durch diese Relationen lassen sich echte Gegebenheiten in der Datenbank nachbilden. Als Relationale Datenbankmanagementsysteme (RDBMS) werden alle Datenbanken bezeichnet, die diese Systematik verwenden. Für dem Zugriff auf die Daten wird häufig die Structured Query Language (SQL) eingesetzt. (Pröll et al. 2011, S. 26-27) In dieser Arbeit steht das seit 1995 entwickelte, freie Datenbanksystem MySQL im Mittelpunkt, welches zu den RDBMS zählt und SQL verwendet. Der nächste Abschnitt beschäftigt sich mit Performance und Skalierbarkeit von MySQL und theoretischen Möglichkeiten, diese zu steigern. 8 KAPITEL 2. GRUNDLAGEN 2.4.1 Performance von MySQL Wie performant eine MySQL-Installation sein kann wird von verschiedensten Faktoren beeinflusst. Einer der wichtigsten Faktoren ist, ob die angefragten Daten von der Festplatte geladen werden müssen oder sie sich schon in einem Puffer befinden. MySQL verfügt über ein QueryCache, in dem die Ergebnisse bereits ausgeführter Abfragen zwischengespeichert werden und bei erneuter Anfrage das Ergebnis sofort zurückgegeben wird. (Pröll et al. 2011, S. 318-319) Der Query-Cache liegt im Arbeitsspeicher und ist daher sehr schnell. Allerdings unterliegt die Nutzung des Caches einigen Einschränkungen bezüglich zulässiger Select-Anweisungen, welche zwischengespeichert werden können. So können keine Abfragen, die Teil anderer Abfragen sind, oder Abfragen mit Variablen verwendet werden. Die zwischengespeicherten Daten werden jedes Mal gelöscht, wenn eine Änderung auf eine betreffende Tabelle durchgeführt wird. (Pröll et al. 2011, S. 167-168) Daneben wird die Performance immer beeinflusst, wenn die Datenbank auf Ressourcen, wie Prozessor, Arbeitsspeicher oder Festplatte, wartet. Aber auch andere Tabellen stellen Ressourcen dar, welche eventuell durch weitere Anfragen blockiert sind. Auch das Design der Abfragen und der Tabellen hat maßgebliche Auswirkungen auf die Leistungsfähigkeit der Datenbank. Die genaue Performance kann im Einzelfall durch entsprechende Messungen und Analysen erfolgen. MySQL bietet für diesen Zweck eigene Benchmark- und Profilingbefehle, um individuelle Schwachstellen zu finden. (Pröll et al. 2011, S. 318-321) Zusätzlich existieren verschiedene Methoden zur Abfrageoptimierung, unter anderem das Slow-Query-Log, mit dessen Hilfe sich langsame Abfragen schnell ermitteln und optimieren lassen. Das Anlegen von Indizes, um häufig abzufragende Werte in separaten Index-Tabellen effektiv zu verwalten und schnell zugänglich zu machen, gehört ebenfalls dazu. (Pröll et al. 2011, S. 359-363) Zusammengefasst lässt sich festhalten, dass die Bewertung der Performance von MySQL individuell betrachtet werden muss und eine pauschale Aussage nicht zu treffen ist. Optimal konfiguriert, kann eine auf MySQL Datenbank eine sehr gute Performance aufweisen. Auf der anderen Seite kann in speziellen Anwendungsfällen die Performance von MySQL nicht ausreichend sein und es ist notwendig auf alternative Systeme zurückzugreifen. Siehe dazu Kapitel 2.5. 2.4.2 Skalierbarkeit von MySQL Bei der Betrachtung der Skalierbarkeit von MySQL soll an dieser Stelle nur auf horizontale Skalierung eingegangen werden. Diese Art der Skalierung stellt eine Herausforderung da, für die sich verschiedene Lösungsansätze entwickelt haben. Einige der wichtigsten Konzepte werden in diesem Kapitel näher betrachtet. Eine vertikale Skalierung ist problemlos möglich, unterliegt aber den in Kapitel 2.3 diskutierten Einschränkungen. Replikation Webanwendungen weisen häufig weit mehr lesende als schreibende Operationen auf, oftmals in einem Verhältnis von 10 zu 1. Von diesem Umstand profitiert das Konzept der Replikation, welches von MySQL nativ unterstützt wird. Es ist dem Begriff zu entnehmen, dass dabei Daten auf ein oder mehr Rechner repliziert werden, um dadurch eine höhere Lesekapazität zu erhalten. Dabei wird zwischen Master und Slave unterschieden. Eine MySQL-Installation kann ein Slave von genau einem Master sein. Umgekehrt kann eine Installation ein Master von keinem oder vielen Slaves sein. Sämtliche Schreibopera9 KAPITEL 2. GRUNDLAGEN tionen werden nur an einen Master-Server gesendet, der alle Operationen wie gewöhnlich ausführt und zusätzlich in einer Logdatei alle Operationen protokolliert. Ein verbundener Slave-Server liest diese Datei und führt die Operationen aus. Auf diesem Weg wird er in exakt den gleichen Zustand versetzt. Ein Master mit einem Slave hätte dann die doppelte Lesekapazität usw. Die Schreibkapazität bleibt bei einem Master allerdings konstant. (Henderson 2006, S. 277-279) Clustering Um sowohl die Lesekapazitäten, als auch Schreibkapazitäten zu skalieren kann eine MySQL Datenbank in sogenannte Cluster aufgeteilt werden. In der einfachsten Form werden beim Clustering die verschiedenen Tabellen einer Datenbank auf mehrere Server verteilt. Dabei müssen sich Tabellen, auf denen Joins angewendet werden sollen auf einem Server befinden. Joins über Rechnergrenzen hinweg sind nicht ohne Weiteres möglich. Durch diese Einschränkung ist Clustering im möglichen Leistungsgewinn limitiert. (Henderson 2006, S. 287-289) Sharding Um diese Limitierung zu überwinden, ist es notwendig, die eigentlichen Tabellen ebenfalls aufzuteilen. Dieses Verfahren wird als Sharding oder auch Federation bezeichnet. Dabei werden Tabellen anhand ihrer Zeilen aufgeteilt und auf verschiedene Server verteilt. Die Implementierung dieser Methode ist außerordentlich kompliziert. Um Operationen auf den Tabellen auszuführen, müssen die Teile über mehrere Server hinweg zusammengeführt werden und Joins werden unpraktikabel aufwendig. Für die Bewältigung diese Art der Verteilung, wäre in jedem Fall die Nutzung einer Middleware notwendig. (Henderson 2006, S. 287-289) Die Software MySQL Cluster1 bietet mittlerweile diese Funktionalität. Unter der Bezeichnung Auto-Sharding ist es möglich Tabellen auf beliebig vielen Server zu verteilen und dabei bleibt der Zugriff absolut transparent. Realisiert wird die Funktion durch eine Hash-Funktion auf den Primärschlüssel (siehe dazu auch 2.6). Auch Joins sind weiterhin wie gewohnt einsetzbar. Ziel ist es, den Nachteil der Replikation auszugleichen und auch Schreiboperationen zu skalieren. (Oracle 2012, S. 5-6) Die verschiedenen Verfahren zeigen, dass es möglich ist MySQL zu skalieren. Unter Nutzung des MySQL Clusters sind sogar beachtliche Werte möglich. Der Hersteller gibt über eine Milliarde Schreiboperationen pro Minute bei 30 Servern an. (Oracle 2012, S. 9) Für den konkreten Anwendungsfall sollte die zweckmäßigste Methode gewählt werden und eventuelle Limitationen ermittelt werden. 2.5 NoSQL Datenbanken Der Begriff NoSQL vereint eine ganze Reihe von Datenbanksystemen, welche sich alle das Ziel gesetzt haben, die existierenden Nachteile der etablierten, relationalen Datenbanksysteme, auszugleichen. (Gull 2011, S. 18) Obwohl der Begriff und die neuen Datenbanksysteme erst in den letzten Jahren massiv an Popularität gewonnen haben, geht die Geschichte der NoSQLDatenbanksysteme bis in das Jahr 1979 zurück. In diesem Jahr entwickelte Ken Thompson2 1 Der Begriff ist an dieser Stelle verwirrend, da wesentlich mehr als einfaches Clustering zur Verfügung steht. 2 Mitentwickler des UNIX-Betriebssystems und der Programmiersprache C 10 KAPITEL 2. GRUNDLAGEN bereits eine Key-Hash-Datenbank namens DBM. Es folgten in den 80er Jahren verschiedene NoSQL-Systeme, wie Lotus Notes, BerkeleyDB und GT.M. (Edlich et al. 2010, S. 1) Im Jahr 1998 wurde dann zum ersten Mal den Begriff NoSQL, im Zusammenhang mit einer Open-SourceDatenbank von Carlo Strozzi, verwendet. (Gull 2011, S. 188) Obwohl dieses System immer noch auf einem relationalen Datenbankmodell basierte, stand keine SQL-API mehr zur Verfügung. Erst seit der Jahrtausendwende, durch die Entstehung des Web 2.0 und der Tendenz immer größere Datenmengen zu generieren, begann die NoSQL Bewegung. Seit 2006 werden die heute üblichen NoSQL-Systeme, wie HBase/Hypertable, CouchDB, Cassandra, Voldemort, Dynamo/Dynomite, MongoDB, Redis und viele mehr1 entwickelt. (Edlich et al. 2010, S. 1) Der eigentliche Begriff NoSQL tauchte aber erst Anfang 2009 bei einem Treffen zum Thema strukturierte, verteilte Datenspeicher auf. Johan Oskarsson bereichnete damit Datenbanken, die nicht relational sind, auf verteilten Systemen liegen und meistens auf ACID-Eigenschaften2 verzichten. (Gull 2011, S. 188) 2.5.1 Definition und Kategorien Eine offizielle Definition des Begriffes NoSQL existiert nicht. Das NoSQL-Archiv (nosql-databases.org 2012) gibt an, dass ein Datenbanksystem, welches einige der folgenden Eigenschaften aufweist, als NoSQL Datenbank gilt: • nicht relational, verteilt und horizontal skalierbar • Open-Source • schemafrei • einfache Replikationsunterstützung • einfache API • Konsistenzmodell BASE3 • Unterstützung für sehr große Datenmengen Das NoSQL-Archiv listet derzeit mehr als 120 NoSQL-Kernsysteme4 , welche sich in vier wichtige Kategorien gliedern und von Edlich et al. (2010, S. 7) wie folgt beschrieben werden. Key-Value-Systeme Diese Kategorie stellt ein einfaches Schema, bestehend aus Schlüssel und Wert zur Verfügung. Oft können die Schlüssel in Namensräumen sortiert werden und Werte 1 http://nosql-databases.org 2 Atomicity, Consistency, Isolation, Durability (engl. für Atomarität, Konsistenz, Isolation, Beständigkeit) 3 Basically Available, Soft state, Eventual consistency - NoSQL Datenbanken achten nicht streng auf Konsistenz der Daten, da Performance und Verfügbarkeit dadurch sinken. Die Datenkonsistenz wird zum Beispiel, von einer nachfolgenden Operation wieder hergestellt. Ein konsistenter und inkonsistenter Zustand wechseln sich somit ständig ab. (Gull 2011, S. 18) 4 Es wird zwischen Kernsystemen und nachgelagerten Systemen unterschieden, wobei die erst genannten direkt aus den Bedürfnissen des Web 2.0 entstanden sind und Letztere erst später entstanden sind. 11 KAPITEL 2. GRUNDLAGEN bestimmten Datentypen zugeordnet sein, wie Hashes oder Listen. Durch dieses einfache Schema ist eine schnelle und effiziente Datenverarbeitung möglich. Als Nachteile gelten die nur einfachen Abfragemöglichkeiten. Als größter Vorteil der Key-Value-Systeme gilt die einfache Skalierbarkeit. In traditionellen Datenbanken sind die Daten relational miteinander verbunden, wodurch eine Skalierung oft erschwert wird. Eben diesen Nachteil haben die Key-Value-Systeme nicht. Jedes SchlüsselWert Paar steht zunächst nur für sich und lässt sich deshalb viel unproblematischer auf verschiedene Rechner verteilen. (Edlich et al. 2010, S. 131) Bekannte Vertreter: Redis, Voldemort, Scalaris, MemcacheDB, Riak Column-Family-Systeme Diese Systeme ähneln klassischen Tabellen, wobei ein Schlüssel auf eine unterschiedliche Anzahl an Key-Value Daten zeigt. Einer solchen Spalte können jederzeit weitere Datenpaare hinzugefügt werden. Bekannte Vertreter: HBase, Cassandra, Hypertable Document Stores Diese Datenbanken speichern strukturierte Daten in verschiedenen Formaten und verweisen über eine ID auf diese. Häufig verwendete Formate sind JSON, YAML oder RDF. Bekannte Vertreter: CouchDB, MongoDB Graphendatenbanken Die letzte Kategorie speichert Daten in Graph- oder Baumstrukturen und stellt dadurch die Beziehung von Daten untereinander dar. Die Einsatzmöglichkeiten von Graphendatenbanken sind vielfältig und in den letzten Jahren hat ihre Bedeutung stark zugenommen. Durch das Verbinden von Informationen im Internet mit Geodaten, Pfadsuche oder Molekülmodellierung. Jede Graphendatenbank verwendet unterschiedliche Arten von Graphen. Die Wichtigsten sind derzeit die Property-Graphen. Bekannte Vertreter: Neo4j, SonesDB, FlockDB 2.5.2 Redis Redis ordnet sich in die Kategorie der Key-Value-Systeme ein und wurde Anfang 2009 von Salvatore Sanfillippo als Open-Source Projekt gestartet. Der Begriff Redis leitet sich vom Wort Redistribute (engl. für umverteilen) und von Remote Dictionary Service (engl. for entfernter Wörterbuchdienst) ab. Redis gilt als Nachfolger von Memcached1 . Alle Daten werden im Arbeitsspeicher gespeichert und gleichen sich nach und nach auf die Festplatte ab. Mit leichten Geschwindigkeitseinbußen können die Daten auch unmittelbar auf die Festplatte geschrieben werden. Es gilt demzufolge, dass alle Daten einer Redis Datenbank in den Arbeitsspeicher passen müssen. (Edlich et al. 2010, S. 132) Die Unterstützung für virtuellen Speicher gilt als überholt. (Seguin 2012, S. 7) Die Datenbank ist sehr schnell. Um die 100.000 Zugriffe pro Sekunde sind gängige Werte. Schreiboperationen sind in der Regel schneller als Leseoperationen. (Offizielle Redis Webseite 2012) Redis stellt fünf verschiedene Datenstrukturen zur Verfügung. Jeder Wert in der Datenbank kann aus einer dieser Strukturen bestehen. 1 Cache-Server zum Ablegen von Daten in den Arbeitsspeicher 12 KAPITEL 2. GRUNDLAGEN String Mit dem einfachsten Datentyp String lassen sich beliebige Werte speichern. Der Begriff String ist dabei etwas verwirrend, da natürlich nicht nur Zeichenketten, sondern auch Zahlen abgelegt werden können. Neben den Befehlen zum Speichern und Auslesen des Datentyps bietet Redis zusätzliche Befehle für die Manipulation der Werte. Zum Beispiel ist eine Verkettung von Zeichenketten oder das gezielte Auslesen eines Bereiches der Zeichenkette möglich. Handelt es sich um einen Zahlenwert kann dieser mit entsprechenden Operatoren erhöht oder verkleinert werden. Demzufolge ist der String Datentyp für Zähler oder für die Speicherung von serialisierten Objekten geeignet. List Dieser Datentyp bietet eine einfache Listenfunktionalität und stellt ein Feld von Strings dar, auf welches verschiedenste Operationen angewendet werden können. Neue Elemente lassen sich an das Ende oder an den Anfang einfügen. Die Elemente behalten stets ihre Reihenfolge und es ist möglich über den Index auf bestimmte Werte zuzugreifen. Darüber hinaus können Werte mehrfach vorkommen. Passende Einsatzmöglichkeiten dieses Datentyps sind das Speichern von Logs oder Referenzen auf andere Schlüssel. Set Mit Sets lassen sich eindeutige Werte in einem unsortierten Feld abspeichern. Im Prinzip lassen sich nur Werte hinzufügen und löschen. Aber auch das Zusammenführen von mehreren Sets ist möglich. Ein Anwendungsfall für Sets ist eine Liste von Freunden auf einer sozialen Plattform. Sorted Set Dieser Datentyp bietet zunächst die gleiche Funktionalität wie die Sets. Wird jedoch um eine Gewichtung ergänzt. Jedes Element besitzt zusätzlich einen Score, der allerdings vom Nutzer angegeben werden muss. Der Wert kann erhöht oder verringert werden. Anhand des Scores können die Elemente des Sets sortiert und ausgegeben werden. Verwendung kann dieser Datentyp bei Ranglisten jeder Art finden. Hash Der letzte Datentyp bietet die Möglichkeit, den Wert eines Schlüssels besser zu qualifizieren. Der Wert besteht aus einer einfachen Hashtabelle. Die einzelnen Felder können separat befüllt und ausgelesen werden. Ebenfalls sind einfache Rechenoperationen, wie bei den reinen Strings, möglich. Der Wert eines einzelnes Feldes kann zum Beispiel erhöht oder verkleinert werden. Hashes eignen sich zum Speichern von strukturierten Daten. (Seguin 2012, S. 9-13) Redis ist nicht in der Lage nativ zu skalieren. Erst in zukünftigen Versionen soll die dynamische Verteilung der Daten auf mehrere Server unterstützt werden. Dennoch existieren zwei einfache Möglichkeiten, Redis zu skalieren Client-Sharding Das Verteilen bzw. Sharding der Daten auf mehrere Rechner lässt sich leicht auf Applikationsebene durchführen. Mit Hilfe einer Hash-Funktion (siehe Kapitel 2.6) lassen sich die Schlüssel analysieren und auf verschiedene Server verteilen, wodurch die Last auf einen Server verringert wird. (Edlich et al. 2011, S. 166) Replikation Analog zu MySQL unterstützt Redis auch Replikation. Dabei kann ein Master seine Daten auf mehrere Slaves verteilen. Wie bei MySQL wird auf diesem Weg die Lesekapazität erhöht. (Edlich et al. 2011, S. 166) 13 KAPITEL 2. GRUNDLAGEN 2.6 Consistent Hashing Die einfache Skalierbarkeit von Key-Value-Datenbanken, wie Redis, basiert unter anderem auf dem Prinzip des Consistent Hashing. Im Allgemeinen bedeutet Hashing, dass nach einem festgelegten Algorithmus ein Wert aus einer großen Menge auf einen Wert aus einer viel kleineren Menge abgebildet wird. Das wohl einfachste Beispiel eines solchen Algorithmus ist die Modulo-Funktion. Hash-Funktionen haben verschiedenste Einsatzmöglichkeiten. Unter anderem werden sie in verteilten Systemen verwendet. Zum Beispiel kann im Rahmen einer Client-Server-Anwendung, einem einzelnen Client aus einer großen Menge schnell ein passender Server zugeordnet werden. Sollte sich die Anzahl der Server nie ändern, würde die Modulo-Funktion für ein solches Szenario völlig ausreichen. Allerdings unterliegen solche Systeme in der Realität ständigen Veränderungen. Die tatsächliche Anzahl an Servern ist oft nicht vorhersehbar oder die Anforderungen an die Anwendung wachsen und weitere Server müssen hinzugefügt werden. Bei einer normalen Hashfunktion würden sich durch das Hinzufügen eines Servers fast alle Zuordnungen ändern und die betroffenen Daten müssten verschoben werden. Im Falle von großen Datenmengen ist das zweifellos nicht erwünscht. Consistent Hashing bietet eine Möglichkeit dieses Problem zu lösen. Dabei wird der mögliche Adressraum als Ring angenommen, auf dem zunächst die Server anhand ihres Hashwertes verteilt werden. Im nächsten Schritt werden die Hashwerte der Clients ermittelt. Der jeweils verantwortliche Server ist der im Uhrzeigersinn am nächsten gelegene auf dem Ring. Sollte ein Server aus dem Ring entfernt werden, muss nun nur ein Teil der Daten verschoben werden. Der nachfolgende Server würde die Daten des entfernten Servers übernehmen. Wird ein weiterer Server hinzugefügt, werden ihm alle Clients zwischen ihm und seinem Vorgänger zugewiesen. (Edlich et al. 2010, S. 36-39) Abbildung 2.1: Hinzufügen und Entfernen von Servern aus Edlich et al. 2010, S. 37 2.7 Objekt-relationales Mapping Auf eine relationale Datenbank wird häufig mit SQL zugegriffen, was dem Konzept der objektorientierten Programmierung, möglichst immer mit Objekten zu arbeiten, widerstrebt. Daher wird eine zusätzliche Schicht verwendet, welche den Zugriff auf die Datenbank abstrahiert und die 14 KAPITEL 2. GRUNDLAGEN relationalen Daten als Objekt darstellt. Dieses Verfahren wird als Objekt-relationales Mapping bezeichnet. Der Zugriff auf die Datenbank kann dann ausschließlich über Objekte erfolgen und es ist nicht länger notwendig direkt SQL zu verwenden. ID 1 2 3 Vorname Bruce Peter Tony Nachname Wayne Parker Stark Tabelle 2.1: Beispieltabelle Personen Zum Beispiel ist in Tabelle 2.1 eine einfache Form einer SQL-Tabelle dargestellt. Nun könnte es eine Klasse Person geben, welche diese Tabelle repräsentieren würde. Durch den Aufruf einer Methode zum Abrufen von Daten erhält man ein Objekt der Klasse. $person = Person :: findById (2); Dieses Objekt würde eine Zeile bzw. einen Datensatz der Tabelle repräsentieren. Über die Eigenschaften des Objektes könnten dann die einzelnen Informationen des Datensatzes abgerufen und manipuliert werden. $person - > Vorname = " Clark " ; $person - > Nachname = " Kent " ; Nach einer Manipulation des Objektes würden durch den Aufruf einer weiteren Methode die Daten zurück in die Datenbank geschrieben werden. $person - > save (); ID 2 Vorname Clark Nachname Kent Tabelle 2.2: Auszug aus der Beispieltabelle Personen nach der Manipulation (Möhrke 2012, S. 327-329) 15 Kapitel 3 Analyse und Vorgehen 3.1 Abgrenzung An dieser Stelle muss die zentrale Aufgabe dieser Arbeit von anderen Verfahren und Techniken um die Performance oder Skalierbarkeit einer Webanwendung zu erhöhen, abgegrenzt werden. Grundsätzlich ist festzuhalten, dass eine Webanwendung ein verteiltes System ist, dessen Qualität von verschiedensten Faktoren abhängig ist. Im einfachsten Fall ist mindestens ein Client, ein Server und ein Netzwerk beteiligt. Alle Teile des Systems bestimmen das Verhalten. Eine umfassende Optimierung müsste sich also mit allen Aspekten auseinandersetzen. Der Fokus dieser Arbeit liegt aber auf der Optimierung der Datenhaltung. Es besteht die Möglichkeit, dass die geforderten Anforderungen mit Optimierungen an anderen Stellen der Anwendung ebenfalls zu erreichen sind und außerhalb des Rahmens dieser Arbeit sollten diese zusätzlich geprüft werden. 3.2 Vorgehen Um allen Anforderungen in vollem Umfang gerecht zu werden, wird neben dem Entwurf und der Umsetzung, der Fokus besonders auf die Analyse gelegt. Die gesamte Umsetzung des Projektes unterliegt dafür einem genau festgelegten Vorgehen, welches zunächst hauptsächlich in drei Schritte gegliedert ist: Analyse des Ist-Zustandes Im ersten Schritt wird der Ist-Zustand hinsichtlich Performance und Skalierbarkeit analysiert. Um den Erfolg durch die Weiterentwicklung im Nachhinein zu bewerten ist es notwendig, den bestehenden Zustand hinreichend zu analysieren und die aktuelle Performance und Skalierbarkeit in Zahlen zu fassen. Mit dem Ziel diese Bewertung mit einer hohen Qualität durchzuführen, wird sich das Vorgehen an den sieben Performance-Test Aktivitäten aus dem Buch Performance Testing Guidance for Web Applications (Meier 2007, S. 45) orientieren. Die sieben Aktivitäten sind über mehrere Kapitel dieser Arbeit verteilt und die Zuordnung ist in Tabelle 3.1 dargestellt. 16 KAPITEL 3. ANALYSE UND VORGEHEN Aktivität Kapitel Testumgebung identifizieren Kapitel 3.3 Performance Kriterien identifizieren Nicht-funktionale Anforderungen in Kapitel 3.4.2 Planung und Design von Tests Kapitel 3.5 Testumgebung konfigurieren Kapitel 3.6 Implementierung des Testdesign Kapitel 3.6 Tests ausführen Kapitel 4 Analyse und Zusammenfassung Kapitel 4 Tabelle 3.1: Verteilung der sieben Performance-Test Aktivitäten über die Kapitel dieser Arbeit Neben diesen sieben Aktivitäten wird der bestehende Zustand in geeigneten Testverfahren festgehalten werden. Damit wird die Möglichkeit geschaffen, die neue Version auf Fehler und abweichendes Verhalten zu prüfen. Durch dieses Vorgehen ist es hinterher möglich, den neuen Zustand mit dem Ausgangszustand zu vergleichen, indem die gleichen Tests wiederholt werden. Siehe dazu Kapitel 3.7. Entwurf und Implementierung der Erweiterung Nach Abschluss der Analyse des bestehendes Zustandes wird mit dem Entwurf und der anschließenden Implementierung der Erweiterung begonnen. Analyse der erweiterten Version Im letzten Schritt wird die Analyse des Ist-Zustandes ebenfalls an der neuen Version der Anwendung durchgeführt. Dadurch lässt sich die Kapazität und die Funktionsweise der Weiterentwicklung mit dem ursprünglichen Zustand direkt vergleichen und eine hinreichende Qualität kann gewährleistet werden. 3.3 Technisches Umfeld Understandr basiert auf einer typischen Zusammenstellung verschiedener Server- und Webtechnologien. Grundlage bildet der LAMP-Stack, bestehend aus der Linux-Distribution Debian1 , dem Webserver Apache, dem Datenbanksystem MySQL und der Skriptsprache PHP. Demzufolge kommt PHP für die eigentliche Anwendungsentwicklung zum Einsatz. Dabei wird auf das quelloffene PHP-Framework CakePHP gesetzt. CakePHP basiert auf der Model View Controller (MVC) Architektur und bietet somit eine strikte Trennung zwischen den Schichten Darstellung (View), Steuerung (Controller) und Datenhaltung (Model). Für die Darstellungsschicht werden die gängigen Technologien Hypertext Markup Language (HTML), Cascading Style Sheets (CSS) und JavaScript verwendet. Darüber hinaus findet die freie JavaScript-Bibliothek jQuery Anwendung, welche die Nutzung von JavaScript, insbesondere die Manipulation des Document Object Model (DOM) und die Animation von Elementen, stark vereinfacht. 1 Alternativ kann und wird auch Ubuntu, eine wiederum auf Debian basierende Linux-Distribution, eingesetzt 17 KAPITEL 3. ANALYSE UND VORGEHEN Für die Analyse und den Test der Anwendung wird eine Testumgebung eingerichtet. Die serverseitige Infrastruktur dafür stellt die Hochschule für Technik und Wirtschaft (HTW) Berlin durch das Aufsetzen von virtuellen Maschinen auf einem Server mit der Virtualisierungssoftware Xen. Für die Testumgebung wird ein Webserver und ein Datenbankserver aufgesetzt. Der inhaltliche Fokus auf die Datenbank spiegelt sich dadurch auch in der Infrastruktur wieder und es wird eine absolut unabhängige Bearbeitung der Datenbankinfrastruktur ermöglicht. Als Client für Testanfragen wird ein einfacher Desktop-PC fungieren, welcher über das Internet auf die Testserver zugreift. Abbildung 3.1 zeigt den vollständigen Aufbau in einem Deployment-Diagramm. Abbildung 3.1: Deployment-Diagramm des technischen Umfelds 18 KAPITEL 3. ANALYSE UND VORGEHEN 3.4 3.4.1 Anforderungen Funktionale Anforderungen Es ist gefordert, dass die Anwendung sich nach der Ergänzung um die Redis Datenbank exakt genauso verhält wie im Ausgangszustand. Ein potentieller Benutzer der Weboberfläche darf keinen Unterschied bei den Funktionalitäten feststellen. Dadurch folgt, dass keine besonderen funktionalen Anforderungen an die Umsetzung gestellt werden. Alle Funktionen müssen entsprechend dem Ist-Zustand zur Verfügung stehen. 3.4.2 Nicht-funktionale Anforderungen Durch die Weiterentwicklung sollen verschiedene nicht-funktionale Anforderungen an die Anwendung sichergestellt werden. Dazu zählt zunächst natürlich die Steigerung der Performance. Langfristiges Ziel ist es, eine durchschnittliche Antwortzeit von 50 Millisekunden zu erreichen. Ziel dieser Arbeit ist nicht, genau diesen Wert zu erreichen, sondern zunächst eine Steigerung der Performance überhaupt zu erreichen. Es wird davon ausgegangen, dass ein entsprechend sehr guter Wert von 50 ms weitere Optimierungen erfordert, welche nicht Gegenstand dieser Arbeit sind (siehe Kapitel 3.1). Neben der Performance ist die Skalierbarkeit eine weitere nicht-funktionale Anforderung. Zunächst ist gefordert, dass die Anwendung auf einem Rechner bzw. auf einem Webserver und einem Datenbankserver besser skaliert als es momentan der Fall ist. Konkret heißt das, dass die Anwendung bei steigender Nutzerzahl einen konstanten Durchsatz pro Minute halten kann. Für den Fall, dass die Nutzerzahl auf ein Niveau ansteigt, bei dem der Durchsatz einbricht, soll eine horizontale Skalierung der Hardware unkompliziert und hinreichend schnell möglich sein. Faktisch heißt das, dass ein bestehendes System innerhalb kürzester Zeit und mit geringem Aufwand um weiteren Server erweitert werden kann. Nicht-funktionale Anforderungen Relative Steigerung der Performance Steigerung der Skalierbarkeit Einfache horizontale Skalierbarkeit ermöglichen Transparenz der Erweiterung Tabelle 3.2: Übersicht der nicht-funktionalen Anforderungen 3.4.3 Technische Anforderungen Die technischen Anforderungen orientieren sich am bestehenden System. Abgesehen von der Redis Datenbank soll die Anwendung um keine neuen Technologien erweitert werden. 19 KAPITEL 3. ANALYSE UND VORGEHEN 3.5 Testdesign und Kapazitätsplanung Für die Umsetzung des Tests der Performance und Skalierbarkeit von Understandr ist eine Kapazitätsplanung und ein entsprechendes Testdesign unumgänglich. Die Anzahl an Nutzern, ihr Verhalten und zu erwartende Datenmengen sind Grundlage des Tests. (Meier 2007, S. 48) Es existieren verschiedene Methoden, um die Last auf eine Anwendung vorherzusagen. Eine grundlegende Aussage solcher Techniken ist, dass eine Vorhersage direkt mit Daten der Vergangenheit verbunden ist. (Calzarossa et al. 2002, S. 147) Da die Anwendung noch nicht online ist, muss auf Daten einer anderen, ähnlichen Plattform zurückgegriffen werden. Neben der Nutzung solcher historischen Daten ist die Ausarbeitung von Nutzungsszenarien (Use Cases) ausschlaggebend, um die Last auf eine Anwendung nah am Produktionsbetrieb zu verteilen. (Calzarossa et al. 2002, S. 150) Beispiel Stack Overflow Als Quelle der historischen Daten wird die Webseite Stack Over- flow (www.stackoverflow.com) dienen. Stack Overflow ist eine kostenfreie Plattform, auf der Nutzer Fragen zum Thema Programmierung stellen können. Andere Nutzer können diese Frage beantworten. Das reine Lesen der Beiträge ist ohne Anmeldung möglich. Das Schreiben von Fragen, Antworten und Kommentaren erfordert eine Registrierung. (stackoverflow.com 2012a) Die Webseite ist Teil des Stack Exchange Netzwerkes, welches neben Stack Overflow 841 weitere Webseiten mit dem gleichen Konzept zu verschiedensten Themengebieten betreibt. Die Fragen und Antworten können von allen Nutzern auf- und abgewertet werden. (stackexchange.com 2012) Die Inhalte werden vollständig von den Nutzern erstellt und gepflegt. Man kann schnell erkennen, dass sich die grundsätzlichen Funktionalitäten von Understandr und von Stack Overflow stark ähneln. Nutzer können Beitrage erstellen, Antworten geben und abstimmen. Daher ist die Webseite sehr gut geeignet, um als Vorlage für die Kapazitätsanalyse von Understandr zu fungieren. Darüber hinaus ermöglicht das Open Source Projekt Stack Exchange Data Explorer Zugriff auf die öffentlichen Daten-Dumps des Stack Exchange Netzwerkes. (data.stackexchange.com 2012) Dadurch können spezifische Daten von Stack Overflow mit Hilfe von SQL2 Befehlen abgerufen und zusammengefasst werden. Auf diesem Weg werden detaillierte Verteilungs- und Nutzungsmuster der Daten erstellt. Stack Overflow existiert seit September 2008 (stackoverflow.com 2009) und die freie Datenbank umfasst alle Daten bis heute. Als Grundlage für die Kapazitätsplanung von Understandr wird der Umfang der Daten und die Anzahl an Zugriffen auf Stack Overflow bis September 2009 fungieren. Eine Planung über ein Jahr hinaus ist unpraktikabel und würde den Umfang der möglichen Testbedingungen sprengen. Zudem ist Stack Overflow eine sehr erfolgreiche Webseite mit sehr schnellem Wachstum. Eine Orientierung an diesen Werten führt zu einer sehr optimistischen Prognose mit ausreichend Puffer. 1 Stand: 2 Stack 26. Juni 2012 Exchange verwendet Microsoft SQL Server, daher wird die SQL-Variante T-SQL verwendet 20 KAPITEL 3. ANALYSE UND VORGEHEN 3.5.1 Use Cases und Verteilung Für die Durchführung aussagekräftiger Lasttests ist es unabdingbar, Anfragen an die Anwendung zu stellen, welche tatsächlich von echten Nutzern auf diese Art durchgeführt werden. Dafür müssen Use Cases, also konkrete Fälle, wie eine Anwendung genutzt wird ermittelt werden. Diese Use Cases können anschließend simuliert werden. An dieser Stelle sollen nur die relevantesten Use Cases herausgearbeitet und bewertet werden, da es unpraktisch bis unmöglich ist, jedes mögliche Szenario zu simulieren. (Meier 2007, S. 140) Die Use Cases ergeben sich aus einer Analyse der bestehenden Anwendung und sind in der Abbildung 3.2 visualisiert. Abbildung 3.2: Use Cases der Anwendung Understandr Für den eigentlichen Lasttest der Anwendung wird aus diesen Use Cases wiederum nochmal eine Auswahl getroffen, um das Testszenario möglichst einfach und überschaubar zu halten. Für den Zweck dieser Arbeit ist es relevanter, dass sich die Use Cases in der Art der abzurufenden Daten aus der Datenbank unterscheiden. Dabei kann nicht jeder Anwendungsfall abgedeckt werden. Diese spezifischen Anwendungsfälle von Understandr werden selbstverständlich nicht gleich verteilt auftauchen. Der Datenbestand von Stack Overflow zeigt eine mögliche Verteilung auf. Ein Jahr nach dem Start von Stack Overflow wurden an einem Tag etwa 5000 Fragen und Antworten, 5000 Kommentare und 25.000 Stimmen abgegeben bei einer Million Page Views (siehe Anhang A). Daraus folgt, dass nur 3% der Anwendungsfälle schreibender Natur sind. Dieser Umstand 21 KAPITEL 3. ANALYSE UND VORGEHEN wird bei den Use Cases von Understandr berücksichtigt. Im Folgenden werden die Use Cases für den Lasttest erläutert und ihre relative Häufigkeit bestimmt. Statement ansehen Dieser Use Case repräsentiert die zentrale Funktionalität von Understandr. Es ist anzunehmen, dass der Großteil der Nutzer den Dienst ausschließlich für diesen Anwendungsfall verwenden wird. Über Suchmaschinen oder Verlinkungen werden die Statements unmittelbar zu finden sein. Darüber hinaus ist dieser Use Case auch aus Sicht der Performance am signifikantesten, da er die aufwendigste Aggregation von Daten beinhaltet. Statement-, Argument-, User- und Stimmdaten werden an dieser Stelle zusammengeführt (siehe Kapitel 5.2). Aus diesem Grund sollen 70% der Anfragen während des Lasttests auf Statement-Ansichten zugreifen. Dabei wird kein Statement mehrfach abgerufen, um eine maximale Verteilung zu gewährleisten. Startseite aufrufen Die Bedeutung der Startseite von dynamischen Diskussionsseiten, wie Stack Overflow, ist längst nicht so groß, wie die Bedeutung der eigentlichen Inhalte. Dennoch soll der Abruf Teil des Tests werden. 10% der Testanfragen werden auf die Startseite verwendet. Suchen Die Suchfunktionalität legt eine größere Last auf die Datenbank, da bei großen Datenmengen ein umfangreicher Index durchsucht werden muss. Es ist anzunehmen, dass diese Funktion sehr zeitintensiv ist. Darüber hinaus wird sie relativ häufig von Nutzern verwendet. 10% der Testanfragen werden eine Suche auslösen Statement erstellen Auch wenn schreibende Aktionen nur einen sehr geringen Anteil der Gesamtnutzung ausmachen, werden sie Teil des Tests sein. Auf diesem Weg wird die Datenbank auch schreibend verwendet. Die Erstellung eines neuen Statements wird mit 2% am Test beteiligt sein. Insgesamt werden die schreibenden Szenarien 10% ausmachen. Das ist deutlich mehr als im Fall von Stack Overflow, da durch die nur sehr kurzen Texteingaben bei Understandr die Schwelle zur aktiven Beteiligung niedriger sein könnte. Argument erstellen Die Erstellung eines neuen Argumentes wird 3% aller Anfragen ausmachen. Stimme für Argument abgeben Die Stimmabgabe erfordert nur einen einzigen Klick, daher wird die Beteiligung am Test 5% betragen. 3.5.2 Entwurf der Testdaten Um die Anwendung nah realitätsnah zu testen, ist es notwendig, die Datenbank mit Daten zu füllen. Dabei steht zunächst die reine Menge der Daten im Vordergrund, obwohl die Verteilung der Daten auch Beachtung finden soll. Es werden nie alle Statements gleich viele Argumente und nie alle Argumente gleich viele Stimmen haben. Darüber hinaus gibt es auf der einen Seite aktive und auf der anderen Seite passive Nutzer. Eine Vorstellung über diese Verteilung liefert ebenfalls die Stack Overflow Datenbank. Die Understandr Datenbank wird mit Testdaten befüllt, die sich dieser Verteilung annähern. Besonders relevant und kritisch für die Leistung sind natürlich Statements mit besonders vielen Argumenten, Stimmen und beteiligten Nutzern. In diesem Fall ist die Last auf die Datenbank am größten und somit als Testgegenstand am relevantesten. Daher 22 KAPITEL 3. ANALYSE UND VORGEHEN wird der Fokus beim Lasttest der Anwendung auf einen solchen Fall gelegt werden. Statements mit wenig, bis keinen Argumenten werden der Vollständigkeit halber erstellt, um eine akkurate Testumgebung zu schaffen. Alle SQL-Abfragen für die Ermittlung der folgenden Daten befinden sich in Anhang A. Statements und Nutzer Inklusive September 2009 wurden auf Stack Overflow fast 300.000 Fragen erstellt und es waren mehr als 65.000 Nutzer registriert. Die Testdaten für Understandr werden sich daher an diesen Werten orientieren. Verteilung der Argumente Zunächst ist die minimale und maximale Anzahl an Antworten eine relevante Größe. Im Fall von Stack Overflow ist das 0 und 416. Nun kann betrachtet werden wie sich diese Zahlen genau verteilen. Die durchschnittliche Anzahl an Antworten beträgt 2,17. Insgesamt wurden bisher ca. 3.300.000 Fragen erstellt. Anzahl Antworten Vorkommen Prozent 0 280.000 8 1-5 2.900.000 88 6-10 100.000 3 11-50 14.000 0,5 51-416 140 0,01 Tabelle 3.3: Verteilung der Anzahl an Antworten auf Stack Overflow In Tabelle 3.3 kann man deutlich erkennen, dass am häufigsten null bis fünf Antworten je Frage auftreten, wohingegen über 10 Antworten praktisch keine Rolle spielen. Diese Verteilung ist ein guter Anhaltspunkt, um die Testdaten für Understandr sinnvoll zu verteilen. Im Gegensatz zu Stack Overflow ist Understandr kein Forum für ein spezifisches Thema, bei dem die Beantwortung einer Frage im Idealfall Fachwissen verlangt. Es ist davon auszugehen, dass die Nutzer häufiger zu einem Statement ein Argument erstellen. Eine häufige Anzahl (80%) von 10 Argumenten scheint dafür eine sinnvolle Wahl zu sein. Des Weiteren wird die Datenbank zu 10% mit Statements ohne Argumente und zu 10% mit Statements mit 10 bis 50 Argumenten befüllt werden. Verteilung der Nutzerbeteiligung Stack Overflow hat ca. 1.225.000 angemeldete Nutzer, welche nicht alle gleich aktiv auf der Plattform sind. Die Aktivität lässt sich sehr gut an der so genannten Reputation ablesen. Dieser Wert gibt an, welchen Stellenwert die Meinung eines Nutzers hat. Der Wert steigt hauptsächlich durch das Erstellen von guten Fragen und Antworten. Jeder Nutzer startet mit einer Reputation von 1 und fällt auch nie unter diesen Wert. Demzufolge sind Nutzer mit einer Reputation von 1 absolut passiv oder sehr wenig aktiv (Reputation kann auch wieder verloren werden). (stackoverflow.com 2012b) Ca. 670.000 User haben eine Reputation von 1. Also nur die Hälfte aller Nutzer beteiligt sind aktiv an der Erstellung der Inhalte. Das gleiche Muster soll bei den Testdaten zur Anwendung kommen. Die Hälfte aller Nutzer werden als Autor von Statements, Argumenten und Stimmen verwendet. Dabei werden alle Nutzer als gleich aktiv angenommen und nicht zwischen sehr aktiven und wenig aktiven Nutzern unterschieden. 23 KAPITEL 3. ANALYSE UND VORGEHEN Anzahl Stimmen 0 1 2 3 4 5 6 -1 Anzahl Beiträge 3.970.000 2.560.000 1.300.000 680.000 380.000 230.000 150.000 120.000 Prozent 8 88 3 0,5 <0,01 <0,01 <0,01 <0,01 Tabelle 3.4: Verteilung der Stimmanzahl auf Stack Overflow Verteilung der Stimmen Auch bei Stack Overflow ist es möglich für Fragen und Antworten zu stimmen. Insgesamt existieren fast 9.990.000 Beiträge (Fragen und Antworten) mit einem durchschnittlichen Score von 1,7. Die Verteilung der Stimmen ist in Tabelle 3.4 ersichtlich. Weit über 50 Prozent haben demzufolge eine Score zwischen 0 und 2. Die gleiche Verteilung soll für die Stimmen der Argumente verwendet werden. Schlagwörter Jedes Statement wird zufällig mit null bis drei Schlagwörtern versehen. 3.5.3 Page Views und Nutzerzahlen Der Kennwert Page View entspricht dem Abruf einer einzelnen HTML-Datei von einem Server. Dabei beinhaltet eine Page View, den Abruf aller eingebetteten Dateien einer HTML-Datei, wie zum Beispiel CSS oder JavaScript Dateien. (Meier 2007, S. 94) Der Begriff ist daher von einer einzelnen HTTP-Anfrage abzugrenzen. Das Darstellen einer einzelnen HTML-Seite beinhaltet fast immer mehrere HTTP Anfragen. Als Kennzahl für die Beliebtheit eines Webauftrittes wird häufig die Angabe der Page Views verwendet. Daneben lässt sich daraus die Last auf den Webserver ablesen, denn jeder Seitenaufruf muss von ihm verarbeitet werden. Bei der Kapazitätsplanung einer Webanwendung spielt die zu erwartende Page View Zahl eine zentrale Rolle. Im Idealfall muss eine Webanwendung mit dieser Kennzahl belastet werden. (Meier 2007, S. 94) Gleichzeitig wird die durchschnittliche Antwortzeit ermittelt und mit der geforderten Antwortzeit verglichen. Diese Zahl kann natürlich nur angenommen werden und durch historische Daten gestützt werden. Stack Overflow soll auch hierfür als Vorbild dienen. Im September 2009 hatte der Dienst erstmals eine Million Page Views an einem Tag (quantcast 2012). Diese Zahl soll den ungefähren Anhaltspunkt für Understandr darstellen. Eine Million Page Views pro Tag entsprechen im Schnitt 12 Page Views pro Sekunde. Allerdings muss für einen Lasttest in jedem Fall die Spitzenlast verwendet werden. Nur wenn eine Webanwendung unter Spitzenlast eine gute Performance vorweisen kann, ist sie insgesamt als performant zu bezeichnen. (Joines et al. 2003, S. 141) Im Idealfall ergibt sich der Wert für die Spitzenlast aus Erfahrungswerten. Da diese nicht zur Verfügung stehen, wird an dieser Stelle auf eine allgemeine Quelle zurückgegriffen: die Traffic-Statistik des German Commercial Internet Exchange (DE-CIX), einem der größten Internet-Knoten der Welt. (www.de-cix.net 2012) In Abbildung 3.3 kann man deutlich ablesen, dass der Spitzenwert (Peak) mehr als 50% über dem Durchschnittswert (Average) liegt. Daher bietet es sich an, für den Lasttest von den angenommen 12 Page Views auf 20 zu erhöhen, um damit einen Wert mit genügend Spielraum zu erhalten. Im Rahmen dieses Tests wird eine Pa- 24 KAPITEL 3. ANALYSE UND VORGEHEN ge View mit einer HTTP-Anfrage gleichgesetzt. Eingebettete Dateien, wie CSS und JavaScript Dateien werden nicht mit abgefragt, um das Szenario möglichst einfach zu halten. Außerdem beinhaltet die Anwendung auch eingebettete Dateien von Drittanbietern, deren Antwortzeiten nicht vorhersehbar ist. Daher entsprechen die geforderten 20 Page Views pro Sekunde auch einem Durchsatz von 20 Anfragen pro Sekunde. Eine weitere wichtige Kennzahl ist, wie vielen verschiedenen Nutzern diese Zugriffe generieren. Stack Overflow hatte im September 2009 ungefähr 300.000 eindeutige Nutzer. Das entspricht inklusive den 50% Peak-Aufschlag fast 6 Nutzer pro Sekunde. (quantcast 2012). Abbildung 3.3: Traffic für 2 Tage des DE-CIX Internetknoten (de-cix.net, 03.07.2012) 3.5.4 Testdesign im Überblick Das Testdesign besteht zunächst aus einer Testdatenbank, die sowohl im Umfang als auch in der Verteilung einen realistischen Bezug aufweist. Daneben wurden Testanfragen entworfen, welche der Nutzung der Anwendung in einem späteren produktiven Einsatz möglichst nahe kommen. In Abbildung 3.4 ist die Verteilung der Daten in der Testdatenbank noch einmal illustriert und in Abbildung 3.5 sind die Testanfragen dargestellt. 25 KAPITEL 3. ANALYSE UND VORGEHEN Abbildung 3.4: Testdaten Übersicht Abbildung 3.5: Testanfragen Übersicht 26 KAPITEL 3. ANALYSE UND VORGEHEN 3.6 3.6.1 Testumgebung Software für die Messung Für die Messung der Performance und Skalierbarkeit wird die Software JMeter eingesetzt. Die Software gehört zum Apache Projekt und ist komplett in Java geschrieben. JMeter wurde entwickelt um Client-Server-Software, wie Webanwendungen, Lasttests zu unterziehen. Es unterstützt verschiedenste Ressourcen, wie statische Webseiten, Java Applets, CGI-Skripte, Datenbanken, FTP-Server und viele weitere. JMeter kann Adressen von Ressourcen automatisiert aufrufen und darüber hinaus eine große Anzahl von gleichzeitigen Nutzern simulieren. Die Performance der Anwendung kann dann unter dieser Last analysiert werden. (Apache 2012) Abbildung 3.6: Screenshot JMeter mit Testplan JMeter ist grundsätzlich in so genannten Thread-Gruppen organisiert. Jede Thread-Gruppe repräsentiert ein Verbund von virtuellen Nutzern, die einen bestimmtem Testfall ausführen. (Apache 2012) Ein Thread entspricht immer einem Nutzer. Einer solchen Gruppe können verschiedene Steuerelemente hinzugefügt werden. Dazu zählen unter anderem logische Controller, wie Schleifen und Bedingungen, aber auch Zeitgeber, um die Abarbeitung der Nutzeraktionen zu takten. Wichtigste Elemente sind aber die Sampler, mit deren Hilfe sich Anfragen an einen Server senden lassen. Verschiedenste gängige Protokolle stehen zu Auswahl: HTTP, FTP, SMTP, SOAP und viele mehr. Zusätzlich lassen sich alle Aspekte einer solchen Anfrage simulieren. Im Falle von HTTP Anfragen zum Beispiel die Übergabe von POST- oder GET-Variablen. Um die Ergebnisse der Lasttest zu überwachen, existieren verschiedene Listener, die unterschiedliche Visualisierungsarten zur Verfügung stellen oder die Daten abspeichern. 27 KAPITEL 3. ANALYSE UND VORGEHEN 3.6.2 Datenbank befüllen Um die Datenbank mit den geforderten Testdaten zu befüllen, existieren verschiedene Lösungsansätze. Es ist möglich die Webanwendung an sich zu verwenden. Ein synthetischer Nutzer kann automatisch alle notwendigen Klicks durchführen und entsprechend häufig wiederholen, um die notwendigen Datenmengen in der Datenbank zu erhalten. Daneben ist es möglich, die Datenbank unmittelbar mit Daten zu füllen. Letztere Methode wird verwendet, um die Understandr Datenbank für den Test vorzubereiten, da die geforderten Datenmengen auf diesem Weg wesentlich schneller angelegt werden können. Hierzu wurde eine CakePHP Konsolenanwendung entwickelt, welche die geforderten Daten in die entsprechenden Tabellen schreibt (siehe Kapitel 6.6). 3.6.3 Testaufbau, Qualität und Messgrößen Für den Lasttest werden die beiden Testserver (Web- und Datenbankserver) verwendet. Die Anwendung wurde leicht modifiziert. Das Einloggen wurde vereinfacht und ist über das Aufrufen einer bestimmten URL möglich, da die Nutzung des verwendeten Facebook-Logins aus JMeter heraus nicht möglich ist. Darüber hinaus werden in der Sidebar nur noch die neuesten Statements angezeigt, da die Implementierung der Statistikfunktionen noch nicht vollständig abgeschlossen ist. Die Anwendung wird vor einem Test in einen Ausgangszustand versetzt, welcher folgende Punkte erfüllt: • Die Datenbank ist nur mit den Daten befüllt, die in Kapitel 3.5.4 aufgezeigt sind • Die Server werden einmal neu gestartet • Netzwerkverbindung und Netzwerkgeschwindigkeit werden durch das Hoch- und Herunterladen einer Referenzdatei überprüft Performance und Skalierbarkeit werden in unterschiedlichen Testszenarien gemessen, da ansonsten die Anzahl an variablen Größen zu groß wird. An dieser Stelle muss erwähnt werden, dass es sich bei allen Tests nur um synthetische Szenarien handelt, deren Aussagekraft entsprechend gewertet werden muss. Hauptsächliches Ziel dieser Arbeit ist es, den relativen Leistungsunterschied zwischen zwei verschiedenen Implementierungen zu ermitteln. Auch wenn die gemessenen Werte eine gewisse Aussagekraft bezüglich der absoluten Leistung der Anwendung haben, sollte für eine diesbezügliche Aussage ein entsprechender Test entwickelt werden. Darüber hinaus unterliegt ein solcher Test gewissen Qualitätsschwankungen. Daher muss jeder Testlauf hinreichend lange durchgeführt werden. Faktoren, die das Testergebnis beeinflussen können, sind in diesem Fall hauptsächlich Schwankungen in der Netzwerk- und Internetleistung. Als Messgrößen werden die beiden Kennzahlen Durchsatz und Antwortzeit dienen. Auf eine Messung der Verteilung der Ressourcenauslastung wird verzichtet, da es sich nicht um einen Stresstest handelt und die Auslastung jeder Ressource nicht das jeweilige Maximum überschreiten soll. Eine entsprechende Überwachung der Ressourcen während der Tests mit passenden Systemtools ist angebracht. 28 KAPITEL 3. ANALYSE UND VORGEHEN 3.6.4 Test der Performance Für den Performance-Test wird jeder in Kapitel 3.5.1 aufgeführte Use Case in JMeter abgebildet. Dabei entspricht jeder Anwendungsfall einer Thread-Gruppe. Jede Thread-Gruppe wird mit den notwendigen HTTP-Requests, verschiedenen Listenern und einem Zeitgeber für einen konstanten Durchsatz versehen. Letzteres dient der Festlegung der Quantität der verschiedenen Use Cases. Zum Beispiel soll in 70% der Zugriffe ein Statement angesehen werden. Der Durchsatz wird pro Minute angegeben. Bei geforderten 20 Page Views pro Sekunde entspricht das 840 Zugriffen pro Minute. Dieser Wert wird angegeben und JMeter versucht den gewünschten Durchsatz zu erreichen. Zusätzlich lässt sich festlegen, wie viele Nutzer den jeweiligen Use Case gleichzeitig ausführen. Um die Testszenarien möglichst übersichtlich zu halten, wird jede Thread-Gruppe nur von einem Nutzer gleichzeitig ausgeführt. Das bedeutet, dass JMeter versucht die geforderte Anzahl an Anfragen innerhalb einer Gruppe nacheinander auszuführen. Insgesamt handelt es sich also um 6 Nutzer, da es 6 Use Cases sind. Sollte der geforderte Durchsatz erreicht werden und jede Anfrage im Schnitt unter einer Sekunde in Anspruch nehmen, entspräche das den geforderten 6 eindeutigen Nutzern pro Sekunde. Als Ergebnis erhält man dann die erzielte mittlere Antwortzeit für die jeweiligen Use Cases und für den gesamten Test als aggregierten Wert. Auch wenn man den gewünschten Durchsatz bereits vorgegeben hat, erhält man zusätzlich den tatsächlich erreichten Durchsatz. 3.6.5 Test der Skalierbarkeit Für den Test der Skalierbarkeit wird auf die Unterteilung in verschiedene Use Cases verzichtet. Konzept dieses Tests ist es, dass mehrere Nutzer gleichzeitig versuchen auf die Anwendung zuzugreifen. Die Anzahl der Nutzer wird dabei stetig erhöht, um eine erhöhte Last auf den Server zu simulieren. Durchsatz und Antwortzeit werden parallel gemessen. Je nachdem in welchem Maß der Durchsatz sinkt bzw. die Antwortzeit steigt, lässt sich die Skalierbarkeit einordnen. Ein einzelner Use Case soll an dieser Stelle genügen, hauptsächlich um den JMeter-Client nicht zu überlasten. Für jeden simulierten Thread wird ein neuer Java-Thread bereitgestellt mit entsprechendem Ressourcenverbrauch. Abgerufen werden die Statements mit den meisten Argumenten und Stimmen. 3.6.6 Profiling Als Profiling bezeichnet man das Erfassen von Informationen über die Abarbeitung von Quelltext zur Laufzeit. Ein Profiler ermittelt an welchen Stellen der Software die meiste Zeit während der Ausführung verbraucht wurde. Häufig wird angegeben, wie oft Funktionen ausgeführt wurden und wie lange eine Ausführung dauerte. Auf diesem Weg lässt sich sehr schnell identifizieren, welche Teile der Anwendung Flaschenhälse sind und optimiert werden sollten. (Henderson 2006, S. 211-212) Neben den Lasttests wird bei der Anwendung auch ein Profiling durchgeführt. Zum einen um einwandfrei auszuschließen, dass es eventuell einen noch größeren Flaschenhals als die Datenbank gibt. Zum anderen, um zusätzliche, langsame Funktionen zu identifizieren, welche sich gegebenenfalls durch die Erweiterung um eine neue Datenhaltungsebene ebenfalls beschleunigen lassen. 29 KAPITEL 3. ANALYSE UND VORGEHEN Für die Durchführung des Profiling wird das von Facebook entwickelte Programm XHProf zum Einsatz kommen. XHProf ist ein Profiler speziell für PHP. Installiert als PHP Extension liefert er die Ergebnisse über ein webbasiertes Interface. 3.7 Integrations- und Unittests Für die unproblematische Bewertung der Funktionalitäten und der Transparenz der Weiterentwicklung, soll der bestehende Zustand mit Hilfe von automatisierten Tests festgehalten werden. Dafür werden zwei verschieden Umgebungen und Arten von Tests verwendet. Integrationstests mit Selenium Ein Integrationstest dient dazu, das Zusammenspiel von verschiedenen Komponenten in einem Softwaresystem zu testen. In diesem Fall wird es sich um die gesamte Anwendung, also die HTML-Seite als Frontend handeln. Dafür wird die Software Selenium (www.seleniumhq.org) zum Einsatz kommen. Mit Selenium lässt sich die Nutzung einer Webanwendung automatisieren und die Ergebnisse mit vorher festgelegten Annahmen vergleichen. Die Use Cases von Understandr sollen auf diesem Weg festgehalten werden. Die Erweiterung wird dann den gleichen Tests unterzogen und damit eine transparente Funktionalität der Frontend-Seite gewährleisten. Unittests mit PHPUnit Ein Unittest überprüft die korrekte Funktionsweise einer Einheit eines Softwaresystems. In den meisten Fällen handelt es sich dabei um eine einzelne Klasse. Dabei wird diese als isoliert betrachtet und im Idealfall werden Verbindungen zu anderen Einheiten bzw. Klassen simuliert, um tatsächlich nur die Richtigkeit der einen Klasse zu bewerten. Ein solcher Test ruft dann Methoden der Klasse auf und überprüft das Ergebnis wiederum mit vorher festgelegten Annahmen. Die Arbeitsweise der wesentlichen Schnittstellen der bestehenden Datenhaltungsschicht sollen auf diesem Weg festgehalten werden. Damit ist es später möglich, die korrekte Arbeitsweise und die geforderte Transparenz sicherzustellen. 30 Kapitel 4 Analyse und Test des Ist-Zustandes 4.1 Profiling Für das Profiling wurde ein Statement mit vielen Argumenten (50) vom Server abgerufen, um zu sehen welche Funktionen den meisten Anteil der Ausführungszeit beanspruchen. Insgesamt hat die Ausführung ca. 460 Millisekunden in Anspruch genommen. Tabelle 7.1 zeigt die fünf Funktionen mit dem größten Anteil. Funktionsname Anzahl Aufrufe Zeit in ms PDO::query PDOStatement::execute PlistaModel:: get@1 PlistaModel::getDataField Understandr\Statement::getUserPosition 231 70 8.699 10.432 60 81 37 36 32 25 Anteil an Ausführungszeit in % 17,5 7,9 7,7 7,0 5,5 Tabelle 4.1: Ergebnis des Profiling des Ist-Zustandes An oberster Stelle befindet sich zwei Funktionsaufrufe der PDO Klasse. Diese Klasse stellt die Datenbankschnittstelle von PHP dar. Der Zugriff auf die Datenbank nimmt an dieser Stelle schon über 25% der Gesamtzeit ein. Die nächsten Funktionen gehören zur PlistaModel Klasse und geben Zugriff auf einzelne Datenfelder (Informationen zum PlistaModel in Kapitel 5.1). Die letzte Funktion ist Teil der Berechnung der öffentlichen Meinung, welche indirekt ebenfalls in Zusammenhang mit Datenbankoperationen steht (siehe Kapitel 5.2). Zusammengefasst bedeutet das, dass die aufwändigsten Funktionsaufrufe mit der Datenhaltung zusammenhängen. 31 KAPITEL 4. ANALYSE UND TEST DES IST-ZUSTANDES Use Case Anzahl Anfragen 1150 Gewünschter Durchsatz in Anfragen/s 120 Tatsächlicher Durchsatz in Anfragen/s 115 Durch. Antwortzeit in ms 258 Startseite aufrufen Statement aufrufen Suche Statement anlegen Argument angelegen Stimme abgeben Insgesamt 1079 840 108 550 860 240 120 24 86 24 644 435 360 36 36 283 597 60 60 310 4286 1200 429 428 Tabelle 4.2: Ergebnisse des Performance-Tests des Ist-Zustandes 4.2 Performance In Tabelle 4.2 sind die Ergebnisse des Performance-Tests zu sehen. Der Test wurde wie in Kapitel 3.6.4 beschrieben durchgeführt und lief 10 Minuten. Auffällig ist sofort, dass der gewollte Durchsatz von 1200 Anfragen pro Sekunde nicht erreicht wurde. Das ist fast ausschließlich auf den sehr niedrigen Durchsatz des Use Cases Statement aufrufen zurückzuführen. Dieser Fall konnte nur ein Achtel des gewünschten Durchsatzes erreichen. 4.3 Skalierbarkeit Für den Skalierungs-Test wurde die Anzahl an gleichzeitigen Nutzern langsam von 1 auf 40 erhöht. Die Zahl 40 ergab sich aus Testläufen um die Obergrenze an gleichzeitigen Nutzern bezüglich Ressourcenauslastung zu ermitteln. Abbildung 4.1 zeigt den Verlauf der Antwortzeit bei steigender Nutzerzahl. Die Kurve verläuft linear, die Antwortzeit steigt also linear zur Anzahl gleichzeitiger Nutzer. Die mittlere Antwortzeit liegt bei ungefähr 3300 ms. Bei mehr als 6 gleichzeitigen Nutzern steigt die Antwortzeit über eine Sekunde. Bei einer Wartezeit von mehr als einer Sekunde kann man die Anwendung als schwer benutzbar bezeichnen. Abbildung 4.2 zeigt den Durchsatz in Anfragen pro Sekunde bei steigender Nutzerzahl. Der mittlere Durchsatz liegt dabei bei etwas über 6 Anfragen. Bei 7 Nutzern bricht der Durchsatz kurz ein, dabei handelt es sich wahrscheinlich um eine zufällige Abweichung. Davon abgesehen, steigt der Durchsatz bis 9 gleichzeitige Nutzer. Danach hält sich der Durchsatz auf einem konstanten Wert. Insgesamt skaliert die Anwendung fast überhaupt nicht, da die Leistung bei steigender Nutzerzahl sich verschlechtert und nur bis zu einer kleinen Nutzerzahl im verwendbaren Bereich bleibt. 32 KAPITEL 4. ANALYSE UND TEST DES IST-ZUSTANDES Abbildung 4.1: Anwortzeit gegenüber Anzahl gleichzeitiger Nutzer im Ist-Zustand Abbildung 4.2: Durchsatz gegenüber Anzahl gleichzeitiger Nutzer im Ist-Zustand 33 KAPITEL 4. ANALYSE UND TEST DES IST-ZUSTANDES 4.4 Testfälle In Anhang C.1 sind die erstellten PHPUnit-Testmethoden dargestellt. Es wurden für die wesentliche Datenhaltungsoperationen Tests erstellt, welche die Funktionsweise festhalten. Dabei wird vor jedem Test eine leere Testdatenbank mit Testdaten befüllt. Die Testmethoden führen anschließend Testoperationen durch. Zum Beispiel das Anlegen eines neuen Datensatzes und die anschließende Überprüfung, ob dieser in der Datenbank auch vorhanden ist. Die Auswahl der Methoden orientiert sich dabei an den Funktionen des PlistaModel, welches in Kapitel 5.1 näher erläutert wird. Die Testfälle waren alle erfolgreich, was zu erwarten war, da sie an die vorhandenen Gegebenheiten angepasst wurden. Anhang C.2 zeigt eine Übersicht der erstellten Testfälle für das Frontend. Die gängigsten Anwendungsfälle wurden festgehalten. 4.5 Beurteilung Insgesamt sind die Werte für Performance und Skalierbarkeit weit unter den Anforderungen. Die durchschnittliche Antwortzeit ist weit entfernt vom langfristigen Ziel von 50 ms und der geplante Durchsatz konnte ebenfalls nicht erreicht werden. Darüber hinaus skaliert die Anwendung nur unwesentlich. Allerdings hat das Profiling gezeigt, dass die Datenhaltung einen wesentlichen Teil der Ausführungszeit beansprucht. Insofern ist eine Überarbeitung der Datenhaltung zunächst der richtige Weg um die Werte zu verbessern. Inwieweit weitere Faktoren eine Rolle spielen, wird sich nach den Tests der erweiterten Version zeigen. 34 Kapitel 5 Entwurf Im folgenden Abschnitt wird der theoretische Entwurf der geforderten neuen Datenhaltungsebene auf Basis einer Redis Datenbank diskutiert. Um dieses Ziel zu erreichen, werden die bestehende Architektur und die zu verarbeitenden Daten analysiert, um einen optimalen Punkt und ein geeignetes Datenmodell für die Erweiterung zu finden. Abbildung 5.1 zeigt noch einmal den grundsätzlichen Aufbau einer CakePHP MVC Anwendung mit den drei wesentlichen Bestandteilen und dem CakePHP Dispatcher, der für die Wahl des passenden Controller zuständig ist. (CakePHP 2012) Der folgende Entwurf konzentriert sich dabei hauptsächlich auf die ModelSchicht, welche für die Datenhaltung verantwortlich ist. Abbildung 5.1: Grundsätzlicher Aufbau einer MVC-Anwendung (CakePHP 2012) 5.1 Das PlistaModel Die plista GmbH verwendet für die eigene Softwareentwicklung eine selbst entwickelte Klassenbibliothek für Objekt-relationales Mapping. Die zentrale Klasse dieser Bibliothek heißt PlistaModel, daher wird dieser Name im Folgenden stellvertretend für die gesamte Klassenbibliothek verwendet. Das PlistaModel wurde hauptsächlich 2009 entwickelt, da zu dieser Zeit gängige Lösungen von Drittanbietern die gewünschten Anforderungen nicht erfüllen konnten. Das PlistaModel ist spezialisiert für die Nutzung mit MySQL. Obwohl CakePHP über ein integriertes Objekt- 35 KAPITEL 5. ENTWURF relationales Mapping verfügt, wird auch für die Entwicklung von Understandr die firmeninterne Komponente verwendet und ersetzt damit vollständig sämtliche Model-Funktionalitäten von CakePHP. Das bedeutetet, dass alle Datenbankoperationen über das Interface des PlistaModel durchgeführt werden. Die Erweiterung der Anwendung soll weitgehend transparent erfolgen. Daher bietet es sich zunächst an, die Datenhaltungsschicht genauer zu betrachten und zu bewerten, inwieweit die gewünschten neuen Funktionalitäten ausschließlich an dieser Stelle eingeführt werden können. In jedem Fall wird für eine Änderung der Datenhaltung diese Schicht der Anwendung Grundlage und wichtigster Ausgangspunkt sein. Abbildung 5.2 gibt in Form eines Klassendiagramms einen Überblick über die interne Struktur des PlistaModel. Dabei zeigt sie nur die wichtigsten und für diesen Entwurf relevanten Methoden und Eigenschaften. Im Folgenden wird die Funktions- und Einsatzweise des PlistaModel genauer erläutert. Abbildung 5.2: Klassendiagramm der Plista Klassenbibliothek für Objekt-relationales Mapping mit den wichtigsten Klassen und Methoden Vererbung Für den Zugriff auf eine Datenbank-Tabelle, wird eine neue Klasse erstellt, welche von der Klasse PlistaModel ableitet. Über geerbte Eigenschaften lassen sich Tabellenname, Beziehungen zu anderen Models und Validierungen für die Daten konfigurieren. Anschließend ist die Klasse sofort einsatzbereit und ermöglicht eine direkte Interaktion mit der entsprechenden Tabelle in der MySQL Datenbank. Statische Finder Ein PlistaModel verfügt über verschiedene statische Methoden mit deren Hilfe sich Datensätze aus der Tabelle abrufen lassen. Jede Methode dient einem speziellen 36 KAPITEL 5. ENTWURF Einsatzzweck. Die find Methode ist dabei die vielfältigste Ausführung. Mit ihr lassen sich komplexe Abfragen mit Konditionen und Sortierungen durchführen. Dabei sind die Schlüsselwörter an SQL angelehnt. PlistaModel :: find ( ’ all ’ , array ( ’ order ’ = > ’ createdAt DESC ’ , ’ conditions ’ = > ’ Id =2 ’ , )); Mit den Methoden findById und findBy lassen sich differenziertere Abfragen durchführen. Die findById Methode fragt einen einzigen Datensatz anhand des Primärschlüssels ab mit der findBy Methode lässt sich gezielt nach Konditionen abfragen. Alle Finder geben entweder eine Sammlung von instanziierten Models in Form einer PlistaCollection oder bei einem einzigen Datensatz eine Instanz eines PlistaModel zurück. Die statischen Methoden dienen nur dem Zugriff von außen, da intern alle Methoden auf nicht statische Varianten der Finder abgebildet werden. Die als Parameter übergebenen Konditionen werden von der PlistaConditions Klasse geparst und in entsprechende MySQL Syntax umgewandelt. Datensätze zählen Die statische Count Methode lässt sich mit ähnlichen Parametern wie die Find-Methoden aufrufen. Allerdings liefert sie als Ergebnis die Anzahl der passenden Datensätze als Integer. Auch sie wird intern auf eine nicht statische Variante abgebildet. Daten auslesen Für den Zugriff auf die einzelnen Datenfelder eines PlistaModel gibt es zwei Varianten. Zum einen kann durch die Implementierung des ArrayAccess Interfaces über die Array-Schreibweise (array[index]) zugegriffen werden. Alle Felder stehen aber auch als Eigenschaften des Objektes zur Verfügung. Daten speichern Manipulierte oder neu erstellte Daten eines Models existieren zunächst nur innerhalb des Objektes. Durch den Aufruf der save Methode werden alle Daten in die Tabelle geschrieben. Ein Aufruf der update Methode ändert nur die Daten des Objektes und führt kein automatisches Speichern durch. Assoziationen Die Beziehungen von Models zueinander, respektive die Relationen der Tabellen, werden durch die Klasse PlistaAssociation abgebildet. Wird auf eine Eigenschaft eines Models zugegriffen, welche als Beziehung konfiguriert wurde, erhält man als Rückgabewert eine PlistaAssociation. Diese Klasse verfügt ebenfalls über Find, Count und Save Methoden, mit deren Hilfe über Relationen definierte Daten gelesen und manipuliert werden können. Das folgende Beispiel zeigt, wie zu einem Statement alle Argumente abgerufen werden. In MySQL entspräche das einem Fremdschlüssel in der Argument-Tabelle, welcher auf die Statement-Tabelle zeigt. $arguments = $statement - > arguments - > find ( ’ all ’ ); Alle Methoden der PlistaModel Klassen wandeln die Anfragen in MySQL Befehle um, welche dann mit Hilfe der PlistaQuery Klasse an die Datenbank weitergeleitet werden. Die gesamte Funktionalität baut auf MySQL auf und die entsprechenden SQL-Befehle sind unmittelbar in den Klassen verankert. Der Datenbankzugriff ist dementsprechend nicht abstrahiert und Erweiterungen erfordern einen direkten Eingriff in die interne Struktur. 37 KAPITEL 5. ENTWURF 5.2 Datenhaltung und Berechnungen Neben den technischen Gegebenheiten der bisherigen Datenhaltung, spielen die Struktur der Daten und die Art und Weise ihrer Aufbereitung eine Rolle beim Entwurf einer neuen Datenschicht. Abbildung 5.3 zeigt die zentralen Tabellen der Anwendung und ihre Beziehungen zueinander. Nachfolgend werden die sechs Use Cases aus Kapitel 3.5.1 aus Sicht der Datenhaltung genauer betrachtet. Die schreibenden Anwendungsfälle, wie Statement und Argument erstellen, oder eine Stimme abgeben, erzeugen zunächst nur jeweils einen neuen Datensatz in den entsprechenden Tabellen. Bei der Erstellung eines Statements werden optionale Schlagwörter abgespeichert und ihre Beziehung zu dem Statement in einer Relationstabelle abgelegt. Insgesamt sind die Schreiboperationen relativ einfach und wenig umfangreich. Im Gegensatz dazu müssen bei den Leseoperationen komplexere Abfragen durchgeführt werden. Die hauptsächliche Ursache dafür liegt bei der Berechnung der öffentlichen und persönlichen Meinung. Für die persönliche Meinung zu einem Statement werden zunächst alle Argumente des Statements ermittelt für die ein Nutzer gestimmt hat. In Abhängigkeit zu der Gesamtzahl der Stimmen dieser Argumente werden die Werte berechnet. Dieser Algorithmus erfordert Zugriffe und Verknüpfungen auf die Tabellen Statements, Arguments, ArgmtVotes und Users. Für die Ermittlung der öffentlichen Meinung eines Statements müssen die Meinungen aller beteiligten Nutzer des Statements einbezogen werden, denn die öffentliche Meinung ist der Mittelwert aller Nutzermeinungen. Die Verknüpfung der Tabellen wird also entsprechend der Anzahl an beteiligten Nutzern mehrfach durchgeführt. Entscheidend ist jetzt, dass diese durchaus aufwendige Berechnung in jedem Fall erfolgt, in dem Statements aufgelistet werden. Die öffentliche Meinung wird als zentrale Information der Anwendung an allen Stellen mitgeführt. Dazu zählt der Aufruf der Startseite, welche die aktuellste Beiträge zeigt, das Darstellen der Sidebar mit einer Auswahl an Statements und auch Suchergebnisse. Darüber hinaus wird die Berechnung bei der Anzeige des Statements durchgeführt, bei der zusätzlich die zugehörigen Argumente abgefragt werden. Außerdem wird auch bei jeglichen Aktionen, die eine Änderung der Meinung verursachen, neu berechnet. Dazu zählen ein neues Argument anlegen und eine Stimme abgeben. Abbildung 5.3: Entity-Relationship-Modell des bestehenden MySQL-Implementierung 38 KAPITEL 5. ENTWURF 5.3 Verwendung von Redis Die vorher beschriebenen Gegebenheiten sollen nun Beachtung beim Entwurf der Erweiterung auf Basis einer Redis Datenbank finden. Dazu muss geklärt werden, welche Daten in welcher Form gespeichert werden und an welcher Stelle dies innerhalb der bestehenden Architektur umgesetzt wird. Art des Einsatzes Die Leistungsfähigkeit einer Webanwendung kann durch den Einsatz eines Caches auf Serverseite erhöht werden. Dieses Konzept ist weit verbreitet und existiert in vielfältigen Ausprägungen. Die Entwickler von Stack Overflow berichten, dass sie grundsätzlich versuchen möglichst alle Inhalte zu cachen. (meta.stackoverflow.com 2010) Entsprechende Literatur zu der Thematik beschäftigt sich dabei häufig mit dem so genannten Proxy-Caching. In Web Caching and its Applications (Nagaraj 2004, S. 3-16) beschreibt der Autor verschiedenste Unterarten dieser Caching-Methode. Grundsätzlich werden dabei vor den eigentlichen Webserver ein oder mehrere Proxy-Server installiert, welche die HTTP-Anfragen aufzeichnen und ihre Ergebnisse zwischenspeichern. (Nagaraj 2004, S. 3-12) Das Ziel dieser Ausarbeitung ist, die bestehende Datenhaltung um eine sehr schnelle Key-Value Datenbank zu erweitern. Es kann also bei diesem Vorgehen von einer Cache-Ebene gesprochen werden, die wesentlich näher an der Anwendung selbst angesiedelt ist und gezielt Daten für den häufigen Zugriff zwischenspeichert. Verschiedene Methoden, dies umzusetzen, sind denkbar. Eine Möglichkeit wäre das Zwischenspeichern von ganzen HTML-Seiten. Zum Beispiel könnte beim Aufruf der aufwendigen Statement-Ansicht, die vollständige HTML-Ausgabe in Redis gespeichert werden. Bei einer wiederholten Anfrage, wäre eine Neuerstellung nicht notwendig. Erst mit einer Schreiboperation jeglicher Art auf die betreffenden Daten müsste der Cache erneuert werden. Mit der Startseite oder anderen Ansichten könnte analog verfahren werden. Dieses Konzept birgt einige Nachteile. Die gespeicherten Daten wären speziell an eine Ansicht und einen Zweck angepasst und könnten nicht an anderer Stelle verwendet werden. Dabei ist es durchaus gewollt die Statement-Metadaten überall schnell zur Verfügung zu stellen, zum Beispiel in Statistiken. Außerdem wäre eine transparente Implementierung schwieriger, da sowohl die ViewSchicht, als auch die Model-Schicht involviert sind. Eine weitere Möglichkeit ist, nur bestimmte Daten gezielt zwischenzuspeichern, die ansonsten aufwendig abgerufen werden müssten. Zum Beispiel, welche die neusten Beiträge sind oder welche User für ein Statement abgestimmt haben. Zusätzlich könnten bereits berechnete Meinungen zwischengespeichert werden und nur bei Datenänderungen neu berechnet werden. Besonders das Speichern der Meinungen würde viel Arbeit vom Server nehmen. Nachteil dieses Ansatzes ist, dass der MySQL-Server nur teilweise entlastet wird und die verschiedenen Speicheroperationen individuell den betreffenden Models hinzugefügt werden müssten. Bei einem Wachstum der Anwendung und Einführung neuer Funktionen müssten eventuell viele Änderungen und Ergänzungen durchgeführt werden. Eine dritte und hier favorisierte Möglichkeit kombiniert Teile der vorhergehenden Ideen. Die Redis Cache-Ebene als generische, zusätzliche Datenschicht, die große Teile oder alle Daten automatisch in Redis mitspeichert. Bei einem lesenden Zugriff wird diese Schicht bevorzugt und MySQL dient nur noch als Fallback-Ebene. Damit hätte man die Daten der Ansichten in der Cache-Ebene, aber auch atomare Informationen wären verfügbar. Zusätzlich können gezielte, 39 KAPITEL 5. ENTWURF häufig verwendete Informationen dem Model hinzugefügt werden und werden automatisch in Redis abgelegt. Transparente Integration Die bestehende MVC-Architektur und die Anforderungen diktie- ren prinzipiell bereits den genauen Ort der Implementierung. Die Implementierung der Datenhaltung gehört in die Model-Schicht und jeder Versuch Teile davon in einer der anderen Schichten zu platzieren, würde das Prinzip verletzen. Darüber hinaus sollen alle Schnittstellen erhalten bleiben und die Implementierung möglichst transparent erfolgen. Da der Controller sämtliche Datenoperationen über die Schnittstellen des PlistaModel ausführt, müssen diese bestehen bleiben. Im Falle einer parallelen Implementierung der neuen Datenhaltung in einem zweiten Model, müssten in allen Controllern Änderungen durchgeführt werden. Das wäre in keinem Fall zielführend. Auf welche Art und Weise das Model modifizert wird, erläutert das nächste Kapitel. 5.4 Generische Cache-Ebene Das PlistaModel ist unmittelbar mit MySQl verzahnt. Interne Methoden generieren teilweise SQL-Syntax als Rückgabewerte und auch die Finder verarbeiten SQL Syntax. Es gibt keine einheitlichen Schnittstellen zum Speichern und Abrufen von Daten. Eine Erweiterung unterhalb des Models ist daher nicht möglich. Die eigentliche Klasse soll natürlich auch nicht modifiziert werden. Funktionalitäten sollten immer gekapselt sein. Es bietet sich daher an, eine neue Klasse von der Klasse PlistaModel abzuleiten und die Schnittstellen zu überschreiben, um auf diesem Weg neue Funktionalitäten einzubinden. Eine überschriebene Methode kann immer die entsprechende Methode der Elternklasse aufrufen. Dadurch ist der Fallback-Aspekt sichergestellt. DataSource Die enge Beziehung des PlistaModel zu MySQL macht die Klasse unflexibel und im Idealfall versucht man ein Model unabhängig von der verwendeten Datenbank zu gestalten. Beispielsweise bietet das Objekt-relationale Mapping von CakePHP die Möglichkeit, verschiedene Datenbanken einzusetzen. Dazu wird eine Abstraktionsebene eingesetzt, die ausschließlich für den Datenzugriff zuständig ist. Die gleiche Vorgehensweise wird bei der Cache-Ebene zum Einsatz kommen. Die neuen Datenhaltungsfunktionen werden daher nicht unmittelbar in die abgeleitete Klasse implementiert, sondern in eine, nur für den Datenzugriff auf Redis verantwortliche Klasse, ausgelagert. Andernfalls würde sich das Model noch mehr spezialisieren und unflexibel gegenüber späteren Anpassungen werden. Diese sogenannte DataSource wird ein CRUD-Interface1 implementieren und von der neuen Klasse über dieses Interface verwendet werden. Damit ist die neue Datenhaltungsebene unabhängig vom eigentliche Mechanismus der Datenhaltung und kann jederzeit ausgetauscht werden. Eine DataSource implementiert dann den konkreten Datenzugriff auf eine bestimmte Datenbank, was in diesem Fall Redis ist. Das UML-Diagramm der DataSource-Klasse in Abbildung 5.4 zeigt diese Pflichtmethoden einer DataSource. Darüber hinaus ist eine DataSource unabhängig von der Art des verwendeten Models. Sie darf Daten nur in Form von reinen Arrays annehmen und gibt ebenfalls nur reine Arrays zurück. Dadurch ist eine hohe Wiederverwendbarkeit gewährleistet. 1 CRUD beschreibt grundlegende Datenbankoperationen: Create, Read, Update und Delete 40 KAPITEL 5. ENTWURF PHPRedis und PlistaRedis Der konkrete Datenbankzugriff der Redis-DataSource wird mit 1 der PHP-Erweiterung PHPRedis erfolgen. Diese Erweiterung bietet sämtliche Redis Kommandos über eine PHP Klasse an und gibt die Ergebnisse als Werte oder Array zurück. Die Software ist im Umfeld des Unternehmens bereits im Einsatz und wird unmittelbar in der Klasse PlistaRedis verwendet. Diese Klasse erweitert die Funktionalität von PHPRedis unter anderem um die Möglichkeit des Consistent Hashing. An der Klasse können mehrere Server angemeldet werden und die Klasse übernimmt die komplette Zuteilung von Schlüsseln auf den Hashing-Ring. Durch den Einsatz der PlistaRedis Klasse ist die sofortige Nutzung von Consistent Hashing somit sichergestellt. Die neue Model-Klasse Die neue, vom PlistaModel abgeleitete Klasse, dient nur noch als Adapter2 zwischen den ursprünglichen Methoden der Elternklasse und den neuen CRUD-Methoden der Redis-DataSource. Ihre Aufgabe ist, übergebene Parameter zu interpretieren und in das von der DataSource unterstützte Format zu konvertieren. Dann wird die entsprechende CRUDMethode aufgerufen. Eine DataSource kann eine Exception werfen, wenn sie die Anfrage nicht bearbeiten kann oder bestimmte Optionen nicht unterstützt werden. Die Modelklasse kann diese fangen und dann die Anfrage an die Elternklasse weiterleiten. Zusätzlich definiert die Klasse einige zusätzliche Callback-Methoden, die dem Nutzer die Möglichkeit geben, Datenhaltungsoperationen gezielt zu manipulieren. Beispielsweise eine Methode, die vor dem Speichern in die DataSource aufgerufen wird. Siehe dazu Klassendiagramm der Modelklasse in Abbildung 5.4. Assoziationen In Abbildung 5.4 wird zusätzlich eine neue Association-Klasse definiert. Das ist notwendig, da die Methoden der PlistaAssociation-Klasse ebenfalls an die neue DataSource angepasst werden müssen. Die Implementierung verhält sich analog zu der Implementierung der Modelklasse. Die neue Redis DataSource wird N-N-Beziehungen nicht generisch unterstützen. Dafür gibt es zwei Gründe. Zum einen, taucht bei der Beispielanwendung, eine solche Beziehung nur einmal auf und das auch bei einer weniger kritischen Funktion, der Speicherung von Schlagwörtern. Zum anderen, soll nicht versucht werden, alle Funktionen eines relationalen Datenbanksystems in Redis abzubilden, da dadurch die Einfachheit verloren und die Key-ValueDatenbank nicht im eigentlichen Sinne verwendet wird. Im konkreten Anwendungsfall kann die Funktionalität natürlich durch den Einsatz der Callback-Methoden erreicht werden. 5.5 Redis-Datenmodell Nach dem die strukturellen Rahmenbedingungen für die neue Datenquelle festgesetzt wurden, wird nun festgelegt in welcher Form die Daten in Redis abgelegt werden. Lässt man die Relationen, in denen die Daten zueinander stehen zunächst einmal außer Betracht, bietet Redis für das Ablegen von tabellarischen Daten gute Möglichkeiten. Eine Zeile kann ohne weiteres in einem Hash abgelegt werden und ist damit in der Redis Datenbank vollständig abgebildet. Die Schlüssel 1 https://github.com/nicolasff/phpredis/ 2 Unter Adapter versteht man ein Entwurfsmuster, bei dem die Schnittstelle einer Klasse in die Schnittstelle einer anderen Klasse konvertiert wird. (Freeman et al. 2004, S. 243) 41 KAPITEL 5. ENTWURF des Hashes sind die ehemaligen Spaltennamen und die Werte die Zellenwerte. Der Schlüssel des Hash enthält dann noch den Primärschlüssel und jeder Datensatz ist sofort auffindbar. statement :[ id ] = hash ( statement = ’ hallo ’ , createdAt = ’ 2012 -08 -01 14:14 ’ ...... ) Prinzipiell würden man mit dieser Methode sämtliche Daten in Redis ablegen können, denn eine Datenbank besteht lediglich aus Zeilen in Tabellen. Allerdings existieren noch gravierende Nachteile. Die Datensätze können nur anhand des Primärschlüssels abgerufen werden. Eine Abfrage über Konditionen auf andere Werte des Datensatzes ist nicht möglich. Redis kann nur Abfragen über die Schlüssel durchführen. Außerdem lassen sich die Datensätze bisher nicht nach dem Wert einer Spalte darstellen. Datensätze sortieren Um die Datensätze sortiert ausgeben zu können bietet Redis den Befehl SORT. Mit diesem Befehl lässt sich ein Set, ein Sorted Set oder eine Liste sortieren. Allerdings ist es auch möglich nach einem externen Schlüssel über ein Schlüsselmuster zu sortieren. Hat man nun ein Set mit allen Primärschlüsseln (ID) und die zugehörigen Daten in Hashes mit Schlüssel die diese ID enthalten, lässt sich das Set anhand eines Wertes des Hashfeldes sortieren. Konkret bedeutet das, es ist notwendig neben der Speicherung der Zeilen, die Primärschlüssel in einem zusätzlichen Set mitzuführen. statements : index = set (1 ,2 ,34 ,45 ,67 , ...) Zusätzlich bietet die Funktion auch die Möglichkeit das Set zu limitieren und einen Startwert anzugeben analog zu den MySQL Attributen LIMIT und OFFSET. Konditionen Wie bereits erwähnt ist eine Abfrage in Redis nur nach Schlüsseln möglich. Möchte man Abfragen nach Konditionen durchführen, muss man diese Information in einer passenden Form in Schlüsseln ablegen. Der Autor ist der Meinung, dass das der bedeutendste Unterschied in der Handhabung zwischen Key-Value-Datenbank und relationaler Datenbank ist. Bei der Verwendung von Redis ist man in jedem Fall gezwungen vor dem Schreiben der Daten festzulegen, nach welchen Informationen man später sucht. Im Gegensatz dazu, können bei einer relationalen Datenbank alle Daten im Nachhinein beliebig aggregiert werden. Praktisch bedeutet das, dass Sets von Primärschlüsseln gepflegt werden müssen, deren Schlüsselnamen die Konditionen enthalten. statements : BY : user :[ id ] = set (4 ,7 ,8 ,12 ,34 ...) Für jede Kondition muss es ein eigenes Set geben. Über die Werte des Sets lassen sich dann die einzelnen Datensätze abrufen. Darüber hinaus kann die SORT Funktion verwendet werden, um die Ausgabe zu sortieren. Konfiguration Folglich ist festzuhalten, dass die Nutzung einer Key-Value-Datenbank nach mehr Konfiguration der zu speichernden Daten verlangt. Daher soll die bisherige Konfiguration 42 KAPITEL 5. ENTWURF über Klasseneigenschaften überdacht werden. Diese Art der Festlegung von Konfigurationen wird bei größeren Mengen von Daten schnell unübersichtlich, da entweder mit vielen Variablen oder mit mehrdimensionalen Arrays gearbeitet werden muss. Aus diesem Grund wird bei der Konfiguration der Cache-Ebene eine zentrale XML-Datei zum Einsatz kommen. In dieser werden Schlüsselname, Indizes, Konditionen und mehr festgelegt. Über den in PHP verfügbaren XMLLeser SimpleXMLElement lassen sich die Informationen unkompliziert auslesen. Zusätzlich bietet ein XML-Dokument die Möglichkeit, Konfigurationen mit einem XML-Schema auf Richtigkeit zu prüfen. 5.6 Denormalisierung Das Profiling aus Kapitel 4.1 und die Analyse der Datenhaltung in Kapitel 5.2 haben gezeigt, dass viele Datenzugriffe von unmittelbaren Berechnungen verursacht werden. Dazu zählt hauptsächlich die Berechnung der öffentlichen Meinung, aber auch die Ermittlung der Anzahl von Pro- und Contra-Argumenten oder Stimmen für ein Argument. Daher bietet es sich an, bei der Einführung einer zusätzlichen Datenhaltungsebene, an dieser Stelle zusätzliche denormalisierte Daten vorzuhalten. So wird die öffentliche Meinung eines Statement als reiner Wert dem Redis-Model hinzugefügt. Sollte eine Aktion den Wert ändern, wird eine Neuberechnung durchgeführt. Analog dazu wird die Anzahl der Stimmen für ein Argument in dem Fall erhöht, wenn eine neue Stimme abgeben wird. Eine weitere Möglichkeit ist, die Speicherung des Nutzernamens als Klartext, anstatt eines Verweis auf diesen über die Nutzer-ID. Die Anwendung wird nicht nur durch die schnellere Datenbank, sondern auch durch diese Methode beschleunigt werden. Natürlich besteht bei einer Denormalisierung immer die Gefahr, dass die Daten inkonsistent werden. Auch Änderungen werden aufwendiger, da dieselben Informationen an verschieden Stellen abgelegt sind. Im Fall einer einfachen Webanwendung ohne kritische Daten und größerer Relevanz ist dieser Punkt allerdings zu vernachlässigen. Sollte ein Zähler zu einem Zeitpunkt inkorrekt sein, ist das ohne Weiteres hinzunehmen. Außerdem bietet die normalisierte MySQL-Ebene die Möglichkeit, ihre Daten mit der Cache-Ebene abzugleichen und eventuelle Inkonsistenzen zu beseitigen. 5.7 Sonderfall Suche Eine Sonderstellung in Bezug auf Redis nimmt die Suchfunktion ein. Bekanntlich ist es bei Redis nicht möglich nach Werten abzufragen. Zur Realisierung einer Suche, müssten alle Begriffe nach denen gesucht wird, in den Schlüsseln stehen. Zum Beispiel müsste eine Statement in seine einzelnen Wörter zerlegt werden. Für jedes Wort wird dann hinterlegt, bei welchem Statement es vorkam. Mit dieser Methode würde man Rechenzeit und viele tausende Schlüssel verbrauchen. Man kann daher festhalten, dass Redis nicht geeignet ist, eine Suchfunktion bereitzustellen. Daher wird diese Funktionalität weiterhin von MySQL übernommen. 43 KAPITEL 5. ENTWURF Abbildung 5.4: Klassendesign der Redis Cache Ebene 44 Kapitel 6 Implementierung Abbildung 5.4 zeigt das Klassendiagramm der Redis Cache-Ebene. Diese Klassen wurde vollständig implementiert. Die folgenden Abschnitte dokumentieren die konkrete Nutzung und Funktionsweise. Zusätzlich werden Probleme, die sich bei der Implementierung ergeben haben, aufgezeigt. 6.1 Cache-Ebene verwenden Wie geplant, lässt sich die neue Cache-Ebene einsetzen, indem die konkreten Models statt vom PlistaModel vom neuen Model ableiten. Zusätzlich sind zwei weitere Schritte notwendig. Zum einen muss der Redis DataSource einmalig eine PHPRedis Instanz übergeben werden. Das geschieht über den Aufruf einer statischen Methode der DataSource. Es ist möglich sowohl eine reine PHPRedis oder eine PlistaRedis Instanz zu übergeben. Zum anderen muss ebenfalls über eine statische Methode der Pfad zur Konfigurationsdatei im XML-Format übergeben werden. Alle Models werden in einer Datei konfiguriert. Anhang B zeigt eine solche XML-Datei. Tabelle 6.1 zeigt die notwendigen Elemente. Element key primarykey Bedeutung Legt den Prefix aller Schlüssel des Models fest Legt fest, welches Feld des Daten-Arrays den Primärschlüssel darstellt Wenn true, wird der Index aller Einträge mitgeführt Wenn true, werden die Daten eines Models abgespeichert Legt alle Konditionen fest, welche mitgespeichert werden index entity conditions Tabelle 6.1: Elemente einer XML-Konfigurationsdatei der Redis DataSource Zusätzliche Funktionalitäten müssen manuell in den Callback-Methoden definiert werden. 45 KAPITEL 6. IMPLEMENTIERUNG 6.2 Datenverarbeitung im Detail Hat man eine Model für die Nutzung der Cache-Ebene konfiguriert, läuft das Lesen und Schreiben von Daten wie im folgenden beschrieben ab. Schreiben von Daten Einen neuen Eintrag erhält man wie bisher durch das Erstellen einer neuen Instanz und der Übergabe eines Arrays mit den Daten. Danach muss nur doch die Save-Methode aufgerufen werden. Diese Methode ruft dann zunächst die Save-Methode der Elternklasse auf, dadurch werden die Daten in MySQL gespeichert. Zusätzlich werden eventuell konfigurierte Validierungen von der Elternklasse durchgeführt. Ist das Speichern erfolgreich, wird die Create-Methode der DataSource aufgerufen. In diesem Fall wurde beim Model eine Instanz der Redis DataSource instanziiert. Die DataSource stellt anhand der Konfigurationsdatei fest, welche Daten gespeichert werden sollen und führt die entsprechenden Operationen durch. Soll ein Index mitgeführt werden, wird das entsprechende Set um den Wert ergänzt und soll die Entität abgespeichert werden, wird ein entsprechender Hash in Redis abgelegt. Eventuelle Konditionen werden ebenfalls ausgelesen und abgespeichert. Die Schlüssel werden dabei über Methoden der Utilities-Klasse (siehe Abbildung 5.4) gebildet, damit deren Strukturen austauschbar sind. Entsprechende Callback-Methoden werden im Zuge des Speicherns ebenfalls aufgerufen. Lesen von Daten Für das Lesen von Daten wird ein passender Finder am Model aufgerufen. Das neue Model ruft dann zunächst eine Methode auf, welche die Parameter des PlistaModel in ein, für eine DataSource kompatibles Format umwandelt. Danach wird die Read-Methode der DataSource mit den kompatiblen Parametern aufgerufen. Folgender Find-Aufruf zeigt alle bisher möglichen Parameter: Statement :: find ( ’ all ’ , array ( ’ order ’ = > ’ createdAt ’ ’ limit ’ = > 5 , ’ offset ’ = > 20 , ’ index ’ = > ’ statements : mostrecent ’ , ’ conditions ’ = > array ( ’ userId ’ = > 4 , ), )); Die Redis DataSource unterstützt genau diese Parameter in dieser Form. Optionen können weggelassen werden. Anders als beim PlistaModel müssen Konditionen als Array übergeben werden. Im nächsten Schritt überprüft die DataSource, ob Abfragen mit den übergebenen Optionen von Redis beantwortet werden können. Zum Beispiel ist eine Abfrage mit Konditionen, die nicht konfiguriert wurden, nicht möglich. Sind die Optionen nicht verfügbar wird eine Exception geworfen und das Model gibt die Abfrage an die Elternklasse weiter. Passen die Optionen werden die Daten aus Redis geholt und als Array zurückgegeben. Das Model muss das reine Array dann abschließend in eine PlistaCollection umwandeln. Ändern, Löschen und Zählen von Daten Alle weiteren Operationen verlaufen analog zum Lesen von Daten. Beim Zählen wird ein passender Index ermittelt und dann mit ei46 KAPITEL 6. IMPLEMENTIERUNG ner entsprechenden Redis Methode die Anzahl von Elementen im Index bestimmt und zurückgegeben. Beim Ändern von Daten werden die alten Daten in Redis einfach durch die neuen ersetzt und eventuell veraltete Konditionen entfernt. Das Löschen von Daten ist exakt die umgekehrte Operation zum Schreiben von Daten. Es wird sowohl in MySQL gelöscht, als auch in Redis. Dabei werden alle Einträge in Indizes mit entfernt. 6.3 Die Models im Detail Die Models, z.b. das Statement-Model, nutzen hauptsächlich die generisch angebotenen Funktionen der Erweiterung. Einige zusätzliche Funktionen wurden mit Hilfe der Callback-Methoden ergänzt. Hauptsächlich handelt es sich dabei um die zusätzlichen Daten, die durch die Denormalisierung entstanden sind. Zum Beispiel wird bei einem Argument vor dem Speichern eine zusätzliches Feld countVotes angelegt, welche die Anzahl an Stimmen erhöht. Wird nun eine neue Stimme über das ArgmtVotes-Model abgeben, wird dort ebenfalls in einer Callback-Methode das entsprechende Feld des zugehörigen Arguments erhöht. Auch das Schlagwortsystem wurde manuell ergänzt. Die Schlagwörter werden denormalisiert, unmittelbar im JSON-Format im Statement-Hash abgelegt und können somit in der Statement-Ansicht unmittelbar ausgegeben werden. Weitere Daten werden auf diese Weise gepflegt. 6.4 Transparenz Eine Anforderung war, dass die Erweiterung transparent sein soll und beim Datenzugriff keine großen Änderungen erfolgen. Es wurden fast alle Schnittstellen des ursprünglichen Models umgesetzt und für die Nutzung der Redis Erweiterung nutzbar gemacht. Einige wenige Ausnahmen existieren. Zum Beispiel wurde der Finder findBy noch nicht implementiert, da seine Funktionalität auch durch ein find erreicht werden kann. Außerdem sind die möglichen Übergabeparameter der Finder etwas eingeschränkter. Die bisherige Möglichkeit unmittelbar SQL Syntax zu verwenden, entfällt bei Nutzung von Redis, um dadurch ein allgemeingültiges Format zu erreichen. Dieses Vorgehen wird vom Autor als einen Schritt angesehen, die vorliegende Software robuster und besser wartbar zu gestalten. Folglich mussten dadurch einige bestehende Finder-Aufrufe angepasst werden. Um die Performance der ursprünglichen Fassung zu erhöhen, wurden teilweise unmittelbar SQL-Abfragen verwendet und das PlistaModel an dieser Stelle umgangen. Durch die schnelle Redis Datenbank ist das an vielen Stellen überflüssig und die Abfragen konnten durch reine Model-Methoden ersetzt werden. Im Prinzip wurde dadurch ein Teil der Transparenz sogar wiederhergestellt. Insgesamt kann festgehalten werden, dass die geforderte Transparenz erreicht wurde. Der Lauf der Unit- und Integrationstests wird endgültige Ergebnisse liefern. 6.5 Probleme und Schwierigkeiten Im Großen und Ganzen gab es bei der Implementierung keine schwerwiegenden Probleme. Eine große Schwierigkeit lag bei der Analyse der bestehenden Funktionalität. Das PlistaModel verwendet häufig keine festen Parameterlisten bei der Definition von Methoden, sondern macht sich 47 KAPITEL 6. IMPLEMENTIERUNG die Möglichkeit von PHP zu Nutze, die Anzahl und den Typ von Übergabeparametern komplett dynamisch zu gestalten. Daher war es notwendig, den gesamten Quelltext zu analysieren, um sich über alle Möglichkeiten klar zu werden. Mögliche Rückgabewerte waren oftmals ebenso schwer zu erkennen, da auch an dieser Stelle PHP sehr dynamisch gehalten ist. Ein Problem tauchte auf, als bei ersten Performance Tests festgestellt wurde, dass die Nutzung der Redis SORT Funktion bei sehr großen Indizes unzumutbar langsam wurde. Die Startseite zeigt beispielsweise die aktuellsten Statements an, wofür der komplette Index verwendet wurde. Als Reaktion darauf wurde zum einen die Nutzung des Index ebenfalls optional und die Übergabe eines eigenen Index an die Find-Methode eingeführt. Dadurch ist es möglich eigene Indizes in Form von Sets oder Lists mit Hilfe der Callback-Methoden zu pflegen. Die aktuellsten Statements werden nun in einer FIFO-Liste geführt. 6.6 Weitere Implementierungen Neben der eigentlichen Erweiterung wurde im Rahmen dieser Arbeit noch weitere Software entwickelt und auch administrative Aufgaben durchgeführt. Unittests Die Entwicklung der Unittests zum Zweck die bestehende Funktionalität festzuhalten und das spätere Ergebnis zu testen gehört ebenfalls zu den Aufgaben der Implementierung. Die Schnittstellen und ihre Parameter müssen identifiziert werden und in geeigneter Art getestet werden. Dazu zählt im Idealfall eine zusätzliche, leere Datenbank als Ausgangsvoraussetzung und das Einbinden der Bibliotheken der Anwendung in die Testumgebung. Innerhalb einer Testmethode werden dann zunächst die Ausgangsvoraussetzungen hergestellt. Beim Test eines Datenmodels beinhaltet das fast immer das Erstellen von Testdaten in der Datenbank. Erst danach kann die eigentliche Methode ausgeführt und das Ergebnis ausgewertet werden. Abschließend müssen die Testdaten wieder gelöscht werden um die gleichen Ausgangsvoraussetzungen wieder herzustellen. Zusätzlich zu den Unittests für die bereits existierenden Model-Klassen, wurden für ausgewählte Klassen und Methoden der Erweiterung Unittests entwickelt. Insbesondere die Methoden der Redis DataSource wurden auf ihre korrekte Arbeitsweise getestet. Anhang C.3 zeigt eine Übersicht der Tests. Testdaten-Script Um die Datenbank mit Testdaten zu befüllen wurde mit Hilfe des CakePHP Framework eine Konsolenanwendung entwickelt. Die Anwendung ist Teil des Understandr Quelltextes und somit auf allen Testservern verfügbar. Der Nutzer kann die gewünschte Anzahl an Statements, Nutzern und Schlagwörtern angeben. Die Anzahl der Argumente und Stimmen ergibt sich dann entsprechend der in der Analysephase bestimmten Verhältnisse. Die Anwendung benutzt für das Schreiben der Daten die Models der Anwendung. Auf diesem Weg war die Anwendung nach der Implementierung der Erweiterung in der Lage, die Daten auch in die Redis Datenbank zu schreiben. Neben dem Erstellen der Testdaten ist es möglich, zu ermitteln, welche die Statements mit den meisten Argumenten sind, um diese für die Lasttests zu verwenden. Server aufsetzen Neben der Entwicklung gehörte auch das Aufsetzen und Pflegen der nötigen Software auf den Testservern zu den Aufgaben dieser Ausarbeitung. Auf dem Webserver 48 KAPITEL 6. IMPLEMENTIERUNG musste die Anwendung installiert und das System entsprechend konfiguriert werden. Über die Versionsverwaltungssoftware Git und den Hostingdienst Github wurde immer die aktuellste Version der Software auf dem Server veröffentlicht. Auch der Datenbankserver mit MySQL und Redis musste installiert werden und die Schnappschüsse der Datenbank gesichert und vor den Lasttests wiederhergestellt werden. Zusätzlich war es während allen Lasttests notwendig, die Server zu überwachen, um eine Überlastung der Systemressourcen zu vermeiden. 49 Kapitel 7 Analyse und Test der neuen Version Die folgenden Abschnitte zeigen die Ergebnisse der Analyse der erweiterten Version der Anwendung. Die Analyse verläuft analog zu der des Ist-Zustandes. 7.1 Profiling Es wurde wiederum ein Statement mit 50 Argumenten abgerufen. Die gesamte Ausführungszeit beträgt nur noch ca. 140 Millisekunden und beträgt dabei nur ein Viertel der alten Variante. Tabelle 7.1 zeigt die genauen Ergebnisse. Die aufwändigsten fünf Funktionsaufrufe haben sich vollständig geändert. Immer noch am aufwändigsten ist trotzdem der Datenbankzugriff, bei dem die absolute Zeit aber deutlich gefallen ist. Die weiteren Funktionen sind nicht weiter einzuordnen. Es handelt sich um interne Funktionen des CakePHP Frameworks. Funktionsname Anzahl Aufrufe Zeit in ms Anteil an Ausführungszeit in % Redis::hGetAll 56 15 11,0 I18n::translate 705 11 8,2 ObjectCollection::trigger 125 11 7,9 CakeRoute::match 236 5 3,9 call user func array 1.366 4 3,0 Tabelle 7.1: Ergebnis des Profiling mit Redis-Cache 7.2 Performance Tabelle 7.2 zeigt die neuen Ergebnisse des Performance-Tests. Der Durchsatz ist insgesamt leicht gestiegen und die durchschnittliche Antwortzeit ist leicht gesunken. Dabei haben sich im Detail 50 KAPITEL 7. ANALYSE UND TEST DER NEUEN VERSION Use Case Anzahl Anfragen 1150 Gewünschter Durchsatz in Anfragen/s 120 Tatsächlicher Durchsatz in Anfragen/s 120 Durch. Antwortzeit in ms 182 Startseite aufrufen Statement aufrufen Suche Statement anlegen Argument angelegen Stimme abgeben Insgesamt 1079 840 233 252 860 240 120 24 44 24 1321 271 360 36 36 277 597 60 60 284 4286 1200 516 332 Tabelle 7.2: Ergebnisse des Performance-Tests mit Redis-Cache nur zwei Use Case verändert. Die Performance vom Aufruf der Statements hat sich stark gesteigert. Der Durchsatz hat sich mehr als verdoppelt womit sich die mittlere Antwortzeit mehr als halbiert. Kurioserweise ist der Durchsatz der Suche in der neuen Version gesunken. Insgesamt kann festgehalten werden, dass die Performance nur leicht gestiegen ist. 7.3 Skalierbarkeit Abbildung 7.1 zeigt den Verlauf der Antwortzeit sowohl für die neue Version als auch für die alte Fassung. Prinzipiell sehen sich die Kurven sehr ähnlich. Es ist wieder ein linearer Anstieg festzustellen. Allerdings ist der Anstieg deutlich geringer. Die mittlere Antwortzeit liegt bei 1700 ms und hat sich damit halbiert. Der geringere Anstieg zeigt, dass die Leistung bei höherer Nutzerzahl weniger einbricht. Das System skaliert dementsprechend etwas mehr als vorher. Auch der Vergleich des Durchsatzes in Abbildung 7.2 zeigt ähnliche Kurven. Allerdings ist der mittlere Durchsatz doppelt so hoch. Auch hält die Phase, in welcher der Durchsatz steigt, etwas länger an. Das System kann mit steigender Nutzerzahl etwas besser umgehen. 51 KAPITEL 7. ANALYSE UND TEST DER NEUEN VERSION Abbildung 7.1: Anwortzeit gegenüber Anzahl gleichzeitiger Nutzer im Vergleich zwischen alter und neuer Version Abbildung 7.2: Durchsatz gegenüber Anzahl gleichzeitiger Nutzer im Vergleich zwischen alter und neuer Version 52 KAPITEL 7. ANALYSE UND TEST DER NEUEN VERSION 7.4 Testfälle Die in Anhang C.1 aufgeführten Tests wurden mit der neuen Fassung durchgeführt. Das Ergebnis war insgesamt sehr erfolgreich. Einige verwendete Optionen der Finder werden in der neuen Fassung nicht mehr unterstützt, daher mussten diese Testfälle minimal angepasst werden. Die Selenenium-Tests (siehe Anhang C.2) wurden ebenfalls mir der erweiterten Fassung durchgeführt. Dabei traten einige Fehler auf. Dadurch konnten kleinere Bugs in der Implementierung entdeckt und beseitigt werden. Anschließend lief der Test ohne Fehler ab. 7.5 Beurteilung Die reinen Zahlen der Tests zeigen zwar eine relative Steigerung der Kennzahlen, allerdings erfüllen die Ergebnisse in diesem Testszenario die Anforderungen noch nicht. Die Performance ist mit einer mittleren Antwortzeit von ca. 330 ms noch nicht zufriedenstellend, angesichts des langfristigen Ziels von 50 ms. Auch ist der Anstieg der Kurve in Abbildung 7.2 noch zu steil, obwohl die Verbesserung zur alten Version sehr deutlich ist. Es ist an dieser Stelle aber festzuhalten, dass die Testumgebung aus relativ schwachen Servern bestand. Beide Server verfügen nur über einen Prozessorkern und der Webserver hat mit 1 GB sehr wenig Arbeitsspeicher. Mit zeitgemäßen Rechnern würde die Leistung der Anwendung mit Sicherheit deutlich steigen. Allerdings ist durch die neue Infrastruktur nun ein horizontales Skalieren unkompliziert möglich. Die Anzahl an Webservern kann ohne Eingriffe in die Software erhöht werden und die RedisDatenbank lässt sich mit Hashing oder Consistent Hashing nun ebenfalls skalieren. Dadurch kann die Hardware auf eine Weise angepasst werden, dass es möglich ist die Anforderungen an Performance und Skalierbarkeit zu erfüllen. Die anfängliche gestellten Anforderungen wurden damit erfüllt (siehe Tabelle 3.2). Performance und Skalierbarkeit wurden gesteigert, eine horizontale Skalierung ist einfach umzusetzen und die Erweiterung ist weitgehend transparent erfolgt. Selbstverständlich ist es notwendig die tatsächlichen Leistungsziele in einer weiteren Testumgebung zu evaluieren. Siehe dazu Kapitel 8.3. Zusätzlich muss genauer untersucht werden, warum der Durchsatz der Suche gesunken ist, um eventuelle Fehler in der Implementierung auszuschließen. 53 Kapitel 8 Zusammenfassung und Ergebnis 8.1 Zusammenfassung Aufgabe dieser Arbeit war es evaluieren, ob und in welchem Maße die Performance und Skalierbarkeit einer Webanwendung unter Einsatz einer Redis Datenbank gesteigert werden kann. Für diesen Zweck wurde eine bestehende, im Vorfeld vom Autor entwickelte Web 2.0-Anwendung, hinsichtlich dieser beiden Größen bewertet und ihr diesbezüglicher Zustand festgehalten. Im Anschluss wurde die, auf einer MySQL Datenbank basierende, Anwendung um eine zusätzliche Datenhaltungsschicht auf Basis der Key-Value-Datenbank Redis erweitert. Die zusätzliche Schicht wurde dabei transparent der ursprünglichen Datenhaltungsschicht hinzugefügt. Die Schnittstellen der Datenhaltungsschicht blieben dementsprechend nahezu unverändert. Es handelt sich dabei um generische Erweiterung, die nach einfacher Konfiguration, parallel zur MySQL Datenbank agiert. Die Daten werden weiterhin in beide Datenbanken geschrieben, wobei für den lesenden Zugriff die sehr schnelle Redis Datenbank favorisiert wird und damit die ursprüngliche Datenbank stark entlastet wird. Die neue Ebene lässt sich über die generischen Funktionen hinweg individuell anpassen. Dadurch war es möglich zusätzlich Daten der bestehenden Anwendung zu denormalisieren und dadurch aufwendige Abfragen und Berechnungen einzusparen. Die Performance und Skalierbarkeit der erweiterten Fassung der Anwendung wurde dann ebenfalls gemessen und festgehalten um den Ausgangszustand mit dem neuen Zustand zu vergleichen. Die Messung der Größen wurde dabei immer mit realistischen Testdaten durchgeführt, deren Umfang und Verteilung an eine reale Anwendung angelehnt sind. Im Rahmen der Qualitätssicherung wurden darüber hinaus für die ursprüngliche Fassung der Anwendung Integrations- und Unittests entwickelt. Auf diesem Weg war es möglich die Transparenz und Qualität der erweiterten Anwendung mit eben diesen Tests sicherzustellen. Sowohl Performance, als auch Skalierbarkeit konnten durch den Einsatz der Redis Datenbank gesteigert werden. Beim Test der Performance ist der Durchsatz um 25% gestiegen und die mittlere Antwortzeit ist um 25% gesunken. Der Test bestand aus dem gleichzeitigen Ausführen verschiedener Use Cases der Anwendung. Bei dem Test der Skalierung, welcher aus Anfragen an die Anwendung bei steigender Anzahl gleichzeitiger Nutzer bestand, konnte die durchschnittliche Antwortzeit halbiert werden. Neben der Steigerung dieser absoluten Zahlen, wurde durch die Redis Datenbank auch die Grundlage gelegt, die Performance und Skalierbarkeit mit wenig 54 KAPITEL 8. ZUSAMMENFASSUNG UND ERGEBNIS Aufwand weiter zu steigern. Die Anwendung ist damit, für die sich verändernden und steigenden Anforderungen an Anwendungen im Umfeld des Web 2.0 gerüstet. Durch die einfache Struktur und das simple Key-Value Prinzip der Redis Datenbank fällt eine horizontale Skalierung besonders leicht. Schlüssel können mit Hilfe von Hashwerten einfach auf mehrere Server verteilt werden. Spezielle Hash-Verfahren wie Consistent Hashing ermöglichen sogar ein Hinzufügen von Servern mit minimalen Aufwand hinsichtlich der Neusortierung von Daten. Dadurch lässt sich Performance und Skalierbarkeit beliebig steigern. Dem gegenüber steht die MySQL Datenbank, für welche ebenfalls Verfahren zur Leistungssteigerung und Skalierung existieren. Dazu gehören unter anderem, die einfach zu implementierende Replikation um Leseoperationen zu beschleunigen oder das aufwändige Sharding, bei dem Tabellen verteilt werden um sowohl Lese-, als auch Schreibleistung zu steigern. Beide Verfahren eigenen sich ebenfalls zur Skalierung. Diese Verfahren haben allerdings gemeinsam, dass sie aufwendiger umzusetzen sind. Das gilt insbesondere für die hier vorgestellte Webanwendung, welche mit vergleichsweise einfachen Datenstrukturen auskommt und deren Migration von einem relationalen Datenbankmodell in ein nicht-relationales Modell relative unkompliziert war. Zusammengefasst kann festgehalten werden, dass die Performance und Skalierbarkeit einer Webanwendung mit Hilfe einer Redis Datenbank in jedem Fall gesteigert werden kann. Dabei steigert sich sowohl die Performance und Skalierbarkeit an sich, als auch die Voraussetzungen beide Werte durch sehr einfaches horizontales Skalieren zu erhöhen, was hauptsächlich durch die Möglichkeit des Consistent Hashing gegeben ist. 8.2 Allgemeingültigkeit Die Ergänzung um eine Redis Datenbank war für die Bespielanwendung von Vorteil. Performance und Skalierbarkeit wurden erhöht und horizontales Skalieren ist nun einfach umzusetzen. Jetzt gilt es noch zu klären, inwieweit das Verfahren für Webanwendungen im Allgemeinen anwendbar ist. Grundsätzlich gilt, dass für jede Webanwendung individuell geklärt werden muss, welches Vorgehen für eine Steigerung der Performance und Skalierbarkeit am geeignetsten ist. Insbesondere muss dabei die Art der Anwendung und der Umfang der Nutzerzahlen berücksichtigt werden. Für den Einsatz einer generischen Redis-Cache-Ebene sind Annwendungen geeignet, die folgende Voraussetzungen erfüllen: • Überschaubares Datenmodell mit wenigen und einfachen Relationen • Absolute Konsistenz nicht notwendig • Hohe Zugriffszahlen und schnelles Wachstum Der erste Punkt ergibt sich aus der Natur der Redis Datenbank. Relationen können nativ nicht abgebildet werden und müssen in der Applikationsebene verwaltet werden. Für die Speicherung komplexer Beziehungen eignen sich relationale Datenbanksysteme immer noch am besten. Der zweite Punkt begründet sich ebenfalls in der Art und Weise wie Redis Daten speichert. Informationen sind oft nicht atomar und Teil verschiedener Schlüssel. Daher können zeitweise und vorübergehend immer Inkonsistenzen auftreten. Im Falle einer einfachen sozialen Webanwendung 55 KAPITEL 8. ZUSAMMENFASSUNG UND ERGEBNIS ist das unproblematisch. Für ernsthafte Datenverarbeitung, wie beispielsweise Online-Banking, ist das selbstverständlich nicht zu verantworten. Der letzte Punkt sollte auf jeden Fall immer bedacht werden. Nur bei wirklich hohen Zugriffszahlen lohnt sich der Einsatz einer sehr schnellen Redis Datenbank und der Aufwand diese zu integrieren. Für normale Szenarien ist eine traditionelle, relationale Datenbank ausreichend. Auch für Anwendungen mit hohen Zugriffszahlen, aber wenig oder sehr gut absehbaren Wachstum, kann eine relationale Datenbank ausreichend sein. Konkrete Vorgehensweisen müssten im Einzelfall bewertet werden. Sind diese Voraussetzungen jedoch gegeben, ist eine Redis Datenbank aufgrund ihrer Einfachheit und Geschwindigkeit in jedem Fall eine Überlegung wert. Eine einmal implementierte, generische Cache-Ebene ist sehr flexibel und kann auch steigenden Anforderungen und sich ändernden Datenstrukturen schnell angepasst werden. 8.3 Ausblick Die hier vorgestellte Implementierung stellt zunächst nur eine einfache Umsetzung einer RedisCache Ebene dar. Eine Fortführung der Entwicklung ist denkbar um weitere Funktionalitäten generisch abzubilden und den Einsatz von Redis damit noch einfacher zu gestalten. Sofort denkbar wäre eine automatische Pflege der aktuellsten Einträge eines Models - eine sicherlich häufig benötigte Funktion. Weitere solcher eingebauten Funktionen sind denkbar. Das Profiling der neuen Version hat gezeigt, dass immer noch der größte Teil der Ausführung auf die Datenhaltung verwendet wird. Es sollten Überlegungen angestellt werden, wie diese absolute Zahl der Zugriffe weiter gesenkt werden kann. Eine Möglichkeit wäre, zusätzlich größere Datenansichten in Redis zu speichern, wie zum Beispiel die bereits diskutierten vollständigen HTML-Ansichten der Statements. In diesem Fall aber als zusätzliche Daten, neben den atomaren Informationen. Dadurch könnte in bestimmten Ansichten die Anzahl an Datenbankanfragen weiter verringert werden. Es sollte auch evaluiert werden, inwieweit vollständig auf MySQL verzichtet werden kann und damit eine Mehrbelastung durch zwei Datenbanksysteme zu vermeiden. Dazu muss in jedem Fall ein Konzept erarbeitet werden, wie Inkonsistenzen vermieden werden können, die durch die Verteilung eines Model auf viele Schlüssel entstehen können. Abschließend wäre ist ebenfalls wünschenswert den Nutzen der Redis Datenbank in Bezug auf Performance und Skalierbarkeit in einem realen Nutzungsszenario mit echten Nutzern und Produktionsservern zu bewerten. Dadurch könnte eine wesentlich differenzierte Empfehlung bezüglich des Mehrwertes erfolgen. 56 Literatur Allspaw, J. (2008). The Art of Capacity Planning: Scaling Web Resources. O’Reilly Series. O’Reilly Media. isbn: 9780596518578. Calzarossa, M.C., S. Tucci und IFIP Working Group 7.3 on Computer System Modelling (2002). Performance Evaluation of Complex Systems: Techniques and Tools : Performance 2002 Tutorial Lectures. Lecture Notes in Computer Science. Springer. isbn: 9783540442523. Edlich, S., A. Friedland, J. Hampe und B. Brauer (2010). NoSQL: Einstieg in die Welt nichtrelationaler Web 2.0 Datenbanken. Hanser. isbn: 9783446427532. Edlich, S., A. Friedland, J. Hampe, B. Brauer und M. Brückner (2011). NoSQL: Einstieg in die Welt nichtrelationaler Web 2.0 Datenbanken. Hanser. isbn: 9783446427532. Freeman, E.T., E. Robson, B. Bates und K. Sierra (2004). Head First Design Patterns. Head First. O’Reilly Media. isbn: 9780596007126. Governor, J., D. Nickull und D. Hinchcliffe (2009). Web 2.0 Architectures. Oreilly Series. O’Reilly Media, Incorporated. isbn: 9780596514433. Gull, Clemens (2011). Web-Applikationen entwickeln mit NoSQL. Franzis. isbn: 9783645601047. Henderson, C. (2006). Building Scalable Web Sites. O’Reilly Series. O’Reilly. isbn: 9780596102357. Joines, S., R. Willenborg und K. Hygh (2003). Performance Analysis for Java Web Sites. Addison Wesley. isbn: 9780201844542. Kecher, Christoph (2011). UML 2: Das umfassende Handbuch. Galileo Design. Galileo Press GmbH. isbn: 9783836214193. Meier, J. (2007). Performance Testing Guidance for Web Applications: Patterns & Practices. O’Reilly Media, Inc. isbn: 9780735646001. Menascé, D.A., V.A.F. Almeida, L.W. Dowdy und L. Dowdy (2004). Performance by Design: Computer Capacity Planning by Example. Prentice Hall PTR. isbn: 9780130906731. Möhrke, C. (2012). Besser PHP programmieren. Galileo computing. Galileo Press. isbn: 9783898423816. Nagaraj, S.V. (2004). Web Caching and Its Applications. Kluwer international series in engineering and computer science. Kluwer Academic Publishers. isbn: 9781402080494. 57 LITERATUR Oracle (2012). Guide to Scaling Web Databases with MySQL Cluster. Techn. Ber. Pröll, S., E. Zangerle und W. Gassler (2011). MySQL: Das Handbuch für Administratoren. Galileo Press GmbH. isbn: 9783836217156. Seguin, K (2012). The Little Redis Book. http://openmymind.net. Shuen, A. (2008). Die Web 2.0 Strategie. O’Reilly. isbn: 9783897218666. Souders, S. (2009). Even Faster Web Sites. O’Reilly Series. O’Reilly. isbn: 9780596522308. Whittaker, J.A., J. Arbon und J. Carollo (2012). How Google Tests Software. Addison Wesley Professional. isbn: 9780321803023. 58 Online Quellen Apache (2012). Apache JMeter. url: http://jmeter.apache.org/usermanual/intro.html (besucht am 11. 07. 2012). CakePHP (2012). Understanding Model-View-Controller. url: http://book.cakephp.org/2. 0 / en / cakephp - overview / understanding - model - view - controller . html (besucht am 06. 08. 2012). data.stackexchange.com (2012). Stack Exchange Data Explorer. (Besucht am 26. 06. 2012). highscalability.com (2012). The Four Meta Secrets of Scaling at Facebook. url: http://highscalability. com/blog/2010/6/10/the-four-meta-secrets-of-scaling-at-facebook.html (besucht am 23. 07. 2012). ITWissen (2012). Performance. url: http : / / www . itwissen . info / definition / lexikon / Performance-performance.html (besucht am 23. 07. 2012). meta.stackoverflow.com (2010). Does Stack Overflow use caching and if so, how? url: http: //meta.stackoverflow.com/questions/69164/does- stack- overflow- use- cachingand-if-so-how (besucht am 02. 08. 2012). nosql-databases.org (2012). List Of NoSQL Databases. url: http://nosql-databases.org/. Offizielle Redis Webseite (2012). How fast is Redis? url: http://redis.io/topics/benchmarks (besucht am 19. 07. 2012). O’Reilly, Tim (2005). What Is Web 2.0? url: http://www.oreilly.de/artikel/web20.html (besucht am 18. 06. 2012). pch.net (2012). Internet Exchange Directory. url: https://prefix.pch.net/applications/ ixpdir/ (besucht am 03. 07. 2012). plista GmbH (2012). Offizielle Webpräsenz. url: http://www.plista.com/infos/company (besucht am 18. 06. 2012). quantcast (2012). Statistiken von Stack Overflow. url: http://www.quantcast.com/stackoverflow. com (besucht am 07. 08. 2012). 59 ONLINE QUELLEN stackexchange.com (2012). About Stack Exchange. url: http : / / stackexchange . com / about (besucht am 26. 06. 2012). stackoverflow.com (2009). Stack Overflow Blog. url: http://blog.stackoverflow.com/2009/ 09/one-million-pageviews/ (besucht am 26. 06. 2012). — (2012a). About Stack Overflow. url: http : / / stackoverflow . com / about (besucht am 26. 06. 2012). — (2012b). Stack Overflow FAQ. url: http://stackoverflow.com/faq (besucht am 09. 08. 2012). www.de-cix.net (2012). DE-CIX. url: https://www.de-cix.net/about/statistics/ (besucht am 03. 07. 2012). 60 Anhang A Stack Overflow SQL Abfragen Listing A.1: Minimale und Maxmimal Anzahl an Antworten SELECT min ( Posts . AnswerCount ) as [ Minimum # of Answers ] , avg ( cast ( Posts . AnswerCount as decimal )) as [ Average # of Answers ] , max ( Posts . AnswerCount ) as [ Maximum # of Answers ] FROM Posts WHERE Posts . PostTypeId =1 Listing A.2: Gesamtzahl an Fragen SELECT count ( Id ) FROM posts WHERE Posts . PostTypeId =1 Listing A.3: Fragen ohne Antworten SELECT count ( Id ) FROM posts WHERE Posts . PostTypeId =1 AND AnswerCount is null OR AnswerCount = 0 Listing A.4: Fragen mit bestimmter Anzahl an Anworten SELECT count ( Id ) FROM posts WHERE Posts . PostTypeId =1 AND AnswerCount between 1 and 50 Listing A.5: Anzahl Nutzer SELECT count ( Id ) FROM users Listing A.6: Maximale Reputation SELECT max ( Reputation ) FROM users Listing A.7: Maximaler Score SELECT max ( score ) FROM posts Listing A.8: Anzahl an Beiträgen insgesamt SELECT count ( id ) FROM posts 61 ANHANG A. STACK OVERFLOW SQL ABFRAGEN Listing A.9: Score sortiert nach Häufigkeit SELECT score , count ( score ) as c FROM posts GROUP BY score ORDER BY c DESC Listing A.10: Anzahl Fragen vor einem bestimmten Datum SELECT count ( id ) FROM posts WHERE posttypeid = 1 AND creationdate < ’ 2009 -09 -30 ’ Listing A.11: Anzahl Nutzer vor einem bestimmten Datum SELECT count ( id ) FROM users creationdate < ’ 2009 -09 -30 ’ Listing A.12: Anzahl Stimmen in bestimmten Zeitraum SELECT count ( id ) FROM votes WHERE CreationDate BETWEEN ’ 2009 -09 -30 ’ and ’ 2009 -10 -01 ’ 62 Anhang B Beispielhafter Aufbau einer XML-Konfigurationsdatei <? xml version = " 1.0 " encoding = " UTF -8 " ? > < redisstorage > < arguments > < key > argument </ key > < primarykey > argumentId </ primarykey > < index > true </ index > < entity > true </ entity > < conditions > < condition > < field name = " statementId " < field name = " polarity " / > </ condition > < condition > < field name = " userId " </ condition > </ conditions > </ arguments > </ redisstorage > 63 /> /> Anhang C Übersicht und Ergebnisse der Tests C.1 PHPUnit Tests der Model Ergebnis und Bemerkung Test Anzahl Assertions Beschreibung IstZustand Redis-Version testFindById 4 Abruf eines Datensatzes Primärschlüssel nach OK OK testFindWithCondition 5 Abruf von Datensätzen mit Konditionen OK Anpassung Option ohne rungsangabe testCount 1 Abruf der Anzahl an Datensätzen OK OK testSave 3 Erstellen eines neuen Datensätzen OK OK testDelete 1 Löschen eines Datensatzes OK OK testUpdate 1 Änderung und anschließende Speicherung eines Datensatzes OK OK testUserAssociation 4 Abruf des Datensatzes des assoziierten Nutzers OK OK testArgumentsAssociation 3 Abruf der Datensätze der assoziierten Argumente OK OK testTagsAssociation 2 Abruf der Datensätze der assoziierten Tags OK OK testFindById 5 Abruf eines Datensatzes Primärschlüssel nach OK OK testFindWithCondition 5 Abruf von Datensätzen mit Konditionen OK Anpassung Option ohne rungsangabe testCount 1 Abruf der Anzahl an Datenssätzen OK OK testSave 3 Erstellen eines neuen Datensatzes OK OK testDelete 1 Löschen eines Datensatzes OK OK Statement Model OrderSortie- Argument Model 64 OrderSortie- ANHANG C. ÜBERSICHT UND ERGEBNISSE DER TESTS testUpdate 1 Änderung und anschließende Speicherung eines Datensatzes OK OK testUserAssociation 4 Abruf des Datensatzes des assoziierten Nutzers OK OK testStatementAssociation 2 Abruf des Datensatzes des assoziierten Statements OK OK testVotesAssociation 3 Abruf der Datensätze der assoziierten Votes OK OK testFindById 5 Abruf eines Datensatzes Primärschlüssel nach OK OK testFindWithCondition 3 Abruf von Datensätzen mit Konditionen OK OK testCount 1 Abruf der Anzahl an Datensätzen OK OK testSave 3 Erstellen eines neuen Datensatzes OK OK testDelete 1 Löschen eines Datensatzes OK OK testUpdate 1 Änderung und anschließende Speicherung eines Datensatzes OK OK testStatementsAssociation 3 Abruf der Datensätze der assoziierten Statements OK Anpassung Option ohne rungsangabe OrderSortie- testArgumentsAssociation 3 Abruf der Datensätze der assoziierten Argumente OK Anpassung Option ohne rungsangabe OrderSortie- testVotesAssociation 3 Abruf der Datensätze der assoziierten Votes OK Anpassung Option ohne rungsangabe OrderSortie- testFindById 2 Abruf eines Datensatzes Primärschlüssel nach OK OK testFindWithCondition 3 Abruf von Datensätzen mit Konditionen OK Anpassung Option ohne rungsangabe testCount 1 Abruf der Anzahl an Datensätzen OK OK testSave 2 Erstellen eines neuen Datensatzes OK OK testDelete 1 Löschen eines Datensatzes OK OK testUpdate 1 Änderung und anschließende Speicherung eines Datensatzes OK OK testStatementsAssociation 3 Abruf der Datensätze der assoziierten Statements OK OK testFindById 3 Abruf eines Datensatzes Primärschlüssel nach OK OK testFindWithCondition 3 Abruf von Datensätzen mit Konditionen OK Anpassung Option ohne rungsangabe testCount 1 Abruf der Anzahl an Datensätzen OK OK testSave 1 Erstellen eines neuen Datensatzes OK OK testDelete 1 Löschen eines Datensatzes OK OK testUpdate 1 Änderung und anschließende Speicherung eines Datensatzes OK Anpassung - ArgumentId muss existieren User Model Tag Model OrderSortie- ArgmtVote Model 65 OrderSortie- ANHANG C. ÜBERSICHT UND ERGEBNISSE DER TESTS C.2 Selenium Tests Test Beschreibung login Einloggen eines Testnutzers createstatement Erstellen eines Statements mit Schlagwörtern. Überprüfung, ob Statement vorhanden ist und mit Schlagwort versehen wurde. createargument Ein Argument zum dem Statement anlegen. Überprüfung, ob Argument vorhanden ist und sich die öffentliche Meinung verändert hat. voteargument Die Stimme des Arguments entfernen. Überprüfung, ob sich die öffentliche Meinung entsprechend ändert. deleteargument Das Argument löschen. deletestatement Das Statement löschen. search Eine Suche ausführen logout Testnutzer ausloggen C.3 PHPUnit Tests der Erweiterung Test Beschreibung testCreate Anlegen eines neuen Datensatzes testCreateWithMissingCondition Anlegen eines neuen Datensatzes, ohne eine konfigurierte Kondition mitzugeben testReadAll Alle Datensätze lesen testReadWithCondition Datensätze mit Kondition lesen testReadWithConditionWithoutEntity Datensätze mit Kondition lesen, ohne dass die Entität mitgespeichert wurde testReadWithWrongCondition Datensätze mit flaschen Konditionen lesen testReadById Einen einzelen Datensatz lesen testUpdateWithIndex Datensatz mit Index ändern testUpdateWithoutIndex Datensatz ohne Index ändern testDelete Datensatz löschen testCountAll Alle Datensätze zählen testCountWithCondition Datensätze nach Kondition zählen testProcessOptionsWrongOption Eine falsche Option übergeben testProcessOptionsWrongOrder Falsche Order-Option übergeben testProcessOptionsWrongConditions Falsches Condition-Format übergeben testProcessOptionsRightConditions Richtiges Condition-Format übergeben testProcessOptionsLimitAndOffset Optionen beinhalten Limit und Offset Redis-DataSource Utilities 66 Anhang D Inhalt der CD-ROM • JMeter Testdateien • JMeter Messergebnisse • Understandr Quelltext in der neuen Version • PHPUnit Tests • Selenium Test • Quelltext der Redis Erweiterung • Diese Arbeit im PDF-Format 67 Eigenständigkeitserklärung Hiermit versichere ich, dass ich die vorliegende Bachelorarbeit selbstständig und nur unter Verwendung der angegebenen Quellen und Hilfsmittel verfasst habe. Die Arbeit wurde bisher in gleicher oder ähnlicher Form keiner anderen Prüfungsbehörde vorgelegt. —————————————— Fabian Kirstein Berlin, 13.08.2012