Testdaten zurücksetzen, Runtime/Cache stabilisieren, Dashboard-Zeitfenster und Run-ID setzen.
LPT Project
Setup Roadmap
Ein neues Load & Performance Testing (LPT) Projekt beginnt nicht mit einem Skript. Dieser chronologische Fahrplan führt von überprüfbaren Qualitätszielen über Systemkenntnis und Testdaten bis zu reproduzierbaren Läufen, CI-Gates und entscheidungsfähigen Reports.
Anforderungsanalyse
Leistung ist nur dann messbar, wenn Stakeholder zuerst vereinbaren, welche Nutzerergebnisse wichtig sind, unter welcher Last sie gelten und was als bestanden zählt. Ziele als überprüfbare Hypothesen festhalten, nicht als „das System muss schnell sein“.
Non-Functional Requirements in Messwerte übersetzen
Non-Functional Requirements (NFRs, nichtfunktionale Anforderungen) beschreiben Eigenschaften wie Antwortzeit, Kapazität, Verfügbarkeit und Stabilität. Für jeden kritischen Pfad Messgrenze, Population, Zeitfenster, Lastprofil und Zielwert definieren. Beispiel: „Checkout-P95 unter 400 ms“ ist ohne Client-/Server-Grenze, Fehlerbehandlung und Mindestdurchsatz noch unvollständig.
SLI, SLO und SLA auseinanderhalten
Ein Service Level Indicator (SLI) ist die gemessene Nutzerwirkung. Ein Service Level Objective (SLO) ist das interne Ziel samt Fenster. Ein Service Level Agreement (SLA) ist eine externe Zusage mit vertraglichen Folgen. Performance-Tests verifizieren vereinbarte Betriebsziele; ein SLA-Wert allein sagt noch nicht, welche Last oder Testmethode ihn belegt.
| ANFORDERUNG | MESSGRENZE / POPULATION | LASTBEDINGUNG | BEISPIEL-ZIEL |
|---|---|---|---|
| Latenz | Clientseitig, erfolgreich abgeschlossene Requests; pro User Journey | Steady State bei definierter Rate und Datengröße | P95 ≤ 400 ms, P99 ≤ 900 ms |
| Kapazität | Erfolgreiche fachliche Transaktionen pro Sekunde | Fehlerrate innerhalb Budget, keine wachsende Queue | ≥ 120 Checkout/s für 20 min |
| Fehler | Fachliche und technische Fehler geteilt durch alle Versuche | Nach Warm-up, pro Transaktion ausweisen | < 0,5 %, keine unerwarteten 5xx |
| Stabilität | Fehlerrate, Heap, Queue und Durchsatz über Zeit | Endurance-Lauf mit realistischem Tagesprofil | Kein ungebremstes Wachstum über 4 h |
System- & Architektur-Analyse
Verstehe den System under Test (SuT) als Ende-zu-Ende-Pfad. Ein Lastgenerator sendet an einen Einstiegspunkt, aber die Antwortzeit kann durch Gateway, Service-Pools, Cache, Datenbank, Queue oder externe Provider geprägt sein.
Grenzen und Abhängigkeiten inventarisieren
OpenAPI-/AsyncAPI-Spezifikationen, Sequenzdiagramme, Architekturentscheidungen und Betriebsdashboards als Startpunkt nutzen und mit Service-Teams validieren. Für jeden Schritt Latenzbudget, Timeout, Retry-Verhalten, Poolgröße und beobachtbare Sättigung erfassen. Synchrone Aufrufe, asynchrone Queues und Hintergrundjobs getrennt modellieren.
Cache und Datenbank nicht ausblenden
Cache-Hit-Rate, TTL, Warm-up und Invalidierung beeinflussen die Last, die bis zur Datenbank durchdringt. Datenbank-Connection-Pools, Query-Mix, Indizes, Locking und Datensatzgröße können den Engpass bestimmen. Festlegen, ob ein Test kalte oder warme Caches abbildet und wie Datenbankzustand zwischen Läufen zurückgesetzt wird.
| KOMPONENTE | FRAGEN FÜR DIE ANALYSE | BEOBACHTBARE SIGNALE | RISIKO FÜR DAS MODELL |
|---|---|---|---|
| API / Gateway | Rate-Limits, Auth, Payload-Limits, Keep-alive? | Request-Rate, 429/5xx, Upstream-Pool-Wartezeit | Test trifft Gateway-Limit statt SuT-Kapazität |
| Service / Cache | Sessionzustand, Hit Rate, TTL, Warm-up? | RED-Metriken, Pool-Wait, Hit/Miss, Evictions | Unrealistische Cache-Verteilung unterschätzt Backend-Last |
| Datenbank | Query-Mix, Datenvolumen, Isolation, Seed/Reset? | Connections, Locks, IOPS, Slow Queries, Buffer Reads | Leere Testdaten erzeugen andere Pläne als Produktion |
| Provider / Queue | Sandbox, Quoten, Latenzverteilung, Consumer-Lag? | Timeouts, Retry-Rate, Queue-Alter, Durchsatz | Externer Engpass oder Retry-Amplification verfälscht Resultat |
Template: Systemgrenze und Abhängigkeitsinventar
System under Test:
Business Owner / Tech Owner:
Entry point(s) and protocol:
Included services and versions:
Excluded dependencies and simulation method:
Journey: [name]
Client start/end boundary:
Synchronous calls:
Async events / queues:
Cache state and expected hit ratio:
Database entities, volume, and query patterns:
Timeouts, retries, rate limits:
Metrics, logs, traces, dashboard links:
Known capacity limits / maintenance windows:
Unverified assumptions / owner / due date:Testdaten & Testumgebung aufbauen
Ein realistischer Lasttest braucht sowohl eine kontrollierte Umgebung als auch repräsentative, sichere Daten. Ohne reproduzierbaren Ausgangszustand lassen sich zwei Ergebnisse nicht sinnvoll vergleichen.
Datenstrategie vor dem Scripting
Für jede Journey benötigte Entitäten, Verteilung, Kardinalität und Lebenszyklus festlegen. Synthetische Daten bevorzugen; falls Produktionsdaten erforderlich sind, genehmigt anonymisieren und Zugriffe beschränken. Pro virtueller Nutzer eindeutige Konten oder Datensätze bereitstellen, wenn konkurrierende Updates sonst künstliche Locks oder fachliche Konflikte erzeugen.
Umgebung reproduzierbar provisionieren
Build, Konfiguration, Datenbankgröße, Feature Flags, Limits, Autoscaling und Abhängigkeiten versionieren. NTP-Zeitsynchronisierung prüfen, Generator und Zielmetriken zeitlich abgleichen und Lastgenerator-Kapazität separat überwachen. Abweichungen zur Produktion als Limitierung im Bericht dokumentieren.
Scripting & Parametrisierung
Das Skript ist die ausführbare Spezifikation des Workloads. Modelliert werden fachliche Journeys, Transaktionsmix, Ankunftsrate, Think Time, Testdaten, Assertions und Abbruchverhalten.
Tool dem Testziel zuordnen
k6 eignet sich für versionierte API-Workloads und direkte Thresholds als Code. JMeter bietet breite Protokollunterstützung und visuelle Testplanmodellierung; GUI zum Entwurf, CLI als reproduzierbare Ausführung. Beide brauchen einen dimensionierten Generator und kleine Smoke-Läufe vor jeder Steigerung.
Parametrisierung kontrollieren
URLs, Nutzer, IDs, Datenmengen und Rate über Umgebungsvariablen oder CSV injizieren. Geheimnisse aus einem Secret Store beziehen. Labels mit niedriger Kardinalität verwenden: Transaktionsname und Service statt Nutzer-ID. Assertions prüfen fachliche Korrektheit, Thresholds entscheiden über den Exitcode.
k6-Vorlage: konfigurierbare Rate und SLO-Thresholds
import http from 'k6/http';
import { check } from 'k6';
const baseUrl = __ENV.BASE_URL;
const token = __ENV.TEST_TOKEN;
export const options = {
scenarios: {
checkout: {
executor: 'constant-arrival-rate',
rate: Number(__ENV.RATE || 10),
timeUnit: '1s',
duration: __ENV.DURATION || '3m',
preAllocatedVUs: 20,
maxVUs: 80,
},
},
thresholds: {
http_req_failed: ['rate<0.005'],
http_req_duration: ['p(95)<400', 'p(99)<900'],
dropped_iterations: ['count==0'],
},
};
export default function () {
const response = http.post(`${baseUrl}/api/checkout`,
JSON.stringify({ sku: __ENV.SKU, quantity: 1 }),
{ headers: { Authorization: `Bearer ${token}`,
'Content-Type': 'application/json' }, tags: { name: 'checkout' } });
check(response, { 'created order': (r) => r.status === 201 });
}Vor dem Lauf SKU, Rate und Token validieren. Ein Arrival-Rate-Szenario hält angebotene Last stabiler als eine feste VU-Zahl, benötigt aber ausreichend vorallokierte VUs. Dropped iterations weisen auf einen überlasteten Generator oder zu niedrige VU-Kapazität hin.
Test-Ausführung, CI/CD & Reporting
Ein Lauf ist ein kontrolliertes Experiment: Versionen, Daten, Umgebung und Zeitfenster festhalten, schrittweise Last anlegen und gleichzeitig Zielsystem sowie Generator beobachten. Ergebnis ist eine wiederholbare Entscheidung, nicht nur ein JTL-File.
Smoke → Ramp-up → Steady State → Ramp-down; vorab definierte Stop-Kriterien aktiv beobachten.
JTL, Skript-Commit, Build, Parameter, Logs und Messfenster gemeinsam versioniert archivieren.
SLI-Budgets und Baseline vergleichen; Abweichungen nach Nutzerwirkung, Ursache, Risiko und nächstem Schritt berichten.
Praxisbeispiel: JMeter-JTL filtern, in eine Datenbank schreiben und Grafana dokumentieren
Automatisierungsablauf
JMeter im Non-GUI-Modus mit CSV-JTL ausführen. Ein `LPT_RUN_ID` wird in JMeter, Metriklabels und Dashboard-Annotation verwendet. Ein Python-Schritt liest nur ausgewählte Transaktionslabels, normalisiert Zeitstempel und Dauer und schreibt Rohsamples in eine Ergebnisdatenbank. Danach wird derselbe Run-Zeitraum über Grafanas Image Renderer als PNG gerendert. JTL, DB-Datensatz, Report und Screenshot erhalten denselben Run-Identifier.
mkdir -p artifacts
export LPT_RUN_ID="build-1842-$(date -u +%Y%m%dT%H%M%SZ)"
jmeter -n -t tests/checkout.jmx \
-JbaseUrl="$BASE_URL" -JrunId="$LPT_RUN_ID" \
-Jusers=80 -Jramp=120 -Jduration=900 \
-l artifacts/results.jtlJMeter im CSV-Modus mindestens mit timeStamp, elapsed, label, success, responseCode konfigurieren. Das folgende Beispiel verwendet SQLite als portable Ergebnisablage; in einer Team-Pipeline lässt sich die Verbindung durch PostgreSQL oder eine zentrale Analytics-Datenbank ersetzen.
import csv
import os
import sqlite3
from datetime import datetime, timezone
from pathlib import Path
RUN_ID = os.environ["LPT_RUN_ID"]
JTL_PATH = Path("artifacts/results.jtl")
TRANSACTIONS = {"Login", "Browse catalog", "Checkout transaction"}
db = sqlite3.connect("artifacts/lpt-results.sqlite")
db.execute("""CREATE TABLE IF NOT EXISTS samples (
sample_id INTEGER PRIMARY KEY, run_id TEXT NOT NULL, transaction_name TEXT NOT NULL,
timestamp_utc TEXT NOT NULL, elapsed_ms INTEGER NOT NULL,
success INTEGER NOT NULL, response_code TEXT
)""")
records = []
with JTL_PATH.open(newline="", encoding="utf-8") as source:
for row in csv.DictReader(source):
if row["label"] not in TRANSACTIONS:
continue # Setup-, Teardown- und technische Sampler ausfiltern
stamp = datetime.fromtimestamp(
int(row["timeStamp"]) / 1000, tz=timezone.utc
).isoformat()
records.append((RUN_ID, row["label"], stamp,
int(row["elapsed"]),
int(row["success"].lower() == "true"),
row.get("responseCode", "")))
with db:
db.execute("DELETE FROM samples WHERE run_id = ?", (RUN_ID,))
db.executemany("""INSERT INTO samples
(run_id, transaction_name, timestamp_utc, elapsed_ms, success, response_code)
VALUES (?, ?, ?, ?, ?, ?)""", records)
db.close()
print(f"{RUN_ID}: {len(records)} Transaktionssamples gespeichert")Der Transaktionsfilter muss zu den tatsächlichen JMeter-Labels passen; bei Parent Samples über den Transaction Controller ist das Label üblicherweise der Transaktionsname. Vor Einlesen Spalten und Dateigröße validieren, Duplikatstrategie festlegen und keine Response-Bodies mit sensiblen Daten persistieren.
import os
from pathlib import Path
import requests
base = os.environ["GRAFANA_URL"].rstrip("/")
token = os.environ["GRAFANA_SERVICE_TOKEN"]
dashboard = os.environ["GRAFANA_DASHBOARD_UID"]
run_id = os.environ["LPT_RUN_ID"]
start_ms = os.environ["RUN_START_MS"]
end_ms = os.environ["RUN_END_MS"]
response = requests.get(
f"{base}/render/d/{dashboard}/lpt-run",
params={"from": start_ms, "to": end_ms,
"var-run_id": run_id, "width": 1600, "height": 1000},
headers={"Authorization": f"Bearer {token}"}, timeout=90)
response.raise_for_status()
artifact = Path("artifacts") / f"grafana-{run_id}.png"
artifact.parent.mkdir(parents=True, exist_ok=True)
artifact.write_bytes(response.content)
print(f"Dashboard-Snapshot: {artifact}")Voraussetzung ist ein aktivierter Grafana Image Renderer und ein Service Account mit rein lesendem Dashboard-Zugriff. Token als CI-Secret injizieren. Dashboard-Variable run_id und Annotationen müssen denselben Wert wie die Testausführung verwenden. Screenshot, JTL, Skript-Commit und Datenbank-Run-ID anschließend als Pipeline-Artefakte gemeinsam aufbewahren.
Report-Vorlage für einen abgeschlossenen Lauf
Run ID / Build / Skript-Commit:
Zielumgebung / Datenstand / Generator:
Testart / Zeitraum / angebotene Rate:
Ergebnis: PASS | PASS WITH FINDINGS | FAIL
SLI: Durchsatz ___/s · Fehlerquote ___ · P95 ___ ms · P99 ___ ms
Generator: VUs ___ · dropped iterations ___
Top-Befund / Nutzerwirkung:
Engpassbeleg: Dashboard / Trace / Query-Plan / Hostsignal
Abweichung zur Baseline:
Risiken und bekannte Einschränkungen:
Entscheidung / Owner / nächster Schritt: