Java Magazin 01/10

Effizientes Performancemanagement

Autor:

In den vorangegangenen Artikeln haben wir uns mit Performance- und Skalierbarkeitsproblemen und deren technischem Umfeld beschäftigt. Im letzten Artikel der Performancereihe werden wir auf die Themen „Performancemanagement“ und „Performance- Engineering“ sowie die Umsetzung im Unternehmensumfeld eingehen.

Performance wird in der Softwareentwicklung immer noch stiefmütterlich behandelt. Während sich speziell durch die Einführung agiler Entwicklungsansätze – speziell auch durch Test-driven Development – die funktionale Qualität von Software verbessert hat, trifft das auf den Bereich von Performance und Skalierbarkeit noch nicht zu. Die Gründe hierfür sind unterschiedlich. Sehr oft kursiert die Einstellung, dass gute Programmierer auch performanten Code schreiben und somit Performancetests überflüssig sind. Hier ist zu entgegnen, dass man Gleiches auch aus funktionaler Sicht behaupten könnte. Allerdings ist uns allen sehr wohl bewusst, dass Fehler einfach passieren und wir deshalb testen. Speziell wenn es um das Laufzeitverhalten einer Anwendung geht, sind potenzielle Probleme im Quellcode nicht ersichtlich. Sehr oft wird Performancetesten auch als höchst kompliziert empfunden. Probleme sind in diesem Bereich sehr schwer zu finden – oft schwerer als funktionale Probleme. Viele haben auch negative Erfahrungen beim Lösen von Performanceproblemen gemacht. Der häufigste Grund hierfür ist, dass man bis zum letzten Moment wartet, sich dem Thema anzunehmen – nämlich dann, wenn im Produktivbetrieb Probleme auftreten. Da man nicht dafür gesorgt hat, die geeigneten Daten zu sammeln und zudem aus Managementsicht die Probleme schon gestern hätten gelöst sein sollen, findet die Problemsuche zudem nicht in einem konstruktiven Umfeld statt. Alles in allem führt das dazu, dass das Analysieren von Performanceproblemen mehr an eine Wurzelbehandlung beim Zahnarzt als an eine Engineering-Tätigkeit erinnert. Performancemanagement gehört aus unserer Erfahrung sicherlich zu den am wenigsten organisierten und reaktivsten Tätigkeiten in der Softwareentwicklung. Wir werden uns in diesem Artikel speziell mit einem pragmatischen Ansatz im Performance-Engineering auseinandersetzen.

Am Anfang anfangen

Performance-Engineering soll bereits in der Entwicklungsphase begonnen werden. Speziell architekturelle Probleme sowie Regressionsprobleme lassen sich hier einfach erkennen und effizient lösen. Wesentlich ist hier, potenzielle Probleme anhand von Laufzeitcharakteristiken der Anwendung zu erkennen.

Vier Augen sehen mehr

Der wichtigste und zugleich am einfachsten realisierte Ansatz von Performance-Engineering in der Entwicklung sind Architektur-Reviews. Neben qualitativen Kriterien werden zusätzlich Laufzeitcharakteristiken der Anwendung untersucht. Das dynamische Verhalten der Anwendung ist aus dem Quellcode allerdings nicht unmittelbar ersichtlich. Die Anzahl der Datenbankzugriffe oder Remote-Aufrufe sowie andere Performancekennziffern müssen durch die Verwendung von Tracing- oder Profiling-Tools gesammelt werden. Beide Ansätze liefern vergleichbare aggregierte Metriken. Ohne Tracing-Informationen ist aber der detaillierte sequenzielle Ablauf der Programmausführung nicht erkennbar. Dieser muss dann erst mittels Debugging ermittelt werden. Hier gilt, wie im Performancemanagement sehr oft, entweder man investiert Geld oder Zeit.

Ursache und Wirkung

Kleine Ursache, große Wirkung – wer kennt das nicht? Das gilt speziell auch für Codeänderungen und die Auswirkungen auf das Laufzeitverhalten einer Anwendung. Speziell in großen Anwendungslandschaften sind oft die Auswirkungen von Codeänderungen auf das Gesamtsystem sehr schwer nachvollziehbar. Eine Impact-Analyse hilft hier schnell, einen Überblick zu bekommen, wie und wo bestimmte Methoden und Services verwendet werden. Hierzu werden die Testszenarien während der Ausführung aufgezeichnet und gespeichert. Will man dann einen bestimmten Service oder eine Methode ändern, kann man auf Basis dieser Daten einfach herausfinden, wo und wie diese verwendet wird. Dieser Ansatz ist ähnlich zu statischen Analyseansätzen, die auf Basis von Codemodellen analysieren, welche Testfälle bei Codeänderungen neu ausgeführt werden müssen.

Performancetesten in Continuous Integration

Verfügt man bereits über ein Continuous-Integration-System mit automatisierten Tests, so kann man diese um Performancetests erweitern. Sowohl Test- als auch Performancemanagementtools lassen sich einfach in Ant oder Maven Scripts integrieren. Wichtig ist hier die Testausführzeit. Diese sollte bei zumindest einer, im Idealfall zwei Sekunde liegen. Grund hierfür sind natürliche Schwankungen in den Ausführzeiten der Tests. Je kürzer die Testlaufzeit ist, desto größer ist die relative Auswirkung auf die Testergebnisse. Zum Performancetesten können einerseits funktionale Integrationstests verwendet werden. Diese decken meist wichtige Teile der Anwendung ab und hier können auch Architekturprobleme analysiert werden. Zu den wesentlichen Problemen gehören hier Datenbankzugriffe (lang laufende Abfragen oder Abfragen, die oft ausgeführt werden) und Remoting-Verhalten.
Des Weiteren empfiehlt es sich, für wichtige Anwendungskomponenten eigene Performancetests zu entwickeln. Diese testen dann spezielle Anwendungsfälle und deren Performance. Wesentlich ist für Performancetests, eigene Hardware anzuschaffen. Im Unterschied zu funktionalen Tests sind diese sehr anfällig auf Veränderungen der Systemumgebung. Eine detaillierte Anleitung ist im dynaTrace-Blog zu finden [2].
Die Ergebnisse der Tests lassen sich auf verschiedenste Weise an die Entwickler weitergeben. Es empfiehlt sich, diese einerseits als HTML Reports – ähnlich den bekannten JUnit Reports – allen Entwicklern zugänglich zu machen. Zusätzlich sollte man den Entwicklern die detaillierten Trace-Daten zur Verfügung stellen, damit diese die Probleme auch gleich analysieren können. Abbildung 1 zeigt die Daten aus einem Regressionstest in einer CI-Umgebung in Form eines Dashboard, in dem man direkt Differenzen zu vorangegangenen Builds erkennen kann.

Performance-Engineering im Testen

Im eigentlichen Anwendungstest ist der Lasttest sicherlich das klassische Mittel, um Performance und Skalierbarkeit zu testen. Heutzutage ist eine Reihe von Tools verfügbar. Diese reichen von Open-Source-Lösungen wie JMeter über günstige und dennoch sehr leistungsfähige Tools wie Proxy Sniffer, bis hin zu den Lösungen bekannter Anbieter wie HP LoadRunner oder Micro Focus SilkPerformer (früher Borland).

Test ist nicht gleich Test

Einfach einmal einen Lasttest zu machen, ist nicht genug. Sehr oft sind auch die Ziele für den Test nicht klar definiert. Ein „wir sehen einmal, wie die Performance so ist“ ist definitiv zu wenig. Zudem wird beim Lasttesten nicht nur die Performance der Anwendung getestet. Neben Performance und Skalierbarkeit soll auch die Stabilität der Anwendung genauer untersucht werden. Die ordnungsgemäße Funktion der Anwendung muss ebenfalls mitgetestet werden. Ist eine Anwendung unter einer bestimmten Last nicht mehr stabil und liefert z. B. nur leere Seiten zurück, so muss das im Lasttest erkannt werden. Ansonsten werden die Performancedaten falsch interpretiert. Die Antwortzeiten können zwar in Ordnung sein, nur stimmen die Inhalte nicht. Je nachdem, was man genau testen möchte, unterscheidet man unterschiedliche Arten von Tests. Im Kasten „Lasttesttypen und wozu sie verwendet werden“ wird ein Überblick über die unterschiedlichen Testtypen gegeben und es wird gezeigt, wozu sie verwendet werden.

Dashboard mit CI-Daten

Lasttesttypen und wozu sie verwendet werden

  • Klassischer Lasttest: Überprüft, ob eine Anwendung definierte Performance- und Skalierbarkeitsanforderungen erfüllt.
  • Stresstest: Erhöht die Last auf eine Anwendung so lange, bis diese zusammenbricht. Ziel ist es herauszufinden, wo der Sättigungspunkt der Anwendung liegt.
  • SOAK-Test: Ein sehr lange (bis zu mehreren Tagen) laufender Test, der dazu dient, die Stabilität der Anwendung zu testen und potenzielle Probleme in der Langzeitnutzung der Anwendung zu finden (z. B. Memory Leaks).
  • Baseline-Test: Ziel ist es, eine Grundlage für spätere Performancetests zu gewinnen und herauszufinden, was die optimal mögliche Performance der Anwendung ist. Dieser wird mit einem oder einer sehr geringen Anzahl an Benutzern durchgeführt.

Die Mischung macht’s

Der schwierigste, wenn auch gleichzeitig wichtigste Teil des Lasttestens ist die richtige Modellierung des Benutzerverhaltens. Hier kommt es darauf an, möglichst realitätsnahe Anwendungsfälle abzubilden. Diese müssen dann zu Szenarien kombiniert werden, die möglichst genau dem echten Verhalten der Benutzer entsprechen. Das gilt auch für die verwendeten Daten. Wenn alle Benutzer auf die gleichen Datensätze zugreifen, sind die getroffenen Aussagen nicht repräsentativ oder grundlegend falsch. Speziell Ressourcenzugriffsprobleme können nur mit einem richtigen Transaktionsmix erkannt werden. Es empfiehlt sich hier, auf Daten aus der Produktion zurückzugreifen. Sie haben diese Daten nicht? Dann befinden Sie sich in guter Gesellschaft.

Entschleunigung

Wichtig ist, in Lasttests mit realistischen Szenarien zu arbeiten. Das bezieht sich auch – oder speziell – auf das Benutzerverhalten. Neben realistischen Szenarien muss auch mit entsprechenden Thinktimes – also Nachdenkzeiten des Benutzers zwischen den Abfragen – gearbeitet werden. Gerade bei der Verwendung von kommerziellen Produkten, die auf Basis von virtuellen Benutzern lizensieren, ist man versucht, Last eher durch kürzere Antwortzeiten als durch eine höhere Anzahl an parallelen Benutzern zu realisieren. Man mag diese Feststellung oft unter dem Gesichtspunkt sehen, dass Toolhersteller versuchen, so mehr Lizenzen zu verkaufen. Das stimmt sicherlich, aber die Hauptargumente sind technischer Natur. Grundsätzlich wird die Performance einer Anwendung durch das Benutzerverhalten bestimmt. Eine Anwendung wird auch genau auf ein bestimmtes Benutzerverhalten hin optimiert. Die Anzahl der parallelen Anfragen und Benutzer ist eine wesentliche Kennzahl für das Verhalten der Anwendung.

Mehr gleichzeitige Benutzer bedeuten mehr offene Benutzersessions und somit höheren Speicherverbrauch. Zusätzlich wird auch eine höhere Anzahl anderer Ressourcen wie Netzwerkverbindungen benötigt. Eine höhere Anzahl paralleler Abfragen bedeutet andererseits, dass Ressourcen intensiver genutzt werden als normal. So passieren mehr gleichzeitige Datenbankzugriffe oder mehrere Threads wollen gleichzeitig dieselben Ressourcen verwenden. Das Locking-Verhalten der Anwendung ändert sich hier signifikant. Speziell die Konfiguration des Garbage Collectors von Resource Pools (z. B. Datenbank) oder Caches ist in diesem Umfeld nicht möglich. Zudem sind die eigentlichen Performancemetriken auch nur bedingt aussagekräftig.

Antwortzeiten, CPU- und Speicherverbrauch

Und jetzt?

Das eigentliche Lasttestwerkzeug ist aber nur ein Teil des Werkzeugkastens für effizientes Performancetesten. Obwohl diese Werkzeuge mehr oder weniger ausgereifte Möglichkeiten bieten, um Diagnosedaten zu sammeln, konzentrieren sie sich sehr stark auf die Lastgenerierung. In diesem Zusammenhang spricht man von Blackbox oder Greybox Testing. Hiermit ist zwar gut zu erkennen, ob man Probleme in der Anwendung hat oder nicht, die Problemanalyse wird aber nur bedingt unterstützt. Deshalb werden in modernen Lasttestumgebungen zusätzlich Profiling- und Tracing-Werkzeuge verwendet, die zusätzlich Metriken auf Anwendungsebene wie die Antwortzeiten von Methoden oder Komponenten sammeln. Auch hier gilt, dass man die Qual der Wahl zwischen Open-Source-Werkzeugen mit geringem Funktionsumfang bis hin zu kommerziellen Lösungen für den Test-Center-Einsatz hat.

Die wesentlichen Kriterien zur Auswahl geeigneter Lösungen ist einerseits die Lasttauglichkeit und anderseits der Detaillierungsgrad der Information. Klassische Profiler lassen sich oft nur schwer bis gar nicht unter hoher Anwendungslast verwenden. Der Overhead auf die Antwortzeiten ist hier zu hoch, sodass die Performancecharakteristiken der Anwendung zu stark verändert werden. Grundsätzlich gilt, dass man mit produktionstauglichen Werkzeugen besser beraten ist als mit Entwicklungswerkzeugen.

Der Detaillierungsgrad der Daten ist das zweite Kriterium. Neben der Anzahl der möglichen Messpunkte bei einem definierten Overhead spielt hier die Messmethode eine wesentliche Rolle. Es kann grundsätzlich zwischen zwei Arten der Performancemessung unterschieden werden. Bei der statistischen Datensammlung werden alle Transaktionen über eine bestimmte Messdauer hinweg zusammengefasst. Spezielle Use Cases und Ausreißer zu untersuchen, ist hier nur schwer möglich, grundsätzliche Performanceprobleme können aber identifiziert werden. Tracing-basierte Ansätze hingegen speichern die Daten unaggregiert. Dadurch ist eine wesentlich genauere Analyse der gesammelten Daten möglich. Im Beispiel werden die Daten beider Ansätze gegenübergestellt. Es ist klar zu erkennen, dass beim statistischen Ansatz sowohl Aufrufreihenfolge als auch Kontextinformation verlorengehen. Bei statistischen Verfahren lassen sich also globale Hotspots gut erkennen, zur Analyse komplexerer Probleme sind diese allerdings nicht geeignet.

Performancemanagement in der Produktion

Im Unterschied zu funktionellen Anforderungen reicht es im Performanceumfeld nicht aus, diese einmal zu testen und zu erwarten, dass sich diese nicht verändern. Zudem möchte (muss) man bei Produktionsanwendungen überprüfen können, ob diese ordnungsgemäß funktionieren, da operative Prozesse von diesen abhängen.

Anwendungsdaten

Monitoring – Was, wie … und warum?

Produktions-Monitoring ist für unternehmenskritische Anwendungen unerlässlich. Deshalb sollte es Teil eines Projekts sein. Leider findet man aber heutzutage immer noch viele Anwendungen, die nicht oder nur unzureichend überwacht werden. Genauso wie beim Lasttesten gilt auch beim Monitoring, dass es hier unterschiedliche Arten gibt,
die alle speziellen Aufgabenstellungen verfolgen. Im Kasten „Monitoring-Arten und wozu sie verwendet werden“ haben wir die wichtigsten zusammengefasst.
Welche Art von Monitoring man für ein konkretes Projekt realisiert, hängt von mehreren Kriterien ab. Zentral ist natürlich die Wichtigkeit der Anwendung oder besser gesagt die Auswirkungen, wenn diese nicht verfügbar ist. Systemmetriken und High-Level-Anwendungsmetriken sollten in jeder Anwendung gemessen werden, da ohne diese keine Problemanalyse möglich ist. Abbildung 2 und 3 zeigen Beispiele für typische, gemessene Anwendungs- und Systemmetriken.

Diagnose

Diagnose von Anwendungsproblemen wollen wir alle vermeiden – das geht aber nicht. Durch entsprechende Maßnahmen im Entwicklungsprozess kann man die Wahrscheinlichkeit von Problemen zwar massiv reduzieren, ganz vermeiden aber nie. Vor Jahren hat eine bekannte österreichische Bank mit dem Slogan „Man muss rechtzeitig darauf schauen, dass man es hat, wenn man es braucht“ geworben. Was für Geld gilt, gilt auch für Diagnosedaten. Problemdiagnose ist umso schwieriger, wenn man keine ausreichenden Daten zur Verfügung hat. Oft ist der erste Schritt bei einem Produktionsproblem, sich zu überlegen, wie man überhaupt die Daten bekommt, um eine Problemanalyse zu beginnen. Manche versuchen das zu umgehen, indem sie einfach „raten“, woran es liegen könnte. Sollten Sie wie wir zu den 99 % der Menschheit gehören, die keine hellseherischen Fähigkeiten besitzen, wird diese „Performance-Voodoo“ nicht funktionieren. Eine effiziente Diagnose steht und fällt also mit der Verfügbarkeit von Überwachungsdaten.

Trendanalyse

Ein wichtiger Teil im Performancemanagement ist das frühzeitige Erkennen von Problemen. Wenn man verschlechternde Performance erkennt, bevor es zu Problemen kommt, kann man im Idealfall vermeiden, dass es für Endbenutzer zu sichtbaren Problemen kommt. Um bei der Trendanalyse erfolgreich und effizient zu sein, müssen die Daten vorhanden sein, um lösungsorientierte Aussagen treffen zu können.

Monitoring-Arten und wozu sie verwendet werden

  • Availability Monitoiring hat das Ziel zu überwachen, ob eine Anwendung überhaupt verfügbar ist. Das kann von einem einfachen Ping bis hin zu einem HTTP oder anderem Serviceaufruf reichen.
  • Performance-(oder SLA-)Monitoring hat das Ziel zu überwachen, ob definierte Antwortzeiten einer Anwendung eingehalten werden.
  • End-User Monitoring überprüft, ob Anwendungsfunktionalitäten verfügbar sind. Das wird mittels synthetischer Transaktionen realisiert, die – ähnlich zum Lasttest – Endbenutzerverhalten simulieren.
  • System-Monitoring hat die Aufgabe zu überwachen, ob Systemressourcen und -komponenten verfügbar sind.
  • Server-Monitoring hat die Aufgabe, wesentliche Metriken eines Application-Servers zu überwachen. Das sind meist Metriken, die von Servern über JMX bzw. PMI zur Verfügung gestellt werden.
  • Transaktions- oder Anwendungs-Monitoring überwacht einzelne Requests innerhalb des Anwendungsservers. Das dient dazu, um detaillierte Metriken auf Anwendungs- oder Codeebene zu erhalten.

Die 3 Ws

Im Wesentlichen muss man folgende Fragen beantworten können: Was passiert mit meiner Anwendung? Dazu benötigt man Performance-Monitoring-Daten wie Antwortzeiten. Im nächsten Schritt müssen wir uns fragen, wie die Umgebung aussieht, d. h. Anzahl der Requests oder Systemverhalten. Dann müssen wir untersuchen, was sich verändert hat. Je mehr Detaildaten wir zur Verfügung haben, desto genauer können wir diese Fragen beantworten. Wichtig ist, dass alle beantwortet werden. Eine Aussage wie „die Antwortzeiten der Anwendung sind im letzten Monat um fünf Prozent gestiegen“ ist definitiv zu wenig, um daraus konkrete Schlüsse ziehen zu können.

Feedback

Produktionsdaten gehören zu den wertvollsten Daten in Hinsicht auf Performance und Skalierbarkeit. Speziell Architekten können daraus wertvolle Informationen zur Verbesserung der Anwendungsarchitektur gewinnen und somit besser Designentscheidungen treffen. Tester können realistischere Szenarien für Lasttests bauen. Beides hat eine positive Auswirkung auf die Stabilität der Anwendung. Sehr oft wird aber genau das in Unternehmen nicht gelebt. Es verursacht aber oft nur minimalen Mehraufwand.

Fazit

Performancemanagement oder auch Performance-Engineering kann komplex sein. Ähnlich wie bei anderen komplexen Themen und Prozessen empfehlen wir einen pragmatischen Ansatz, den man kontinuierlich verbessert und vor allem in die gelebten Prozesse der Anwendungsentwicklung integriert. Jeder Prozess, jedes Werkzeug ist nutzlos, wenn man es nicht jeden Tag im Projekt einsetzen kann und das Leben des Entwicklers dadurch etwas einfacher wird. Gerade die Automatisierung von Performanceanalyse und auch Performancetests kann hier der entscheidende Faktor sein – ansonsten wird das Thema „Performance“, wie leider sehr oft, dem Alltagsdruck zum Opfer fallen.

Links & Literatur

[1] dynaTrace: www.dynatrace.com
[2] Performancemanagement in Continuous Integration: http://blog.dynatrace.com/2009/05/04/performance-management-in-continuous-integration/
[3] Micro Focus SilkPerformer: http://www.microfocus.com/products/SilkPerformer/index.asp
[4] Apache JMeter: http://jakarta.apache.org/jmeter/
[5] Proxy Sniffer Loadtesting: http://www.proxy-sniffer.com/

Vollständiger Artikel