24. August 2026 11 Min. Lesezeit

SMTP-Lasttest mit Apache JMeter in der Praxis: 10'000 Mails, fünf Regelpfade, ein HTML-Report

Ein durchgeführter Lasttest von A bis Z: Testplan mit Nachrichtenmix entlang der Ruleset-Pfade eines Verschlüsselungs-Gateways, portables Setup ohne Installation, 10'000 Mails im Burst und die Auswertung über den JMeter-HTML-Report, inklusive der Probleme, die dabei tatsächlich auftraten.

Der Überblicksartikel zu Mail-Lasttests hat die Werkzeuge verglichen und den Testplan skizziert. Hier folgt die praktische Durchführung: ein vollständiger JMeter-Lasttest mit 10’000 Mails, einem Nachrichtenmix entlang realer Gateway-Regelpfade und dem HTML-Report als Auswertung. Alle gezeigten Werte stammen aus dem tatsächlichen Lauf, inklusive der Fehler, die unterwegs auftraten.

Das Szenario ist einem realen Projekt nachempfunden: Ein E-Mail-Verschlüsselungs-Gateway auf Apache-James-Basis (Totemomail) hängt als Smarthost-Schlaufe hinter Exchange Online und entscheidet pro Nachricht über Verschlüsselung, Signierung und Sonderrouting. Das Mailet-Ruleset kennt dafür mehrere Pfade: Betreff-Trigger wie (sec), (sign) und (unsec), Schlüsselwörter wie VERTRAULICH für das Routing zu einem Branchen-Gateway und den Standardpfad mit Zertifikatsprüfung und Klartext-Fallback. Ein Lasttest, der nur eine einzige Nachrichtensorte einliefert, würde immer denselben Pfad durch dieses Regelwerk messen; der Testplan bildet deshalb fünf Klassen ab, deren Mischverhältnis dem erwarteten Verkehr entspricht.

Wichtig für die Einordnung: Dieser Testplan erzeugt das Lastbild vieler unabhängiger Einlieferer, denn JMeter öffnet für jede Nachricht eine eigene Verbindung (die Hintergründe stehen in der Abgrenzung am Ende). Für den Nachweis, dass ein Regelwerk unter parallelem Mischverkehr korrekt und schnell genug arbeitet, ist das das passende Muster. Die Spitzenlast eines einzelnen Massenversenders mit offenen Sessions bildet der Plan dagegen nicht ab; für dieses Lastbild ist smtp-source aus dem Überblicksartikel das richtige Werkzeug.

Die wichtigsten Optionen von jmeter

Zur Orientierung vorab die Kommandozeilenoptionen, die in diesem Beitrag vorkommen, sinngemäss aus der Dokumentation übersetzt:

Optionen im Überblick
OptionBedeutung
-nCLI-Modus (non-GUI): führt den Testplan ohne grafische Oberfläche aus
-t dateiPfad zur JMX-Datei mit dem Testplan
-l dateiPfad zur JTL-Ergebnisdatei, in die die Messwerte geschrieben werden
-eErzeugt nach dem Lauf direkt den HTML-Dashboard-Report
-o verzeichnisZielverzeichnis für den Report; muss leer sein oder darf noch nicht existieren
-g dateiErzeugt den Report nachträglich aus einer vorhandenen JTL-Datei, ohne neuen Lauf
-J<property>=<wert>Setzt eine JMeter-Property nur für diesen Aufruf

Die vollständige Liste zeigt jmeter -?; beschrieben sind die Optionen im Kapitel zum Non-GUI-Betrieb des JMeter User’s Manual.

Der Aufbau: nichts installieren müssen

Der Test lief auf einer Windows-Maschine ohne Java und ohne JMeter. Beides lässt sich portabel betreiben, was auf Admin-Arbeitsplätzen mit eingeschränkten Installationsrechten der entscheidende Punkt ist: Temurin-JRE als ZIP von Adoptium, JMeter als ZIP von apache.org, beides entpacken, JAVA_HOME auf das JRE-Verzeichnis setzen, fertig.

export JAVA_HOME="$PWD/jdk-21-jre"
export PATH="$JAVA_HOME/bin:$PATH"
./apache-jmeter-5.6.3/bin/jmeter -n -t gateway-lasttest.jmx -l lauf.jtl -e -o report
Optionen erklärt
OptionWirkung
export JAVA_HOME=…Zeigt auf das entpackte JRE-Verzeichnis; JMeter findet darüber die Java-Laufzeit ohne Installation
export PATH=…Stellt die JRE-Binaries an den Anfang des Suchpfads
-nCLI-Modus ohne grafische Oberfläche
-t gateway-lasttest.jmxDer auszuführende Testplan
-l lauf.jtlErgebnisdatei mit den Messwerten jedes Samplers
-eHTML-Report direkt nach dem Lauf erzeugen
-o reportZielverzeichnis für den Report

