FIELDNOTES/ENGINEERING
LPT PLAYBOOK · 01 / 02
BUILD THE PRACTICE

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.

05PHASEN
01GEMEINSAME BASELINE
∞ITERATIONEN
FIELDNOTES / LPTABLAUF ANALYSIEREN → MODELLIEREN → BELEGEN
PHASE 01 — ALIGN

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.

ANFORDERUNGMESSGRENZE / POPULATIONLASTBEDINGUNGBEISPIEL-ZIEL
LatenzClientseitig, erfolgreich abgeschlossene Requests; pro User JourneySteady State bei definierter Rate und DatengrößeP95 ≤ 400 ms, P99 ≤ 900 ms
KapazitätErfolgreiche fachliche Transaktionen pro SekundeFehlerrate innerhalb Budget, keine wachsende Queue≥ 120 Checkout/s für 20 min
FehlerFachliche und technische Fehler geteilt durch alle VersucheNach Warm-up, pro Transaktion ausweisen< 0,5 %, keine unerwarteten 5xx
StabilitätFehlerrate, Heap, Queue und Durchsatz über ZeitEndurance-Lauf mit realistischem TagesprofilKein ungebremstes Wachstum über 4 h
PHASE 02 — DISCOVER

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.

KOMPONENTEFRAGEN FÜR DIE ANALYSEBEOBACHTBARE SIGNALERISIKO FÜR DAS MODELL
API / GatewayRate-Limits, Auth, Payload-Limits, Keep-alive?Request-Rate, 429/5xx, Upstream-Pool-WartezeitTest trifft Gateway-Limit statt SuT-Kapazität
Service / CacheSessionzustand, Hit Rate, TTL, Warm-up?RED-Metriken, Pool-Wait, Hit/Miss, EvictionsUnrealistische Cache-Verteilung unterschätzt Backend-Last
DatenbankQuery-Mix, Datenvolumen, Isolation, Seed/Reset?Connections, Locks, IOPS, Slow Queries, Buffer ReadsLeere Testdaten erzeugen andere Pläne als Produktion
Provider / QueueSandbox, Quoten, Latenzverteilung, Consumer-Lag?Timeouts, Retry-Rate, Queue-Alter, DurchsatzExterner Engpass oder Retry-Amplification verfälscht Resultat
Template: Systemgrenze und Abhängigkeitsinventar
TXT system-boundary.md
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:
PHASE 03 — PREPARE

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.

01SEEDAusgangsdaten laden
→
02WARM-UPCache / Runtime stabilisieren
→
03VERIFYCounts, Quoten, Health Checks
→
04RESETNach Lauf sauber zurücksetzen
PHASE 04 — MODEL

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
JS tests/lpt-smoke.js
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.

PHASE 05 — PROVE

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.

01
Baseline und Warm-up

Testdaten zurücksetzen, Runtime/Cache stabilisieren, Dashboard-Zeitfenster und Run-ID setzen.

02
Last stufenweise anlegen

Smoke → Ramp-up → Steady State → Ramp-down; vorab definierte Stop-Kriterien aktiv beobachten.

03
Rohdaten sichern und auswerten

JTL, Skript-Commit, Build, Parameter, Logs und Messfenster gemeinsam versioniert archivieren.

04
Gate und Befund veröffentlichen

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.

CLI reproduzierbarer Lauf
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.jtl

JMeter 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.

PY tools/ingest_jtl.py
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.

PY tools/capture_grafana.py
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
MD run-report.md
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: