Performanz im Griff behalten
Agile Softwareentwicklung vermeidet heute, dass Softwaresysteme am Kunden vorbei entwickelt werden. Funktional und auch gemäß Qualitätseigenschaften wie Bedienbarkeit entspricht das System den Kundenwünschen. Gleichzeitig können die damit einhergehenden Anpassungen und Erweiterung zur Verschlechterung der Performanz führen, wenn diese nicht gleichzeitig im Blick behalten wird. Am Ende steht dann ein System, das zwar alle gewünschten Funktionen implementiert, doch als langsam empfunden wird und dadurch an fehlender Akzeptanz leidet. Auch spätere Änderungen der Nutzung des Systems können sich auf die Performanz auswirken. Die Performanz muss deshalb nicht nur während der Entwicklung, sondern dauerhaft unter Beobachtung stehen.
Im Folgenden betrachten wir unterschiedliche Aspekte, die die Performanz eines Systems beeinflussen. Jeder Aspekt erfordert ein gezieltes Vorgehen, um die Performanz auf Dauer im Griff zu behalten.
Antwortverhalten – im Großen …
Spricht man von Performanz eines Systems, so liegt der Fokus oft ausschließlich auf seinem Antwortverhalten. Auch wenn das zu kurz greift, wie die nachfolgend erörterten Aspekte zeigen, widmen auch wir uns zunächst diesem Aspekt.
Das Antwortverhalten eines Systems lässt sich sehr einfach ermitteln, in dem die Zeit zwischen Stimulus und Antwort des Systems gemessen wird. Im Idealfall bietet das System Schnittstellen, die auch von außen angesprochen werden können. Werkzeuge wie JMeter1, Gatling2 oder Locust3 erlauben die einfache Definition von Lasttests gegen diverse Schnittstellen eines Systems.
Auch wenn die Messung durch Werkzeugunterstützung sehr einfach scheint, so bleibt doch die Frage, was gemessen werden soll und wie die Messergebnisse interpretiert werden. Messwerte als reine Zahlen haben keine Aussagekraft.
Um nicht nur eine quantitative Aussage zu treffen, sondern auch ein qualitative, sollten folgende Fragen vor der Messung gestellt werden:
- Was sind die Szenarien, die gemessen werden sollen? Nur realistische Durchläufe durch das System sind relevant für die Endnutzer des Systems. Ein Beispiel hierfür ist die Average-Hour, also eine typische Stunde der Nutzerinteraktion im Lebenszyklus des Systems. Die gewählten Szenarien werden im Werkzeug der Wahl abgebildet.
- Wie groß ist die typische und die maximale Anzahl von Anfragen pro Zeiteinheit? Die simulierten Anfragen sollten dem realen Aufkommen entsprechen. Dies dient als Grundlage für die durch das Werkzeug simulierte Last.
- Was ist die Erwartung? Die Messung muss quantifizierbar sein, um eine qualitative Aussage abzuleiten: System erfüllt die festgelegten Kriterien oder nicht.
Die Beantwortung dieser drei Fragen dient als Blaupause und Leitfaden für die Lasttests. Für die Testausführung müssen noch zusätzliche Einstellungen getätigt werden, z.B. die wie schnell die maximale Anzahl der parallelen Nutzer erreicht werden soll (Ramp-up-Zeit) oder wie die Nutzer prozentual auf die gewählten Szenarien verteilt werden. Den Rest übernimmt dann das Werkzeug.
Da wir auch die dritte Frage im Vorfeld der Messung beantwortet haben, ist die Interpretation des Messergebnisses einfach. So kann das Messergebnis zweifellos eingeordnet werden und unterliegt nicht der Spekulation oder gar Manipulation.
Die Grafik zeigt beispielhaft die Szenarien für eine typische Stunde im Lebenszyklus des Systems. Neben einer skalierbaren Anzahl von Nutzern, die Daten lesen und ändern, wird die periodische Datensynchronisation mit einem Drittsystem simuliert, um deren Einfluss auf die Performanz zu ermitteln.
… und im Kleinen
Lasttests testen die Performanz eines System als Ganzes. Doch was ist mit systeminternen Messungen des Laufzeitverhaltens? Auch diese können ihre Relevanz haben, sollten jedoch nur in gut begründeten Fällen eingesetzt werden. Ein Beispiel ist die Ermittlung der Schwankungsbreite der Laufzeiten einer definierten Funktion, um auf Basis der Messergebnisse die Ursache für die unterschiedlichen Laufzeiten zu finden.
Dieses sogenannte Microbenchmarking muss direkt im System erfolgen. Da ein manuelles Einfügen von Code zum Messen der Laufzeit einer Funktion meist wenig praktikabel ist, sollte auch hier auf Werkzeugunterstützung zurückgegriffen werden sollte, z.B. im Java-Umfeld auf JMH.
Mircobenchmarking hat jedoch seine Tücken bei der Messung der Performanz. Je nach verwendeter Laufzeitumgebung kann die Zeit ggf. nicht exakt bestimmt werden. Der zum Messen injizierte Code bleibt je nach verwendetem Werkzeug ggf. auch nach der Auslieferung im System und verhindert so eine klare Trennung von Test- und Produktivcode. Bevor man Microbenchmarking einsetzt, sollte man also sehr genau hinterfragen, ob die Messung tatsächlich notwendig ist und wenn ja, für welchen Zweck und an welchen Stellen.
Mengengerüste nicht aus den Augen verlieren
Unsere drei Fragen für die Vorbereitung der Lasttests haben einen Aspekt bisher ausgeklammert, der oft vergessen wird: Das Antwortverhalten eines Systems hängt von der performanten Implementierung der Funktionen ab, aber nicht ausschließlich. Oft steigt mit der Anzahl der zu verarbeitenden Datenmengen der Aufwand und damit die Laufzeitverhalten quadratisch oder gar exponentiell. Bei kleinen Sets von Testdaten fällt dies oft noch nicht auf und auch zum Produktionsstart ist oft nur eine kleine Menge von Daten zu verwalten. Je länger das System in Betrieb ist und je voller die Datenbank wird, desto eher zeigen sich die Effekte ungünstiger Implementierung, Anfragen von Kundenseite häufen sich, die ein „langsames“ System beanstanden.
Diesem Effekt gilt es vorzubeugen. Es gilt bereits bei den ersten Messungen der Performanz das später erwartete Mengengerüst zu berücksichtigen und für die Lasttests eine dementsprechende Datenmenge zu simulieren. Die Simulation sollte möglichst realistisch sein, also kein vereinfachten „Spieldaten“ nutzen, sondern sich an realen Daten mit all ihrer Varianz orientieren. Auf Änderungen an den Mengengerüsten, z.B. durch korrigierte Fehlannahmen oder neue Nutzeranforderungen, muss mit Anpassungen der simulierten Datenbasis reagiert werden. Am einfachsten realisiert man dies durch einen Datensimulator, der auf Knopfdruck eine gewünschte Menge von Testdaten bereitstellt.
Simulierte Baumaßnahmen (rechts) für einen Stresstest in einem System zur Baustellenkoordination. Trotz der hohen Datenmenge, die weit über das aktuelle Maß (links) hinausgeht, funktioniert der Client ausreichend performant.
Die richtige Basis wählen
Lasttests dienen dazu, die Performanz des Systems gegen seine aktuellen Anforderungen zu testen, wenn es in seiner aktuellen oder geplanten Umgebung betrieben wird. Aber automatisierte Performanztests können noch mehr.
Steht man bspw. vor der Aufgabe, die optimale Umgebung für sein System zu finden, leisten Tests ihren Beitrag. Sammeln wir die erstellten Szenarien zur Messung des Antwortverhaltens mit den simulierten Daten und kombinieren sie mit unterschiedlichen Plattformen, dann kommt man zu den Sizing-Tests. Diese dienen dazu, bei feststehendem System die Plattform zu bestimmen, auf der das System noch zuverlässig arbeitet.
Natürlich ziehen wir nicht unterschiedliche Hardwareausstattungen aus dem Schrank, sondern wählen auch hier den Weg der Automatisierung. Unterschiedliche Plattformen lassen sich heute automatisiert sehr einfach aufsetzen, z.B. mit Terraform bei einem Cloudanbieter. Eine größere Plattform ist dann nur noch eine Frage der Konfiguration.
Der Sizing-Test erzeugt also zunächst eine Instanz des Systems auf einer definierten Plattform, stellt die Daten bereit, führt die Tests per Lasttestwerkzeug durch und wertet die Messergebnisse aus. Werden die festgelegten Kriterien nicht erfüllt, bspw. die geforderte Antwortzeit überschritten, erfolgt die Bereitstellung der nächstgrößeren Plattform und der Test beginnt erneut. Da alle Schritte automatisierbar sind, lässt sich die gesamte Kette automatisieren. Der Test bricht ab, wenn die Kriterien erfüllt sind oder keine nächstgrößere Plattform mehr zur Verfügung steht.
Stress machen
Auch wenn wir jetzt wissen, wie die optimale Ausstattung unseres Systems aussieht, um die geforderte Last zu bewältigen, so ist unklar, wie das System auf Überlast reagiert. Um das Verhalten des Systems außerhalb seiner Grenzen beurteilen zu können und mögliche Probleme zu erkennen, bevor sie im Produktivbetrieb auftreten, sollte das System einem Stresstest unterzogen werden.
Der klassische Stresstest ist eigentlich ein Lasttest, der nur mehr Last in Form von Anfragen an das System erzeugt. Daneben lässt sich allerdings auch das Mengengerüst über das Maß hinaus erhöhen, für das das System eigentlich spezifiziert ist.
Stress kann man zudem erzeugen, indem man das System in seiner Basis einschränkt. Wie verhält sich das System bei einem Teilausfall oder wenn die Netzwerkverbindung zur Datenbank unterbrochen wird? Techniken aus dem Chaos Engineering können hier neben klassischen Performanztests eine Blaupause für eigene Stresstests sein.
Beide Bilder zeigen das gleiche Szenario, den Abruf einer definierten Datenmenge durch 150 parallele Nutzer (Gatling-Tests). Während im ersten Test alle Requests durchlaufen, schlägt im zweiten Test fast die Hälfte der Requests mit einem Timeout fehl. Der einzige Unterschied zwischen den Test besteht in unterschiedlichen Ramp-up-Zeiten der 150 Nutzer – im ersten sind es 90 Sekunden, im zweiten 180 Sekunden. Durch Variation der Tests (mehr Nutzer, mehr Daten, …) lassen sich die Grenzen eines Systems leicht ausloten.
Die Nutzer und ihr Verhalten kennen
Als letzten Aspekt, der die Performanz eines Systems beeinflusst, betrachten wir den Nutzer. Auch wenn die Anforderungen bezüglich der Performanz genau spezifiziert sind, kann der Anwender das System als langsam empfinden. Hier gilt es herauszufinden, wie die gefühlte Langsamkeit zu Stande kommt.
Dazu kommen sich ändernde Anforderungen über die Zeit, von denen die Entwicklung manchmal nur durch Zufall erfährt. Spezifizierte Grenzen des Systems werden ausgereizt und überschritten, weil es möglich ist. Umso wichtiger ist es, den Kontakt zum Kunden nicht abreißen zu lassen und auch technische Lösungen zur Überwachung des Laufzeitverhaltens in Betracht zu ziehen, wenn dies möglich ist. Moderne Monitoringsysteme ermöglichen es, Daten gezielt und anonymisiert zu sammeln, sofern der Kunde mit dieser Art des Monitorings einverstanden ist. Bottlenecks können so leicht erkannt werden, auch in der historischen Betrachtung.
Fazit zur Performanz von Systemen
Der Lasttest und seine Geschwister Stresstest und Sizing-Test sind wichtige Mittel, die Performanz seines Systems im Griff zu behalten. Gezielt eingesetzt und idealerweise direkt in die Build-Pipeline eingebunden, sind sie ein Baustein zur Sicherstellung der Softwarequalität und damit der Kundenzufriedenheit.
Hinweise:
Interessieren Sie sich für weitere Tipps aus der Praxis? Testen Sie unseren wöchentlichen Newsletter mit interessanten Beiträgen, Downloads, Empfehlungen und aktuellem Wissen.
Beschäftigen Sie sich auch mit dem Thema Performanz? Wollen Sie sich über Lasttests, Stresstests und Sizing-Tests austauschen, dann sprechen Sie Dr. Dehla Sokenou gerne an.
Weitere Erläuterungen zu Lasttest und Stresstest finden Sie hier.
Dr.-Ing. Dehla Sokenou
Dr.-Ing. Dehla Sokenou fühlt sich in allen Phasen der Software-Entwicklung zu Hause, besonders in Qualitätssicherung und Testen. Bei WPS – Workplace Solutions ist sie als Test- und Qualitätsmanagerin sowie Software-Architektin tätig. Daneben ist sie ein aktives Mitglied der GI-Fachgruppe Test, Analyse und Verifikation von Software und im Sprechergremium des Arbeitskreises Testen objektorientierter Programme.