Als Senke diente eine lokale SMTP-Blackbox auf Basis von aiosmtpd, gut 40 Zeilen Python: Sie nimmt jede Nachricht mit 250 an, verwirft den Inhalt, zählt mit und ordnet jede Mail anhand der Betreffzeile einer Klasse zu. Diese unabhängige Zählung auf der Empfangsseite ist der Kontrollversuch des Tests; stimmen Generator- und Senkenzahlen nicht überein, ist unterwegs etwas verloren gegangen.

from aiosmtpd.controller import Controller

class SinkHandler:
    def __init__(self):
        self.count = 0

    async def handle_DATA(self, server, session, envelope):
        self.count += 1
        # Betreff fuer die Klassen-Statistik aus dem Header ziehen,
        # Inhalt wird verworfen
        return "250 Message accepted for delivery"

controller = Controller(SinkHandler(), hostname="127.0.0.1", port=2525)
controller.start()

Wichtig für die Einordnung: Generator und Senke liefen auf derselben Maschine, ohne TLS und ohne Netzwerk dazwischen. Die gemessenen Zahlen sind darum keine Aussage über ein Gateway, sondern der Selbsttest des Generators aus dem Überblicksartikel: der Beleg, dass der Lastaufbau die Zielrate überhaupt erzeugen kann, und die Obergrenze, mit der spätere Messungen gegen das echte Testsystem verglichen werden.

Der Testplan: fünf Nachrichtenklassen, ein Mischverhältnis

Das Herzstück des Plans ist eine Thread Group mit 20 Threads, 10 Sekunden Ramp-up und 500 Schleifen, also 10’000 Iterationen. Darunter liegen fünf Throughput Controller im Modus “Percent Executions”, jeder mit genau einem SMTP Sampler:

Klasse (Sampler-Label)AnteilRegelpfad im Gateway
01 Standard ohne Trigger60 %AutoGenerated-Prüfung, Zertifikatsprüfung, Klartext-Fallback
02 Trigger (sec)15 %TRE-Envelope für Empfänger ohne Zertifikat
03 Trigger (sign)10 %Certificate Exchange: signieren, Schlüssel mitschicken
04 Schlüsselwort VERTRAULICH10 %Sonderrouting zum Branchen-Gateway
05 Trigger (unsec)5 %Klartext erzwungen

Die Aufteilung auf fünf getrennte Sampler statt eines Samplers mit variablem Betreff hat einen handfesten Grund: Der HTML-Report gruppiert alle Kennzahlen nach dem Sampler-Label. Fünf Labels ergeben fünf Zeilen in der Statistik mit eigenen Perzentilen pro Klasse; ein einzelner Sampler mit CSV-gespeistem Betreff ergäbe eine einzige Sammelzeile, und der Unterschied zwischen den Regelpfaden wäre in der Auswertung unsichtbar.

Jeder Sampler füllt die üblichen Felder: Zielhost und Port als benutzerdefinierte Variablen (${zielhost}, ${zielport}), damit derselbe Plan ohne Änderung gegen Senke, Testumgebung oder PreProd laufen kann, dazu Absender, Empfänger, Betreff mit einem klaren Marker (hier das Wort LOADTEST im Betreff) und ein Textkörper von rund 1 bis 2 KB. Die Option “Include timestamp in subject” ergänzt den Einlieferungszeitpunkt in Millisekunden; bei einem späteren Lauf gegen ein echtes mehrstufiges System lässt sich daraus zusammen mit den Empfangszeitpunkten der Senke die Ende-zu-Ende-Latenz pro Nachricht rechnen.

Ein Fehler aus diesem Lauf, der sich verallgemeinern lässt: Der erste Versuch scheiterte mit 10’000 Fehlern in 10 Sekunden, alle mit java.lang.ClassCastException ... NullProperty cannot be cast to ... CollectionProperty statt einer SMTP-Antwort. Ursache war eine von Hand gebaute JMX-Datei, in der die Header-Liste des Samplers fehlte; der Sampler erwartet die Property zwingend, auch wenn sie leer ist. Die Lehre daraus ist weniger die konkrete Property als das Muster: Testpläne in der GUI zusammenklicken und speichern, nicht als XML von Hand schreiben, und vor jedem Burst einen Kleinstlauf machen und auf der Senke nachsehen, dass Betreff und Inhalt wirklich ankommen. Ein Fehlerzähler von 100 Prozent bei 0 ms Antwortzeit bedeutet fast immer, dass der Fehler vor dem Netzwerk passiert, der Test also nie beim Zielsystem war.

