Kapitel 4. Muster und Antimuster für Leistungstests
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
In diesem Kapitel stellen wir die verschiedenen Arten von Tests vor, die ein Team durchführen möchte, und besprechen die bewährten Methoden für jede Art von Test.
In der zweiten Hälfte des Kapitels werden wir einige der häufigsten Antipattern skizzieren, die einen Leistungstest oder ein Team plagen können, und überarbeitete Lösungen erklären, die verhindern, dass sie zu einem Problem für Teams werden.
Arten von Leistungstests
Leistungstests werden häufig aus den falschen Gründen oder schlecht durchgeführt. Die Gründe dafür sind sehr unterschiedlich, liegen aber oft darin begründet, dass das Wesen der Leistungsanalyse nicht verstanden wird und dass man glaubt, dass "etwas besser ist als nichts". Wie wir immer wieder sehen werden, ist dieser Glaube oft bestenfalls eine gefährliche Halbwahrheit.
Einer der häufigsten Fehler ist es, allgemein von "Leistungstests" zu sprechen, ohne sich mit den Einzelheiten zu befassen. Tatsächlich gibt es viele verschiedene Arten von groß angelegten Leistungstests, die mit einem System durchgeführt werden können.
Hinweis
Gute Leistungstests sind quantitativ. Sie stellen Fragen, die eine numerische Antwort ergeben, die als experimentelles Ergebnis behandelt und einer statistischen Analyse unterzogen werden kann.
Die Arten von Leistungstests, die wir in diesem Buch besprechen, haben in der Regel unabhängige (aber sich überschneidende) Ziele, deshalb solltest du bei der Planung eines einzelnen Tests vorsichtig sein. Eine gute Faustregel für die Planung eines Leistungstests ist es, die quantitativen Fragen, die der Test beantworten soll, aufzuschreiben (und dem Management/dem Kunden zu bestätigen) und zu erklären, warum sie für die zu testende Anwendung wichtig sind.
Im Folgenden findest du einige der häufigsten Testtypen und eine Beispielfrage für jeden:
- Latenztest
-
Wie lange dauert eine Transaktion von Anfang bis Ende?
- Durchsatztest
-
Wie viele gleichzeitige Transaktionen kann die aktuelle Systemkapazität bewältigen?
- Belastungstest
-
Kann das System eine bestimmte Last bewältigen?
- Stresstest
-
Was ist die Sollbruchstelle des Systems?
- Ausdauertest
-
Welche Leistungsanomalien werden entdeckt, wenn das System über einen längeren Zeitraum betrieben wird?
- Test zur Kapazitätsplanung
-
Skaliert das System wie erwartet, wenn zusätzliche Ressourcen hinzugefügt werden?
- Degradierung
-
Was passiert, wenn das System teilweise fehlschlägt?
Schauen wir uns jeden dieser Testtypen nacheinander genauer an.
Latenztest
Dies ist eine der häufigsten Arten von Leistungstests, weil sie in der Regel engmit einer Systembeobachtung verbunden ist, die für das Management von direktem Interesse ist: Wie lange warten unsere Kunden auf eine Transaktion (oder einen Seitenaufbau)? Das ist ein zweischneidiges Schwert, denn die quantitative Frage, die ein Latenztest beantworten soll, scheint so offensichtlich, dass sie die Notwendigkeit der Identifizierung quantitativer Fragen für andere Arten von Leistungstests verschleiern kann.
Hinweis
Das Ziel einer Latenzoptimierung ist in der Regel die direkte Verbesserung des Nutzererlebnisses oder die Einhaltung einer Service-Level-Vereinbarung.
Doch selbst im einfachsten Fall hat ein Latenztest einige Feinheiten, die sorgfältig behandelt werden müssen. Eine der auffälligsten ist, dass (wie wir in "Statistiken für die JVM-Leistung" ausführlich erörtern werden ) ein einfacher Mittelwert (Durchschnitt) als Maß dafür, wie gut eine Anwendung auf Anfragen reagiert, nicht sehr nützlich ist.
Durchsatztest
Der Durchsatz ist wahrscheinlich die zweithäufigste Größe, die bei Leistungstests verwendet wird. In gewissem Sinne kann er sogar mit der Latenzzeit gleichgesetzt werden.
Wenn wir z. B. einen Latenztest durchführen, ist es wichtig, die gleichzeitig laufenden Transaktionen anzugeben (und zu kontrollieren), wenn wir eine Verteilung der Latenzergebnisse erstellen.
Hinweis
Die beobachtete Latenz eines Systems sollte bei bekannten und kontrollierten Durchsatzwerten angegeben werden.
Ebenso führen wir normalerweise einen Durchsatztest durch, während wir die Latenz überwachen. Wir ermitteln den "maximalen Durchsatz", indem wir feststellen, wann sich die Latenzverteilung plötzlich ändert, d. h. einen "Bruchpunkt" (auch Wendepunkt genannt) des Systems. Der Sinn eines Stresstests besteht darin, solche Punkte und die Belastungsstufen, bei denen sie auftreten, zu ermitteln.
Bei einem Durchsatztest hingegen geht es darum, den beobachteten maximalen Durchsatz zu messen, bevor das System anfängt, sich zu verschlechtern.
Belastungstest
Ein Lasttest unterscheidet sich von einem Durchsatztest (oder einem Stresstest) dadurch, dass er in der Regelals binärer Test angelegt ist: "Kann das System die erwartete Last bewältigen oder nicht?" Lasttests werden manchmal im Vorfeld von erwarteten Geschäftsereignissen durchgeführt, z. B. wenn ein neuer Kunde oder ein neuer Markt hinzukommt, von dem erwartet wird, dass er den Datenverkehr in der Anwendung stark erhöht. Andere Beispiele für mögliche Ereignisse, die die Durchführung dieser Art von Tests rechtfertigen könnten, sind Werbekampagnen, Social Media Events und "virale Inhalte".
Stresstest
Eine Möglichkeit, einen Stresstest durchzuführen, besteht darin, festzustellen, wie viel Spielraum das System hat. In der Regel wird das System in einen stabilen Zustand versetzt, d. h. es wird ein bestimmter Durchsatz (oft der aktuelle Höchstwert) erreicht. Der Test erhöht dann langsam die Anzahl der gleichzeitigen Transaktionen, bis die beobachtbaren Werte des Systems abnehmen.
Der Wert kurz vor dem Beginn der Verschlechterung der Observablen bestimmt den maximalen Durchsatz, der in einem Durchsatztest erreicht wird.
Ausdauertest
Einige Probleme treten erst über einen viel längeren Zeitraum auf (oft in Tagen gemessen). Dazu gehören langsame Speicherlecks, Cache-Verschmutzung und Speicherfragmentierung (vor allem bei Anwendungen, die den Concurrent Mark and Sweep Garbage Collector verwenden, der irgendwann im Concurrent-Modus ausfallen kann; siehe "CMS" für weitere Informationen).
Um diese Art von Problemen zu erkennen, ist ein Dauertest (auch als Soak-Test bekannt) die übliche Vorgehensweise. Diese werden bei durchschnittlicher (oder hoher) Auslastung durchgeführt, aber innerhalb der beobachteten Belastungen für das System. Während des Tests werden die Ressourcen genau überwacht, um eventuelle Ausfälle oder die Erschöpfung der Ressourcen zu erkennen.
Diese Art von Test ist in Systemen mit schneller Reaktionszeit (oder niedriger Latenz) sehr verbreitet, da diese Systeme häufig nicht in der Lage sind, die Dauer eines Stop-the-World-Ereignisses zu tolerieren, das durch einen vollständigen GC-Zyklus verursacht wird (siehe Kapitel 6 und die folgenden Kapitel für weitere Informationen über Stop-the-World-Ereignisse und damit verbundene GC-Konzepte).
Kapazitätsplanungstest
Kapazitätsplanungstests haben viele Ähnlichkeiten mit Stresstests, aber sie sind eine andere Art von Test. Bei einem Stresstest geht es darum, herauszufinden, was das aktuelle System bewältigen kann, während ein Kapazitätsplanungstest eher zukunftsorientiert ist und darauf abzielt, herauszufinden, welche Last ein modernisiertes System bewältigen könnte.
Aus diesem Grund werden Kapazitätsplanungstests oft als Teil einer geplanten Planungsübung durchgeführt und nicht als Reaktion auf ein bestimmtes Ereignis oder eine Bedrohung.
Zersetzungstest
Ein Degradationstest wird auch als Teilausfalltest bezeichnet. Eine allgemeine Diskussion über Ausfallsicherheit und Failover-Tests würde den Rahmen dieses Buches sprengen, aber es genügt zu sagen, dass in den am stärksten regulierten und überprüften Umgebungen (einschließlich Banken und Finanzinstituten) Failover- und Wiederherstellungstests extrem ernst genommen werden und in der Regel sehr gründlich geplant werden.
Für unsere Zwecke ist die einzige Art von Ausfallsicherheitstest, die wir in Betracht ziehen, der Degradationstest.Der grundlegende Ansatz dieses Tests besteht darin, zu sehen, wie sich das System verhält, wenn eine Komponente oder ein ganzes Subsystem plötzlich an Kapazität verliert, während das System mit simulierten Lasten läuft, die den üblichen Produktionsvolumen entsprechen. Beispiele hierfür sind Anwendungsserver-Cluster, die plötzlich keine Mitglieder mehr haben, Datenbanken, die plötzlich keine RAID-Platten mehr haben, oder Netzwerkbandbreite, die plötzlich abfällt.
Zu den wichtigsten Beobachtungen während eines Degradationstests gehören die Verteilung der Transaktionslatenz und der Durchsatz.
Eine besonders interessante Unterart von Teilausfalltests ist der sogenannte Chaos Monkey. Er ist nach einem Projekt bei Netflix benannt, mit dem die Robustheit der Netflix-Infrastruktur überprüft werden sollte.
Die Idee hinter Chaos Monkey ist, dass in einer wirklich belastbaren Architektur der Ausfall einer einzelnen Komponente keinen kaskadierenden Ausfall verursachen oder einen bedeutenden Einfluss auf das Gesamtsystem haben sollte.
Chaos Monkey versucht, dies zu demonstrieren, indem es nach dem Zufallsprinzip Live-Prozesse beendet, die in der Produktionsumgebung tatsächlich im Einsatz sind.
Um Systeme nach dem Vorbild von Chaos Monkey erfolgreich zu implementieren, muss eine Organisation ein Höchstmaß an Systemhygiene, Service Design und operativer Exzellenz aufweisen. Dennoch ist es ein Bereich, der für immer mehr Unternehmen und Teams von Interesse und Anspruch ist.
Fibel für bewährte Methoden
Bei der Entscheidung, worauf du dich bei der Leistungsoptimierung konzentrieren solltest, gibt esdrei goldene Regeln, die dir eine nützliche Orientierung bieten können:
-
Finde heraus, was dir wichtig ist, und überlege, wie du es messen kannst.
-
Optimiere das, was wichtig ist, nicht das, was leicht zu optimieren ist.
-
Spiele zuerst die großen Punkte.
Der zweite Punkt hat eine Kehrseite: Erinnere dich daran, dass du nicht in die Falle tappst, einer Größe, die du leicht messen kannst, zu viel Bedeutung beizumessen. Nicht jede beobachtbare Größe ist für ein Unternehmen von Bedeutung, aber es ist manchmal verlockend, über eine einfache Größe zu berichten, anstatt über die richtige.
Top-Down Leistung
Einer der Aspekte der Java-Leistung, der vielen Ingenieuren auf Anhieb entgeht, ist die Tatsache, dass ein umfangreiches Benchmarking von Java-Anwendungen in der Regel einfacher ist als der Versuch, genaue Zahlen für kleine Codeabschnitte zu ermitteln. Wir werden dies in Kapitel 5 ausführlich besprechen.
Hinweis
Der Ansatz, mit dem Leistungsverhalten einer gesamten Anwendung zu beginnen, wird üblicherweise als Top-down-Leistung bezeichnet.
Um den Top-Down-Ansatz optimal nutzen zu können, braucht ein Testteam eine Testumgebung, eine klare Vorstellung davon, was es messen und optimieren muss, und eine Vorstellung davon, wie sich die Performance-Übung in den gesamten Lebenszyklus der Softwareentwicklung einfügt.
Erstellen einer Testumgebung
Das Einrichten einer Testumgebung ist eine der ersten Aufgaben, die die meisten Leistungstestteams erledigen müssen. Diese sollte nach Möglichkeit in allen Aspekten ein exaktes Duplikat der Produktionsumgebung sein. Dazu gehören nicht nur die Anwendungsserver (die Server sollten die gleiche Anzahl an CPUs, die gleiche Version des Betriebssystems und der Java-Laufzeitumgebung usw. haben), sondern auch Webserver, Datenbanken, Load Balancer, Netzwerk-Firewalls usw. Alle Dienste (z. B. Netzwerkdienste von Drittanbietern, die nicht einfach zu replizieren sind oder nicht über ausreichende QA-Kapazitäten verfügen, um eine der Produktion vergleichbare Last zu bewältigen) müssen für eine repräsentative Leistungstestumgebung nachgebildet werden.
Manchmal versuchen Teams, eine bestehende QA-Umgebung für Leistungstests wiederzuverwenden oder zeitlich zu teilen. Das kann für kleinere Umgebungen oder für einmalige Tests möglich sein, aber der Verwaltungsaufwand und die damit verbundenen Zeitplanungs- und Logistikprobleme sollten nicht unterschätzt werden.
Hinweis
Leistungsprüfungsumgebungen, die sich deutlich von den Produktionsumgebungen unterscheiden, die sie abzubilden versuchen, schlagen oft fehl, wenn es darum geht, Ergebnisse zu erzielen, die in der realen Umgebung von Nutzen oder aussagekräftig sind.
In traditionellen (d. h. nicht cloudbasierten) Umgebungen ist eine produktionsähnliche Umgebung für Leistungstests relativ einfach zu erreichen: Das Team kauft einfach so viele physische Maschinen, wie in der Produktionsumgebung verwendet werden, und konfiguriert sie dann genau so wie die Produktionsumgebung.
Das Management wehrt sich manchmal gegen die zusätzlichen Infrastrukturkosten, die damit verbunden sind. Das ist fast immer eine falsche Sparsamkeit, aber leider misslingt es vielen Unternehmen, die Kosten für Ausfälle korrekt zu berücksichtigen. Das kann dazu führen, dass man glaubt, dass die Einsparungen durch den Verzicht auf eine genaue Leistungstestumgebung sinnvoll sind, da die Risiken, die durch eine QS-Umgebung entstehen, die die Produktion nicht widerspiegelt, nicht richtig berücksichtigt werden.
Jüngste Entwicklungen, vor allem das Aufkommen der Cloud-Technologien, haben dieses eher traditionelle Bild verändert. On-Demand-Infrastruktur und automatische Skalierung bedeuten, dass immer mehr moderne Architekturen nicht mehr dem Modell "Server kaufen, Netzwerkdiagramm erstellen, Software auf Hardware installieren" entsprechen. Der Devops-Ansatz, bei dem die Serverinfrastruktur als "Vieh, nicht als Haustier" behandelt wird, bedeutet, dass sich viel dynamischere Ansätze für das Infrastrukturmanagement durchsetzen.
Das macht den Aufbau einer Umgebung für Leistungstests, die der Produktionsumgebung ähnelt, zu einer potenziellen Herausforderung. Es besteht jedoch die Möglichkeit, eine Testumgebung einzurichten, die bei Nichtgebrauch abgeschaltet werden kann. Das kann dem Projekt erhebliche Kosteneinsparungen bringen, erfordert aber ein geeignetes Verfahren zum planmäßigen Starten und Abschalten der Umgebung.
Leistungsanforderungen ermitteln
Erinnern wir uns an das einfache Systemmodell, das wir in "Ein einfaches Systemmodell" kennengelernt haben . Daraus geht klar hervor, dass die Gesamtleistung eines Systems nicht allein durch deinen Anwendungscode bestimmt wird. Der Container, das Betriebssystem und die Hardware spielen alle eine Rolle.
Deshalb sollten die Kennzahlen, die wir zur Leistungsbewertung verwenden, nicht nur im Hinblick auf den Code betrachtet werden. Stattdessen müssen wir das System als Ganzes betrachten und die beobachtbaren Größen, die für Kunden und Management wichtig sind. Diesewerden normalerweise als nichtfunktionale Leistungsanforderungen (NFRs) bezeichnet und sind die Schlüsselindikatoren, die wir optimieren wollen.
Einige Ziele sind offensichtlich:
-
Reduziere die Transaktionszeit im 95%-Perzentil um 100 ms.
-
Verbessere das System so, dass ein 5-facher Durchsatz auf der vorhandenen Hardware möglich ist.
-
Verbessere die durchschnittliche Reaktionszeit um 30%.
Andere sind vielleicht weniger offensichtlich:
-
Reduziere die Ressourcenkosten für die Bedienung eines durchschnittlichen Kunden um 50%.
-
Sicherstellen, dass das System immer noch innerhalb von 25 % der Reaktionsziele liegt, selbst wenn die Anwendungscluster um 50 % degradiert sind.
-
Verringere die Absprungrate deiner Kunden um 25 % pro 25 ms Latenzzeit.
Eine offene Diskussion mit den Beteiligten darüber, was genau gemessen werden soll und welche Ziele erreicht werden sollen, ist unerlässlich. Idealerweise sollte diese Diskussion Teil des ersten Kick-off-Meetings für die Leistungsübung sein.
Java-spezifische Probleme
Vieles aus der Wissenschaft der Leistungsanalyse ist auf jedes moderne Softwaresystem anwendbar. Die JVM ist jedoch so beschaffen, dass es einige zusätzliche Komplikationen gibt, die der Leistungsingenieur kennen und sorgfältig berücksichtigen sollte. Diese ergeben sich größtenteils aus den dynamischen Selbstverwaltungsfähigkeiten der JVM, wie z. B. der dynamischen Abstimmung von Speicherbereichen.
Eine besonders wichtige Java-spezifische Erkenntnis bezieht sich auf die JIT-Kompilierung. Moderne JVMs analysieren, welche Methoden ausgeführt werden, um Kandidaten für die JIT-Kompilierung in optimierten Maschinencode zu identifizieren. Das bedeutet, dass, wenn eine Methode nicht JIT-kompiliert wird, eines von zwei Dingen auf diese Methode zutrifft:
-
Sie wird nicht häufig genug ausgeführt, um eine Kompilierung zu rechtfertigen.
-
Die Methode ist zu groß oder zu komplex, um sie für die Kompilierung zu analysieren.
Die zweite Bedingung ist viel seltener als die erste. Eine frühe Leistungsübung für JVM-basierte Anwendungen besteht jedoch darin, eine einfache Protokollierung der Methoden, die kompiliert werden, einzuschalten und sicherzustellen, dass die wichtigen Methoden für die wichtigsten Codepfade der Anwendung kompiliert werden.
In Kapitel 9 werden wir die JIT-Kompilierung im Detail besprechen und einige einfache Techniken zeigen, mit denen sichergestellt wird, dass die wichtigen Methoden der Anwendungen von der JVM für die JIT-Kompilierung ausgewählt werden.
Leistungstests als Teil des SDLC
Manche Unternehmen und Teams ziehen es vor, Leistungstests als gelegentliche, einmalige Aktivität zu betrachten. Anspruchsvollere Teams neigen jedoch dazu, laufende Leistungstests und insbesondere Leistungsregressionstests zu einem festen Bestandteil ihres Softwareentwicklungszyklus (SDLC) zu machen.
Dies erfordert die Zusammenarbeit zwischen den Entwicklern und den Infrastrukturteams, um zu kontrollieren, welche Versionen des Codes sich zu einem bestimmten Zeitpunkt in der Umgebung für Leistungstests befinden. Außerdem ist es praktisch unmöglich, ohne eine spezielle Testumgebung zu arbeiten.
Nachdem wir einige der bewährten Methoden zur Leistungssteigerung erörtert haben, wollen wir uns nun den Fallstricken und Antipatterns zuwenden, denen Teams zum Opfer fallen können.
Einführung von Antipatterns zur Leistung
Ein Antipattern ist ein unerwünschtes Verhalten eines Softwareprojekts oder -teams, das bei einer großen Anzahl von Projekten beobachtet wird.1 Die Häufigkeit des Auftretens lässt den Schluss (oder den Verdacht) zu, dass ein zugrunde liegender Faktor für das unerwünschte Verhalten verantwortlich ist. Einige Antipatterns mögen auf den ersten Blick gerechtfertigt erscheinen, da ihre nicht-idealen Aspekte nicht sofort offensichtlich sind. Andere sind das Ergebnis negativer Projektpraktiken, die sich im Laufe der Zeit anhäufen.
In einigen Fällen kann das Verhalten durch soziale oder teaminterne Zwänge, durch falsch angewandte Managementtechniken oder einfach durch die menschliche Natur (und die der Entwickler) bedingt sein. Indem wir diese unerwünschten Eigenschaften klassifizieren und kategorisieren, entwickeln wir eine "Mustersprache", um sie zu diskutieren und hoffentlich aus unseren Projekten zu eliminieren.
Die Leistungsoptimierung sollte immer als ein sehr objektiver Prozess behandelt werden, für den schon in der Planungsphase genaue Ziele festgelegt werden. Das ist leichter gesagt als getan: Wenn ein Team unter Druck steht oder nicht unter vernünftigen Bedingungen arbeitet, kann das leicht auf der Strecke bleiben.
Viele Leserinnen und Leser kennen die Situation, in der ein neuer Kunde in Betrieb genommen oder eine neue Funktion eingeführt wird und es zu einem unerwarteten Ausfall kommt - wenn du Glück hast, beim User Acceptance Testing (UAT), aber oft auch in der Produktion. Das Team ist dann aufgeschmissen, um die Ursache für den Engpass zu finden und zu beheben. Das bedeutet in der Regel, dass die Leistungstests nicht durchgeführt wurden oder dass der "Ninja" des Teams eine Vermutung geäußert hat und nun verschwunden ist (Ninjas sind gut darin).
Ein Team, das auf diese Weise arbeitet, wird wahrscheinlich häufiger Opfer von Antipatterns als ein Team, das gute Leistungstests durchführt und offene und fundierte Gespräche führt. Wie bei vielen Entwicklungsproblemen sind es oft die menschlichen Elemente, wie z. B. Kommunikationsprobleme, und weniger die technischen Aspekte, die zu Problemen in einer Anwendung führen.
Eine interessante Möglichkeit zur Einordnung bietet ein Blogbeitrag von Carey Flichel mit dem Titel "Why Developers Keep Making Bad Technology Choices". In dem Beitrag werden fünf Hauptgründe genannt, die Entwickler dazu bringen, schlechte Entscheidungen zu treffen. Schauen wir uns diese Gründe nacheinander an.
Langeweile
Die meisten Entwickler/innen haben schon einmal erlebt, dass sie sich in ihrer Rolle gelangweilt haben, und bei manchen dauert es nicht lange, bis sie nach einer neuen Herausforderung oder Rolle im Unternehmen oder anderswo suchen. Es kann aber auch sein, dass es in dem Unternehmen keine anderen Möglichkeiten gibt und ein Wechsel in ein anderes Unternehmen nicht möglich ist.
Wahrscheinlich sind viele Leser/innen schon einmal einem Entwickler begegnet, der einfach nur ausharrt und vielleicht sogar aktiv nach einem leichteren Leben sucht. Gelangweilte Entwickler können einem Projekt jedoch auf verschiedene Weise schaden. Zum Beispiel können sie den Code komplizierter machen, als er eigentlich sein müsste, z. B. indem sie einen Sortieralgorithmus direkt in den Code schreiben, obwohl ein einfaches Collections.sort()
ausreichen würde. Sie könnten ihre Langeweile auch dadurch zum Ausdruck bringen, dass sie versuchen, Komponenten mit Technologien zu bauen, die sie nicht kennen oder die vielleicht nicht zum Anwendungsfall passen, nur um sie zu nutzen - was uns zum nächsten Abschnitt führt.
Auffüllen des Lebenslaufs
Manchmal hat die übermäßige Nutzung von Technologien nichts mit Langeweile zu tun, sondern damit, dass der Entwickler die Gelegenheit nutzt, seine Erfahrung mit einer bestimmten Technologie in seinem Lebenslauf aufzuwerten. In diesem Fall versucht der/die Entwickler/in aktiv, sein/ihr potenzielles Gehalt und seine/ihre Marktfähigkeit zu erhöhen, da er/sie kurz vor dem Wiedereinstieg in den Arbeitsmarkt steht. Es ist unwahrscheinlich, dass viele Leute in einem gut funktionierenden Team damit durchkommen, aber es kann trotzdem die Ursache für eine Entscheidung sein, die ein Projekt auf einen unnötigen Weg führt.
Die Folgen einer unnötigen Technologie, die aus Langeweile oder zur Aufbesserung des Lebenslaufs eines Entwicklers hinzugefügt wurde, können weitreichend und sehr langlebig sein und noch viele Jahre andauern, nachdem der ursprüngliche Entwickler sich auf grünere Weiden begeben hat.
Gruppenzwang
Technische Entscheidungen sind oft dann am schlimmsten, wenn Bedenken nicht geäußert oder diskutiert werden, wenn die Entscheidungen getroffen werden. Das kann sich auf verschiedene Weise äußern: Vielleicht will ein Nachwuchsentwickler vor den älteren Teammitgliedern keinen Fehler machen ("Impostersyndrom"), oder ein Entwickler fürchtet, bei einem bestimmten Thema als uninformiert dazustehen. Eine andere, besonders schädliche Form des Gruppendrucks besteht darin, dass konkurrierende Teams, die als sehr schnell in der Entwicklung gelten wollen, wichtige Entscheidungen überstürzen, ohne alle Konsequenzen zu bedenken.
Mangelndes Verständnis
Entwickler/innen versuchen oft, neue Tools einzuführen, um ein Problem zu lösen, weil sie sich der vollen Leistungsfähigkeit ihrer aktuellen Tools nicht bewusst sind. Es ist oft verlockend, sich einer neuen und aufregenden Technologiekomponente zuzuwenden, weil sie für eine bestimmte Aufgabe hervorragend geeignet ist. Bei der Einführung von mehr technischer Komplexität muss jedoch abgewogen werden, was die aktuellen Tools tatsächlich leisten können.
Zum Beispiel wird Hibernate manchmal als die Antwort auf die Vereinfachung der Übersetzung zwischen Domänenobjekten und Datenbanken gesehen. Wenn das Team nur ein begrenztes Verständnis von Hibernate hat, können die Entwickler Annahmen über die Eignung von Hibernate treffen, weil sie es in einem anderen Projekt eingesetzt haben.
Dieses mangelnde Verständnis kann zu einer überkomplizierten Nutzung von Hibernate und zu nicht wiederherstellbaren Produktionsausfällen führen. Wenn du dagegen die gesamte Datenschicht mit einfachen JDBC-Aufrufen umschreibst, bleibt der Entwickler auf vertrautem Terrain. Einer der Autoren unterrichtete einen Hibernate-Kurs, in dem sich ein Teilnehmer genau in dieser Situation befand. Er versuchte, genug Hibernate zu lernen, um zu sehen, ob die Anwendung wiederhergestellt werden kann, musste aber am Ende Hibernate im Laufe eines Wochenendes aus dem Verkehr ziehen - definitiv keine beneidenswerte Situation.
Missverstandenes/nicht existierendes Problem
Entwickler/innen setzen oft eine Technologie ein, um ein bestimmtes Problem zu lösen, ohne dass der Problemraum selbst ausreichend untersucht wurde. Ohne gemessene Leistungswerte ist es fast unmöglich, den Erfolg einer bestimmten Lösung zu verstehen. Das Zusammentragen dieser Leistungskennzahlen ermöglicht oft ein besseres Verständnis des Problems.
Um Antipatterns zu vermeiden, ist es wichtig, dass die Kommunikation über technische Fragen für alle Teilnehmer/innen im Team offen ist und aktiv gefördert wird. Wenn etwas unklar ist, können das Sammeln von Fakten und die Arbeit an Prototypen helfen, die Entscheidungen des Teams zu steuern. Eine Technologie mag attraktiv aussehen, aber wenn der Prototyp nicht den Anforderungen entspricht, kann das Team eine fundierte Entscheidung treffen.
Katalog der Antipatterns der Leistung
In diesem Abschnitt stellen wir einen kurzen Katalog von Antipatterns vor. Die Liste ist keineswegs vollständig, und es gibt zweifellos noch viele weitere, die entdeckt werden müssen.
Abgelenkt durch Glänzendes
Beschreibung
Die neueste oder coolste Technologie ist oft das erste Tuning-Ziel, da es spannender sein kann, zu erforschen, wie neuere Technologien funktionieren, als in altem Code herumzuwühlen. Es kann auch sein, dass der Code für die neuere Technologie besser geschrieben und einfacher zu warten ist. Beides führt dazu, dass sich Entwickler/innen mit den neueren Komponenten der Anwendung beschäftigen.
Beispielkommentar
"Es sind Kinderkrankheiten - wir müssen der Sache auf den Grund gehen."
Realität
-
Das ist oft nur ein Schuss ins Blaue und kein Versuch einer gezielten Abstimmung oder Messung der Anwendung.
-
Es kann sein, dass der Entwickler die neue Technologie noch nicht ganz versteht und eher herumprobiert, als sich mit der Dokumentation zu befassen - was in Wirklichkeit oft andere Probleme verursacht.
-
Bei neuen Technologien beziehen sich die Online-Beispiele oft auf kleine oder beispielhafte Datensätze und behandeln keine bewährten Verfahren für die Skalierung auf eine Unternehmensgröße.
Diskussion
Dieses Verhaltensmuster ist häufig bei neu gegründeten oder weniger erfahrenen Teams zu beobachten. Sie wollen sich beweisen oder vermeiden, dass sie an vermeintliche Altsysteme gebunden sind, und setzen sich daher oft für neuere, "heißere" Technologien ein - die zufälligerweise genau die Art von Technologien sind, die ihnen in einer neuen Position einen Gehaltsaufschwung bescheren würden.
Daher ist die logische unterbewusste Schlussfolgerung, dass jedes Leistungsproblem zuerst mit einem Blick auf die neue Technologie angegangen werden sollte. Schließlich hat man sie noch nicht richtig verstanden, also wäre ein neues Paar Augen hilfreich, oder?
Entschließungen
-
Messung, um den tatsächlichen Standort des Engpasses zu bestimmen.
-
Stelle sicher, dass das neue Bauteil ausreichend gesichert ist.
-
Sieh dir bewährte Methoden und vereinfachte Demos an.
-
Stelle sicher, dass das Team die neue Technologie versteht und im gesamten Team die bewährten Methoden einführt.
Abgelenkt durch Einfach
Beschreibung
Das Team nimmt zuerst die einfachsten Teile des Systems ins Visier, anstatt die Anwendung insgesamt zu profilieren und objektiv nach Schmerzpunkten zu suchen. Es kann Teile des Systems geben, die als "speziell" gelten und nur von dem Assistenten bearbeitet werden können, der sie ursprünglich geschrieben hat.
Beispiel Kommentare
"Fangen wir mit den Teilen an, die wir verstehen."
"John hat diesen Teil des Systems geschrieben und er ist im Urlaub. Lass uns warten, bis er zurück ist, um die Leistung zu sehen."
Realität
-
Der ursprüngliche Entwickler versteht es, (nur?) diesen Teil des Systems zu optimieren.
-
Es gab keinen Wissensaustausch oder Pair Programming für die verschiedenen Systemkomponenten, wodurch einzelne Experten entstanden sind.
Diskussion
Dieses Antipattern wird oft in einem etablierten Team beobachtet, das eher an eine Wartungs- oder "Keep-the-Lights-On"-Rolle gewöhnt ist. Wenn die Anwendung vor kurzem zusammengeführt oder mit neueren Technologien kombiniert wurde, kann es sein, dass das Team eingeschüchtert ist oder sich nicht mit den neuen Systemen beschäftigen will.
Unter diesen Umständen fühlen sich die Entwickler vielleicht wohler, wenn sie nur die Teile des Systems profilieren, die ihnen vertraut sind, in der Hoffnung, dass sie die gewünschten Ziele erreichen können, ohne ihre Komfortzone zu verlassen.
Besonders bemerkenswert ist, dass die ersten beiden Muster von einer Reaktion auf das Unbekannte angetrieben werden. Bei Distracted by Shiny äußert sich dies in dem Wunsch des Entwicklers (oder des Teams), mehr zu lernen und sich einen Vorteil zu verschaffen - im Grunde ein offensives Verhalten. Im Gegensatz dazu ist "Distracted by Simple" eine defensive Reaktion, bei der man sich auf das Vertraute konzentriert, anstatt sich auf eine potenziell bedrohliche neue Technologie einzulassen.
Entschließungen
-
Messung, um den tatsächlichen Standort des Engpasses zu bestimmen.
-
Bitte um Hilfe von Fachleuten, wenn das Problem bei einer unbekannten Komponente liegt.
-
Stelle sicher, dass die Entwickler alle Komponenten des Systems verstehen.
Assistent für Leistungsoptimierung
Beschreibung
Die Geschäftsleitung hat das Hollywood-Image des "einsamen Genies", des Hackers, übernommen und jemanden eingestellt, der diesem Klischee entspricht, um im Unternehmen herumzuziehen und alle Leistungsprobleme zu beheben, indem er seine vermeintlich überlegenen Fähigkeiten zur Leistungsoptimierung einsetzt.
Hinweis
Es gibt echte Experten und Unternehmen für Leistungsoptimierung, aber die meisten sind sich einig, dass man jedes Problem messen und untersuchen muss. Es ist unwahrscheinlich, dass die gleiche Lösung für alle Anwendungen einer bestimmten Technologie in allen Situationen gilt.
Beispielkommentar
"Ich bin mir sicher, dass ich genau weiß, wo das Problem liegt..."
Realität
-
Das Einzige, was ein vermeintlicher Zauberer oder Superheld wahrscheinlich tun wird, ist, die Kleiderordnung in Frage zu stellen.
Diskussion
Dieses Verhalten kann Entwickler im Team abschrecken, die glauben, dass sie nicht gut genug sind, um Leistungsprobleme zu lösen. Das ist besorgniserregend, denn in vielen Fällen kann eine kleine, vom Profiler gesteuerte Optimierung zu einer guten Leistungssteigerung führen (siehe Kapitel 13).
Das soll nicht heißen, dass es keine Spezialisten gibt, die bei bestimmten Technologien helfen können, aber der Gedanke, dass es ein einziges Genie gibt, das alle Leistungsprobleme von Anfang an versteht, ist absurd. Viele Technologen, die Leistungsexperten sind, sind Spezialisten für Messungen und Problemlösungen auf der Grundlage dieser Messungen.
Superhelden-Typen in Teams können sehr kontraproduktiv sein, wenn sie nicht bereit sind, ihr Wissen oder ihre Lösungsansätze für ein bestimmtes Problem mit anderen zu teilen.
Entschließungen
-
Messung, um den tatsächlichen Standort des Engpasses zu bestimmen.
-
Stelle sicher, dass alle Experten, die du in ein Team einstellst, bereit sind, sich mit anderen auszutauschen und als Teil des Teams zu agieren.
Stimmung durch Folklore
Beschreibung
Während ein Teammitglied verzweifelt versucht, eine Lösung für ein Leistungsproblem in der Produktion zu finden, findet es einen "magischen" Konfigurationsparameter auf einer Website. Ohne den Parameter zu testen, wendet das Team ihn in der Produktion an, denn er muss die Dinge genau so verbessern, wie er es für die Person im Internet getan hat...
Beispielkommentar
"Ich habe diese tollen Tipps auf Stack Overflow gefunden. Das ändert alles."
Realität
-
Der Entwickler versteht den Kontext oder die Grundlage des Leistungstipps nicht und die wahren Auswirkungen sind unbekannt.
-
Das mag in diesem speziellen System funktioniert haben, aber das bedeutet nicht, dass die Änderung auch in einem anderen System von Vorteil sein wird. In Wirklichkeit könnte sie alles noch schlimmer machen.
Diskussion
Ein Leistungstipp ist ein Workaround für ein bekanntes Problem - im Grunde eine Lösung, die ein Problem sucht. Leistungstipps haben eine begrenzte Lebensdauer und altern in der Regel schlecht; jemand wird eine Lösung finden, die den Tipp (bestenfalls) in einer späteren Version der Software oder Plattform unbrauchbar macht.
Eine Quelle für Leistungsempfehlungen, die in der Regel besonders schlecht ist, sind Verwaltungshandbücher. Sie enthalten allgemeine Ratschläge ohne jeglichen Kontext. Anwälte bestehen oft auf diesen vagen Ratschlägen und "empfohlenen Konfigurationen" als zusätzliche Verteidigungslinie, wenn der Anbieter verklagt wird.
Die Leistung von Java findet in einem bestimmten Kontext statt, zu dem eine Vielzahl von Faktoren beiträgt. Wenn wir diesen Kontext entfernen, ist es aufgrund der Komplexität der Ausführungsumgebung fast unmöglich, darüber nachzudenken, was übrig bleibt.
Hinweis
Die Java-Plattform wird außerdem ständig weiterentwickelt, was bedeutet, dass ein Parameter, der in einer Version von Java eine Leistungsumgehung darstellt, in einer anderen Version möglicherweise nicht mehr funktioniert.
Zum Beispiel ändern sich die Schalter zur Steuerung der Algorithmen für die Speicherbereinigung häufig zwischen den Versionen. Was in einer älteren VM (Version 7 oder 6) funktioniert, kann in der aktuellen Version (Java 8) nicht mehr angewendet werden. Es gibt sogar Schalter, die in Version 7 gültig und nützlich sind, aber dazu führen, dass die VM in der kommenden Version 9 nicht mehr startet.
Die Konfiguration kann eine ein- oder zweistellige Änderung sein, aber in einer Produktionsumgebung erhebliche Auswirkungen haben, wenn sie nicht sorgfältig verwaltet wird.
Entschließungen
-
Wende nur gut getestete und gut verstandene Techniken an, die sich direkt auf die wichtigsten Aspekte des Systems auswirken.
-
Suche nach Parametern und probiere sie im UAT aus, aber wie bei jeder Änderung ist es wichtig, den Nutzen zu beweisen und zu profilieren.
-
Überprüfe und bespreche die Konfiguration mit anderen Entwicklern und Betriebsmitarbeitern oder Devops.
Der Esel der Schuld
Beschreibung
Bestimmte Komponenten werden immer als das Problem identifiziert, auch wenn sie nichts mit dem Problem zu tun haben.
Einer der Autoren erlebte zum Beispiel einen massiven Ausfall im UAT am Tag vor dem Go-Live. Ein bestimmter Pfad durch den Code verursachte eine Tabellensperre in einer der zentralen Datenbanktabellen. Durch einen Fehler im Code blieb die Sperre erhalten und machte den Rest der Anwendung unbrauchbar, bis ein vollständiger Neustart durchgeführt wurde. Hibernate wurde als Datenzugriffsschicht verwendet und sofort für das Problem verantwortlich gemacht. In diesem Fall war der Schuldige jedoch nicht Hibernate, sondern ein leerer catch
Block für die Timeout-Ausnahme, der die Datenbankverbindung nicht bereinigt hat. Es dauerte einen ganzen Tag, bis die Entwickler aufhörten, Hibernate die Schuld zu geben, und sich ihren Code ansahen, um den wahren Fehler zu finden.
Beispielkommentar
"Es ist immer JMS/Hibernate/A_N_OTHER_LIB."
Realität
-
Für diese Schlussfolgerung wurde eine unzureichende Analyse durchgeführt.
-
Der übliche Verdächtige ist der einzige Verdächtige bei den Ermittlungen.
-
Das Team ist nicht bereit, weiter zu schauen, um die wahre Ursache zu finden.
Diskussion
Dieses Antipattern wird oft von der Geschäftsleitung oder dem Unternehmen an den Tag gelegt, da sie in vielen Fällen den technischen Stack nicht vollständig verstehen und kognitive Voreingenommenheiten haben, so dass sie nach dem Pattern Matching vorgehen. Aber auch Technologen sind davor nicht gefeit.
Technologen fallen diesem Muster oft zum Opfer, wenn sie nur wenig über die Codebasis oder die Bibliotheken wissen, die nicht für das Problem verantwortlich sind. Es ist oft einfacher, einen Teil der Anwendung zu benennen, der üblicherweise das Problem ist, als eine neue Untersuchung durchzuführen. Das kann ein Zeichen für ein müdes Team sein, das mit vielen Produktionsproblemen zu kämpfen hat.
Hibernate ist das perfekte Beispiel dafür. In vielen Situationen wird Hibernate so weit ausgebaut, dass es nicht mehr richtig eingerichtet oder verwendet wird. Das Team neigt dann dazu, die Technologie zu verdammen, weil sie in der Vergangenheit fehlgeschlagen ist oder nicht funktioniert hat. Das Problem könnte aber genauso gut die zugrunde liegende Abfrage, die Verwendung eines ungeeigneten Index, die physische Verbindung zur Datenbank, die Objektzuordnungsschicht oder etwas anderes sein. Ein Profiling zur Eingrenzung der genauen Ursache ist unerlässlich.
Entschließungen
-
Widerstehe dem Druck, voreilige Schlüsse zu ziehen.
-
Führe die Analyse wie gewohnt durch.
-
Kommuniziere die Ergebnisse der Analyse an alle Beteiligten (um ein genaueres Bild von den Ursachen der Probleme zu erhalten).
Das große Ganze übersehen
Beschreibung
Das Team ist wie besessen davon, Änderungen auszuprobieren oder kleinere Teile der Anwendung zu profilieren, ohne die vollen Auswirkungen der Änderungen zu erkennen. Die Ingenieure fangen an, die JVM-Switches zu verändern, um eine bessere Leistung zu erzielen, vielleicht auf der Grundlage eines Beispiels oder einer anderen Anwendung im selben Unternehmen.
Das Team kann auch versuchen, kleinere Teile der Anwendung mit Hilfe von Microbenchmarking zu profilieren (was bekanntermaßen schwer zu bewerkstelligen ist, wie wir in Kapitel 5 untersuchen werden).
Beispiel Kommentare
"Wenn ich diese Einstellungen ändere, wird die Leistung besser."
"Wenn wir nur die Versandzeit der Methode beschleunigen könnten..."
Realität
-
Das Team versteht die Auswirkungen der Veränderungen nicht vollständig.
-
Das Team hat die Anwendung nicht vollständig unter den neuen JVM-Einstellungen profiliert.
-
Die Auswirkungen eines Mikrobenchmarks auf das Gesamtsystem wurden nicht ermittelt.
Diskussion
Die JVM hat buchstäblich Hunderte von Schaltern. Das führt zu einer sehr konfigurierbaren Laufzeit, aber auch zu einer großen Versuchung, all diese Konfigurationsmöglichkeiten auszunutzen. Das ist in der Regel ein Fehler - die Standardeinstellungen und die Selbstverwaltungsfunktionen sind normalerweise ausreichend. Einige der Schalter kombinieren sich außerdem auf unerwartete Weise miteinander, was blinde Änderungen noch gefährlicher macht. Selbst in ein und demselben Unternehmen werden die Anwendungen wahrscheinlich ganz anders funktionieren und ein anderes Profil aufweisen. Deshalb ist es wichtig, dass du dir die Zeit nimmst, die empfohlenen Einstellungen auszuprobieren.
Leistungsoptimierung ist eine statistische Tätigkeit, die auf einen sehr spezifischen Kontext für die Ausführung angewiesen ist. Das bedeutet, dass größere Systeme in der Regel einfacher zu benchmarken sind als kleinere - denn bei größeren Systemen wirkt sich das Gesetz der großen Zahlen zu Gunsten des Ingenieurs aus und hilft dabei, Effekte in der Plattform zu korrigieren, die einzelne Ereignisse verzerren.
Je mehr wir hingegen versuchen, uns auf einen einzelnen Aspekt des Systems zu konzentrieren, desto härter müssen wir arbeiten, um die einzelnen Subsysteme (z. B. Threading, GC, Scheduling, JIT-Kompilierung) der komplexen Umgebung, die die Plattform ausmacht, zu entflechten (zumindest im Fall von Java/C#). Das ist extrem schwierig, und der Umgang mit Statistiken ist heikel und gehört nicht zu den Fähigkeiten, die sich Softwareentwickler/innen im Laufe der Zeit angeeignet haben. So kommt es leicht zu Zahlen, die das Verhalten des Systemaspekts, von dem der Ingenieur dachte, er würde ihn bewerten, nicht genau wiedergeben.
Dies hat leider die Tendenz, sich mit der menschlichen Neigung zu verbinden, Muster zu erkennen, auch wenn es keine gibt. Zusammen führen diese Effekte dazu, dass wir einen Leistungsingenieur sehen, der sich von einer schlechten Statistik oder einer schlechten Kontrolle hat verführen lassen - einen Ingenieur, der leidenschaftlich für eine Leistungskennzahl oder einen Effekt argumentiert, den seine Kollegen einfach nicht wiederholen können.
Es gibt noch ein paar andere Punkte, die du beachten solltest. Erstens ist es schwierig, die Effektivität von Optimierungen ohne eine UAT-Umgebung zu bewerten, die die Produktionsumgebung vollständig nachbildet. Zweitens macht es keinen Sinn, eine Optimierung zu haben, die deiner Anwendung nur in Stresssituationen hilft und im allgemeinen Fall die Leistung beeinträchtigt - aber es kann schwierig sein, Datensätze zu erhalten, die typisch für die allgemeine Anwendungsnutzung sind und gleichzeitig einen aussagekräftigen Test unter Last ermöglichen.
Resolutionen
Bevor du eine Änderung an den Schaltern vornimmst:
-
Maßnahme in der Produktion.
-
Ändere einen Schalter nach dem anderen in UAT.
-
Stelle sicher, dass deine UAT-Umgebung die gleichen Stresspunkte aufweist wie die Produktionsumgebung.
-
Stelle sicher, dass Testdaten verfügbar sind, die der normalen Belastung des Produktionssystems entsprechen.
-
Teste die Änderung im UAT.
-
Teste erneut im UAT.
-
Lass jemanden deine Argumentation noch einmal überprüfen.
-
Diskutiere mit ihnen über deine Schlussfolgerungen.
UAT ist mein Schreibtisch
Beschreibung
UAT-Umgebungen unterscheiden sich oft erheblich von Produktionsumgebungen, wenn auch nicht immer in einer Weise, die erwartet oder verstanden wird. Viele Entwickler/innen haben schon in Situationen gearbeitet, in denen ein leistungsschwacher Desktop verwendet wurde, um Code für leistungsstarke Produktionsserver zu schreiben. Es kommt aber auch immer häufiger vor, dass der Rechner eines Entwicklers wesentlich leistungsstärker ist als die kleinen Server, die in der Produktion eingesetzt werden. Leistungsschwache Mikroumgebungen sind in der Regel kein Problem, da sie oft virtualisiert werden können, so dass ein Entwickler jeweils eine davon hat. Das gilt nicht für leistungsstarke Produktionsrechner, die oft deutlich mehr Kerne, Arbeitsspeicher und effiziente E/A haben als der Rechner eines Entwicklers.
Beispiel Kommentar
"Eine UAT-Umgebung in voller Größe wäre zu teuer."
Realität
-
Ausfälle, die durch unterschiedliche Umgebungen verursacht werden, sind fast immer teurer als ein paar Boxen mehr.
Diskussion
Das UAT-Ist-Mein-Schreibtisch-Antipattern beruht auf einer anderen Art von kognitiver Voreingenommenheit, als wir sie bisher kennengelernt haben. Diese Voreingenommenheit besteht darin, dass eine Art von UAT besser sein muss als gar keine. Leider verkennt diese Hoffnung die komplexe Natur von Unternehmensumgebungen. Damit eine sinnvolle Extrapolation möglich ist, muss die UAT-Umgebung der Produktionsumgebung entsprechen.
In modernen adaptiven Umgebungen werden die Laufzeit-Subsysteme die verfügbaren Ressourcen bestmöglich nutzen. Wenn sich diese radikal von denen der Zielumgebung unterscheiden, werden sie unter den unterschiedlichen Umständen andere Entscheidungen treffen - was unsere hoffnungsvolle Extrapolation bestenfalls nutzlos macht.
Entschließungen
-
Verfolge die Kosten für Ausfälle und Opportunitätskosten, die durch verlorene Kunden entstehen.
-
Kaufe eine UAT-Umgebung, die mit der Produktionsumgebung identisch ist.
-
In den meisten Fällen überwiegen die Kosten der ersten Variante bei weitem die der zweiten, und manchmal muss man den Managern die richtigen Argumente liefern.
Produktionsnahe Daten sind schwierig
Beschreibung
Dieses auch als DataLite-Antipattern bekannte Verhaltensmuster bezieht sich auf einige häufige Fallstricke, auf die man stößt, wenn man versucht, produktionsähnliche Daten darzustellen. Stell dir eine Handelsabwicklungsanlage bei einer großen Bank vor, die Futures- und Optionsgeschäfte abwickelt, die bereits gebucht wurden, aber noch abgewickelt werden müssen. Ein solches System würde normalerweise Millionen von Nachrichten pro Tag verarbeiten. Betrachte nun die folgenden UAT-Strategien und ihre möglichen Probleme:
-
Um das Testen zu vereinfachen, wird eine kleine Auswahl dieser Nachrichten im Laufe des Tages aufgezeichnet. Die Nachrichten werden dann alle durch das UAT-System laufen gelassen.
Dieser Ansatz schlägt fehl, um das Burst-Verhalten zu erfassen, das das System sehen könnte. Außerdem wird möglicherweise die Aufwärmphase nicht erfasst, die dadurch entsteht, dass an einem bestimmten Markt mehr Futures gehandelt werden, bevor ein anderer Markt öffnet, an dem Optionen gehandelt werden.
-
Damit das Szenario leichter zu testen ist, werden die Gewerke und Optionen so aktualisiert, dass nur einfache Werte für die Behauptung verwendet werden.
Wenn man bedenkt, dass wir eine externe Bibliothek oder ein externes System für die Preisfindung von Optionen verwenden, können wir mit unserem UAT-Datensatz nicht feststellen, dass diese Produktionsabhängigkeit kein Leistungsproblem verursacht hat, da die von uns durchgeführten Berechnungen eine vereinfachte Teilmenge der Produktionsdaten sind.
-
Um die Sache zu vereinfachen, werden alle Werte auf einmal durch das System gejagt.
Das wird oft in der UAT gemacht, aber dabei werden wichtige Aufwärm- und Optimierungsprozesse verpasst, die stattfinden können, wenn die Daten mit einer anderen Geschwindigkeit eingespeist werden.
Meistens wird bei UAT der Testdatensatz vereinfacht, um die Dinge zu erleichtern. Das macht die Ergebnisse aber nur selten nützlich.
Beispiel Kommentare
"Es ist zu schwer, Produktion und UAT auf dem gleichen Stand zu halten."
"Es ist zu schwierig, Daten so zu manipulieren, dass sie mit dem übereinstimmen, was das System erwartet.
"Produktionsdaten sind durch Sicherheitsaspekte geschützt. Entwickler sollten keinen Zugang zu ihnen haben."
Realität
Die Daten im UAT müssen produktionsähnlich sein, um genaue Ergebnisse zu erzielen. Wenn Daten aus Sicherheitsgründen nicht verfügbar sind, sollten sie verschlüsselt (maskiert oder verschleiert) werden, damit sie trotzdem für einen aussagekräftigen Test verwendet werden können. Eine andere Möglichkeit ist es, die UAT so zu partitionieren, dass die Entwickler die Daten nicht sehen, aber die Ergebnisse der Leistungstests sehen können, um Probleme zu erkennen.
Diskussion
Auch hier gilt: "Irgendetwas ist besser als gar nichts". Die Idee ist, dass es besser ist, auch mit veralteten und nicht repräsentativen Daten zu testen, als gar nicht zu testen.
Wie bereits erwähnt, ist dies eine extrem gefährliche Argumentation. Zwar können Tests im großen Maßstab (auch wenn sie nicht mit Produktionsdaten vergleichbar sind) Fehler und Lücken in den Systemtests aufdecken, aber sie vermitteln ein falsches Gefühl von Sicherheit.
Wenn das System in Betrieb genommen wird und die Nutzungsmuster nicht mit den erwarteten Normen übereinstimmen, die durch die UAT-Daten verankert wurden, kann es gut sein, dass die Entwicklungs- und Betriebsteams aufgrund des warmen Scheins, den die UAT geliefert hat, selbstgefällig geworden sind und nicht auf den schieren Schrecken vorbereitet sind, der schnell auf eine Produktionsfreigabe im großen Maßstab folgen kann.
Entschließungen
Kognitive Verzerrungen und Leistungstests
Menschen sind schlecht darin, sich schnell eine genaue Meinung zu bilden, selbst wenn sie vor einem Problem stehen, bei dem sie auf frühere Erfahrungen und ähnliche Situationen zurückgreifen können.
Hinweis
Eine kognitive Voreingenommenheit ist ein psychologischer Effekt, der das menschliche Gehirn dazu bringt, falsche Schlüsse zu ziehen. Das ist besonders problematisch, weil die Person, die die Voreingenommenheit zeigt, sich dessen in der Regel nicht bewusst ist und vielleicht glaubt, dass sie rational handelt.
Viele der in diesem Kapitel untersuchten Antipatterns sind ganz oder teilweise auf eine oder mehrere kognitive Voreingenommenheit zurückzuführen, die wiederum auf unbewussten Annahmen beruht.
Wenn zum Beispiel eine Komponente in letzter Zeit mehrere Ausfälle verursacht hat, kann es sein, dass das Team davon ausgeht, dass dieselbe Komponente die Ursache für jedes neue Leistungsproblem ist. Alle Daten, die analysiert werden, werden eher als glaubwürdig angesehen, wenn sie die Annahme bestätigen, dass der "Blame Donkey" verantwortlich ist. Dieses Verhaltensmuster kombiniert Aspekte der Voreingenommenheit, die als Confirmation Bias und Recency Bias bekannt sind (die Tendenz, davon auszugehen, dass alles, was in letzter Zeit passiert ist, auch weiterhin passieren wird).
Hinweis
Eine einzelne Komponente in Java kann sich von Anwendung zu Anwendung unterschiedlich verhalten, je nachdem, wie sie zur Laufzeit optimiert wird. Um bereits vorhandene Verzerrungen zu beseitigen, ist es wichtig, die Anwendung als Ganzes zu betrachten.
Vorurteile können sich gegenseitig ergänzen oder ergänzen. Manche Entwickler gehen zum Beispiel davon aus, dass das Problem nichts mit der Software zu tun hat und die Ursache in der Infrastruktur zu suchen ist, auf der die Software läuft. Das ist häufig der Fall bei dem "Works for Me"-Antipattern, das sich durch Aussagen wie "Das hat im UAT gut funktioniert, also muss es ein Problem mit dem Produktionskit geben. Das Gegenteil ist die Annahme, dass jedes Problem durch die Software verursacht werden muss, weil dies der Teil des Systems ist, den der Entwickler kennt und direkt beeinflussen kann.
Reduktionistisches Denken
Diese kognitive Voreingenommenheit basiert auf einem analytischen Ansatz, der davon ausgeht, dass man ein System verstehen kann, wenn man es in genügend kleine Teile zerlegt. Jedes Teil zu verstehen bedeutet, die Wahrscheinlichkeit falscher Annahmen zu verringern.
Das Problem mit dieser Ansicht ist, dass sie in komplexen Systemen einfach nicht stimmt. Nicht-triviale Software- (oder physikalische) Systeme zeigen fast immer ein emergentes Verhalten, bei dem das Ganze größer ist, als es die einfache Addition seiner Teile vermuten lässt.
Bestätigungsvorurteil
Confirmation Bias kann zu erheblichen Problemen führen, wenn es um Leistungstests oder den Versuch geht, eine Anwendung subjektiv zu betrachten. Ein Confirmation Bias wird - meist unabsichtlich - eingeführt, wenn ein schlechtes Testset ausgewählt wird oder die Testergebnisse nicht statistisch fundiert analysiert werden. Bestätigungsfehler sind schwer zu bekämpfen, weil oft starke motivationale oder emotionale Faktoren im Spiel sind (z. B. wenn jemand im Team versucht, einen Standpunkt zu beweisen).
Nehmen wir ein Antipattern wie Distracted by Shiny, bei dem ein Teammitglied die neueste und beste NoSQL-Datenbank einführen möchte. Sie führen einige Tests mit Daten durch, die nicht mit den Produktionsdaten übereinstimmen, weil die Darstellung des vollständigen Schemas zu kompliziert für die Auswertung ist. Sie stellen schnell fest, dass die NoSQL-Datenbank auf ihrem lokalen Rechner bessere Zugriffszeiten liefert. Der Entwickler hat allen gesagt, dass dies der Fall sein würde, und nachdem er die Ergebnisse gesehen hat, fährt er mit einer vollständigen Implementierung fort. Hier sind mehrere Gegenmuster am Werk, die alle zu neuen, unbewiesenen Annahmen im neuen Bibliotheksstapel führen.
Nebel des Krieges (Action Bias)
Diese Verzerrung zeigt sich normalerweise bei Ausfällen oder in Situationen, in denen das System nicht wie erwartet funktioniert. Zu den häufigsten Ursachen gehören:
-
Änderungen an der Infrastruktur, auf der das System läuft, vielleicht ohne Benachrichtigung oder ohne zu wissen, dass dies Auswirkungen haben würde
-
Änderungen an Bibliotheken, von denen das System abhängig ist
-
Ein seltsamer Fehler oder eine Rennsituation, die sich am geschäftigsten Tag des Jahres manifestiert
In einer gut gewarteten Anwendung mit ausreichender Protokollierung und Überwachung sollten diese eindeutige Fehlermeldungen erzeugen, die das Support-Team zur Ursache des Problems führen.
Allerdings haben zu viele Anwendungen keine Fehlerszenarien getestet und es fehlt eine angemessene Protokollierung. Unter diesen Umständen können selbst erfahrene Ingenieure in die Falle tappen, weil sie das Gefühl haben müssen, dass sie etwas tun, um den Ausfall zu beheben, und Bewegung mit Geschwindigkeit verwechseln - der "Nebel des Krieges" setzt ein.
Zu diesem Zeitpunkt können viele der in diesem Kapitel besprochenen menschlichen Elemente ins Spiel kommen, wenn die Beteiligten nicht systematisch an das Problem herangehen. Zum Beispiel kann ein Antipattern wie der "Blame Donkey" eine vollständige Untersuchung abkürzen und das Produktionsteam auf einen bestimmten Untersuchungspfad führen - und dabei oft das Gesamtbild übersehen. Ebenso kann das Team versucht sein, das System in seine Bestandteile zu zerlegen und den Code auf einer niedrigen Ebene zu untersuchen, ohne zuerst festzustellen, in welchem Teilsystem das Problem wirklich liegt.
In der Vergangenheit hat es sich vielleicht immer ausgezahlt, systematisch mit Ausfallszenarien umzugehen und alles, was nicht gepatcht werden musste, einem Postmortem zu überlassen. Dies ist jedoch das Reich der menschlichen Emotionen, und es kann sehr schwierig sein, die Spannung aus der Situation zu nehmen, besonders während eines Ausfalls.
Risikovorurteile
Menschen sind von Natur aus risikoscheu und resistent gegen Veränderungen. Meistens liegt das daran, dass die Menschen Beispiele dafür gesehen haben, wie Veränderungen schief gehen können. Das führt dazu, dass sie versuchen, das Risiko zu vermeiden. Das kann unglaublich frustrierend sein, wenn das Eingehen kleiner, kalkulierter Risiken das Produkt voranbringen könnte. Wir können die Risikobereitschaft erheblich reduzieren, indem wir robuste Einheitstests und Regressionstests für die Produktion durchführen. Wenn einem von beiden nicht vertraut wird, werden Änderungen extrem schwierig und der Risikofaktor wird nicht kontrolliert.
Diese Voreingenommenheit äußert sich oft darin, dass aus Anwendungsproblemen (sogar aus Serviceausfällen) keine Lehren gezogen werden und keine geeigneten Abhilfemaßnahmen ergriffen werden.
Ellsbergs Paradoxon
Ein Beispiel dafür, wie schlecht der Mensch die Wahrscheinlichkeitsrechnung versteht, ist das Ellsberg-Paradoxon. Benannt nach dem berühmten US-amerikanischen Enthüllungsjournalisten und Whistleblower Daniel Ellsberg, geht es bei diesem Paradoxon um den menschlichen Wunsch nach "bekannten Unbekannten" gegenüber "unbekannten Unbekannten".2
Die übliche Formulierung des Ellsberg-Paradoxons ist ein einfaches Wahrscheinlichkeitsgedankenexperiment. Stell dir eine Tonne vor, die 90 farbige Kugeln enthält - 30 sind bekanntlich blau, der Rest ist entweder rot oder grün. Die genaue Verteilung der roten und grünen Kugeln ist nicht bekannt, aber die Tonne, die Kugeln und damit die Wahrscheinlichkeiten sind durchgehend festgelegt.
Der erste Schritt des Paradoxons wird als eine Auswahl von Einsätzen ausgedrückt. Der Spieler kann sich für eine von zwei Wetten entscheiden:
-
Der Spieler gewinnt $100, wenn eine zufällig gezogene Kugel blau ist.
-
Der Spieler gewinnt 100 $, wenn eine zufällig gezogene Kugel rot ist.
Die meisten Menschen entscheiden sich für A), da die Wahrscheinlichkeit, zu gewinnen, genau 1/3 beträgt. Wenn der Spieler jedoch eine zweite Wette abgibt, passiert etwas Überraschendes (unter der Annahme, dass eine entfernte Kugel wieder in die gleiche Tonne gelegt wird und dann erneut zufällig ausgewählt wird). In diesem Fall sind die Optionen:
-
Der Spieler gewinnt 100 $, wenn eine zufällig gezogene Kugel blau oder grün ist.
-
Der Spieler gewinnt 100 $, wenn eine zufällig gezogene Kugel rot oder grün ist.
In dieser Situation entspricht die Wette D den bekannten Quoten (2/3 Gewinnchance), also wählt praktisch jeder diese Option.
Das Paradoxon besteht darin, dass die Menge der Möglichkeiten A und D irrational ist. Wenn du A wählst, drückst du implizit eine Meinung über die Verteilung der roten und grünen Kugeln aus, nämlich dass es mehr grüne als rote Kugeln gibt. Wenn du A wählst, ist die logische Strategie daher, es mit C zu kombinieren, da dies bessere Chancen bietet als die sichere Wahl von D.
Zusammenfassung
Wenn du Leistungsergebnisse auswertest, ist es wichtig, die Daten angemessen zu behandeln und zu vermeiden, dass du in unwissenschaftliches und subjektives Denken verfällst. In diesem Kapitel haben wir uns mit einigen Testarten, bewährten Methoden und Gegenmustern beschäftigt, die für die Leistungsanalyse typisch sind.
Im nächsten Kapitel befassen wir uns mit Low-Level-Leistungsmessungen, den Fallstricken von Mikrobenchmarks und einigen statistischen Techniken für den Umgang mit Rohdaten aus JVM-Messungen.
Get Java optimieren now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.