Der Lauf

Die Messung selbst läuft im CLI-Modus; die GUI ist nur der Editor. Ein einziger Aufruf erzeugt Lauf, Rohdaten und Report:

jmeter -n -t gateway-lasttest.jmx -l lauf-10k.jtl -e -o report-10k
Optionen erklärt
OptionWirkung
-nCLI-Modus: der Testplan läuft ohne GUI, nur der Summariser schreibt auf die Konsole
-t gateway-lasttest.jmxDer in der GUI erstellte Testplan
-l lauf-10k.jtlRohdaten des Laufs; aus dieser Datei lässt sich der Report später erneut erzeugen
-eReport unmittelbar nach dem Lauf generieren
-o report-10kZielverzeichnis für den HTML-Report

Der Summariser auf der Konsole zeigt den Verlauf live, das Endergebnis des Laufs:

summary =  10000 in 00:00:13 =  781.8/s Avg: 6 Min: 1 Max: 27 Err: 0 (0.00%)

10’000 Nachrichten in 12.8 Sekunden, 782 Nachrichten pro Sekunde im Schnitt, keine Fehler. Die Senke bestätigte unabhängig davon exakt 10’000 angenommene Mails mit dem Mix 6000 / 1500 / 1000 / 1000 / 500, das Mischverhältnis der Throughput Controller ging also auf die Nachricht genau auf.

Der HTML-Report

Das Argument für JMeter gegenüber schlankeren Generatoren wie smtp-source ist die Auswertung, und die liefert der Dashboard-Report ohne Zusatzarbeit:

JMeter-Dashboard des Laufs: APDEX 1.000 für alle fünf Klassen, Requests Summary 100 Prozent PASS, Statistiktabelle mit Perzentilen pro Nachrichtenklasse

Die Statistiktabelle ist der wichtigste Teil des Reports. Pro Sampler-Label, also pro Nachrichtenklasse, stehen dort Anzahl, Fehlerquote, Durchschnitt, Median, 90.-, 95.- und 99.-Perzentil, Maximum und Durchsatz. Im konkreten Lauf: Median 7 ms, p95 bei 11 ms, p99 bei 12 ms, Maximum 27 ms, und zwar praktisch identisch über alle fünf Klassen. Das ist bei einer lokalen Senke, die jede Nachricht gleich behandelt, genau das erwartete Bild und gleichzeitig der Referenzwert: Läuft derselbe Plan später gegen das echte Gateway und die (sec)-Klasse zeigt plötzlich ein Vielfaches des Standard-Medians, ist das die Mehrarbeit des Verschlüsselungspfads, sauber isoliert pro Regelzweig.

Der APDEX-Block darüber verdichtet dasselbe auf eine Zahl pro Klasse (hier überall 1.000, weil alle Antworten weit unter der Toleranzschwelle von 500 ms lagen); die Schwellen lassen sich in den Report-Properties an eigene Service-Ziele anpassen. Der Errors-Block bleibt in diesem Lauf leer, ist aber bei Tests gegen echte Systeme die erste Anlaufstelle: Er gruppiert Fehler nach Antworttext, sodass ein 421-Drosseln des Zielsystems sofort von Verbindungsabbrüchen unterscheidbar ist.

Ein typischer Auswertungsfehler auch hier, und er betrifft jeden kurzen Burst: Die Zeitreihen-Charts des Reports arbeiten standardmässig mit einer Granularität von einer Minute. Ein Lauf von 13 Sekunden kollabiert damit zu einem einzigen Datenpunkt, und die Kurven unter “Charts” sehen aus wie ein Messfehler. Der Report lässt sich aus der vorhandenen JTL-Datei ohne neuen Lauf mit feinerer Auflösung regenerieren:

jmeter -g lauf-10k.jtl -o report-fein -Jjmeter.reportgenerator.overall_granularity=1000
Optionen erklärt
OptionWirkung
-g lauf-10k.jtlErzeugt den Report aus der vorhandenen JTL-Datei, ohne den Test erneut auszuführen
-o report-feinNeues Zielverzeichnis; das bestehende Report-Verzeichnis bleibt unverändert
-Jjmeter.reportgenerator.overall_granularity=1000Setzt die Chart-Granularität für diesen Aufruf auf 1’000 ms statt der standardmässigen Minute

Mit Sekunden-Granularität wird aus dem einzelnen Punkt der tatsächliche Lastverlauf:

Hits per Second mit 1-Sekunden-Granularität: Anstieg während des 10-Sekunden-Ramp-ups auf ein Plateau um 840 Nachrichten pro Sekunde, dann steiler Abfall am Testende

Die Kurve zeigt den 10-Sekunden-Ramp-up, ein Plateau um 840 Nachrichten pro Sekunde und den Abfall am Ende, wenn die ersten Threads ihre 500 Schleifen abgeschlossen haben. Für die Interpretation zählt das Plateau, nicht der Durchschnitt über den Gesamtlauf: Der Schnitt von 782/s enthält Ramp-up und Auslauf und unterschätzt die erreichte Dauerrate.

Was dieser Lauf belegt und was nicht

Belegt ist nach diesem Lauf: Der Testplan ist funktional korrekt (Kleinstlauf mit Inhaltskontrolle auf der Senke), das Mischverhältnis stimmt exakt, und der Generator schafft auf dieser Maschine mindestens 840 Nachrichten pro Sekunde ohne TLS. Wer damit ein Gateway testen will, das für 100 Mails pro Sekunde ausgelegt ist, hat Reserve um den Faktor acht und kann Engpässe guten Gewissens dem Zielsystem zuschreiben.

Nicht belegt ist alles andere, und diese Abgrenzung gehört zu jedem Testbericht: keine Aussage über TLS-Handshake-Kosten (der echte Pfad spricht STARTTLS), keine über das Queue-Verhalten des Gateways, keine über die Verarbeitungszeit der Regelpfade. Dafür zeigt derselbe Plan mit umgestellten Variablen zielhost/zielport auf die Testumgebung des Gateways; die Auswertung läuft dann identisch, ergänzt um die Gateway-Logs und die Queue-Beobachtung aus dem Überblicksartikel. Genau diese Wiederverwendbarkeit, ein Plan für Senke, Testumgebung und PreProd bei identischer Auswertung, ist der eigentliche Grund, den Aufwand für einen sauberen JMeter-Plan einmal zu treiben.

Eine Grenze des Werkzeugs selbst gehört ebenfalls in die Abgrenzung: JMeter kann SMTP-Sessions nicht offenhalten. Der SMTP Sampler öffnet für jede Nachricht eine neue Verbindung, durchläuft EHLO, gegebenenfalls STARTTLS und AUTH und beendet sie nach genau einer Transaktion mit QUIT. Die 840 Nachrichten pro Sekunde enthalten also pro Nachricht einen vollständigen Verbindungsaufbau. Ein Massenversender, der hunderte Nachrichten über eine offene Session schickt, erzeugt am Gateway ein anderes Lastbild mit weniger Verbindungen und mehr Transaktionen pro Verbindung, und Verbindungs-Limits greifen bei JMeter-Last entsprechend früher. Der Grund ist die Architektur des Frameworks: JMeter misst jeden Sampler als in sich geschlossene, unabhängige Einheit, damit Timer, Assertions und Perzentile für alle unterstützten Protokolle gleich funktionieren, und der SMTP Sampler ist ein Aufsatz auf die JavaMail-Bibliothek, die als Client-API pro Sendevorgang verbindet und wieder trennt. Eine Verbindungs-Wiederverwendung wie beim HTTP-Sampler mit Keep-Alive existiert für SMTP nicht. Für das Lastbild eines Bulk-Senders mit offener Session eignen sich smtp-source oder ein eigenes Skript besser; der Werkzeugvergleich im Überblicksartikel ordnet das ein.

Quellen

  1. Apache JMeter User’s Manual: Component Reference, SMTP Sampler

    Referenz der Sampler-Felder inklusive Header, Timestamp-Option und EML-Versand.

    https://jmeter.apache.org/usermanual/component_reference.html
  2. Apache JMeter: Generating Dashboard Report

    Erzeugung des HTML-Reports aus dem Lauf oder nachträglich aus der JTL, inklusive der Granularitäts- und APDEX-Properties.

    https://jmeter.apache.org/usermanual/generating-dashboard.html
  3. Apache JMeter: Test Plan, Logic Controllers

    Funktionsweise des Throughput Controllers im Modus Percent Executions für den Nachrichtenmix.

    https://jmeter.apache.org/usermanual/component_reference.html#Throughput_Controller
  4. aiosmtpd, Dokumentation

    Der asyncio-basierte SMTP-Server, mit dem die Senke in wenigen Zeilen Python entsteht.

    https://aiosmtpd.aio-libs.org/
  5. Eclipse Temurin, Adoptium

    Portable JRE-Archive für den Betrieb von JMeter ohne Java-Installation.

    https://adoptium.net/temurin/releases/
  6. Apache JMeter: Getting Started, Non-GUI Mode

    Übersicht der Kommandozeilenoptionen für den CLI-Betrieb, inklusive -n, -t, -l, -e, -o, -g und -J.

    https://jmeter.apache.org/usermanual/get-started.html

Kommentare

Die Kommentare werden von GitHub / Giscus geladen.