26. Benchmark-Suite¶
Die tests/NN-*-bench.sh-Skripte messen, wie schnell die Live-Pepsi-Pipeline Mail verarbeitet. Sie verwenden dieselben Hosts, Konten und Helper wie die Korrektheits-Suite (tests/test-accounts.ini, tests/lib.sh) plus ein gemeinsames tests/bench-lib.sh wieder und werden auf dieselbe Weise angetrieben:
tests/08-perstage-bench.sh tests/test-accounts.ini
tests/09-latency-bench.sh tests/test-accounts.ini
tests/10-goodput-bench.sh tests/test-accounts.ini
# or all three in order:
make benchmarks ACCOUNTS=tests/test-accounts.ini
Sie sind nicht Teil von make integrationtests (sie sind langsam und belasten den MTA bewusst). Jedes führt zuerst preflight_common aus, sodass es sauber SKIPpt, wenn die Live-Hosts/DB/Dienste unerreichbar sind. Führen Sie zuerst tests/01-deploy.sh aus, damit die Pipeline — einschließlich des DAVE-Lokale-Maildir-Zustellziels — existiert.
Dieses Kapitel beschreibt die Methode. Wie die Ergebnisse zu lesen sind — Kosten pro Stage, Leerlauf-Latenz und Sättigungs-Nutzdurchsatz — und was sich abstimmen lässt, beschreibt das Kapitel Leistung.
26.1. Wie die Zahlen gewonnen werden¶
Die Kosten pro Stage stammen aus der dispatchereigenen Zeitmessung:
pepsi-dispatchzeichnet jede Stage-Ausführung inpepsi.stage_stats(stage, messages, duration_us)auf. Ein Schnappschuss dieser Tabelle vor/nach einer Nachricht ergibt exakte Mikrosekunden-pro-Nachricht je Stage — für eine Stage, die eine per SMTP gesendete Nachricht erreicht, ebenso wie für eine, in die direkt injiziert wird, da beide als gewöhnliche vom Dispatcher gestartete Worker laufen. Die Benchmarks senken[pepsi-dispatch] STATS_INTERVAL(undPOLL_INTERVAL), sodass die Zähler umgehend weggeschrieben werden, und setzen außerdem[pepsi] LOGauf eine ruhige Stufe (warnstandardmäßig, überBENCH_LOG_LEVELüberschreibbar), sodass das Logging pro Nachricht die Messungen nicht aufbläht — diese Stellschraube wird von jeder Komponente gelesen, sodass sie den Dispatcher und die von ihm gestarteten Stage-Worker abdeckt (die kein eigenes Log-Flag annehmen). Sowohlpepsi-dispatchals auchpepsi-ingresswerden neu gestartet, damit die neue Stufe wirksam wird, und die Benchmarks stellen beim Beenden die installierte Konfiguration wieder her (eine einmaligepepsi.conf.bench.bak-Sicherung auf dem ADMIN-Host, durch eintrap … EXITzurückgespielt).Nutzdurchsatz ist das Delta des globalen Zählers
pepsi.dispatch_stats.messages_processedüber jedes 5-Sekunden-Fenster. Er wird alleSTATS_INTERVALgeschrieben, hängt bei hoher Rate also um bis zu ein Intervall hinter der Wahrheit her; die Spaltedeliv/sdaneben — Dateien, die im Ziel-Maildir/newerscheinen — ist die unmittelbare Gegenprobe, und wenn die beiden um einen Schritt auseinanderliegen, ist das dieser Verzug und keine verlorene Nachricht.Die Zustellung wird durch Zählen der Dateien im Ziel-
Maildir/newgegengeprüft.Der Lastgenerator sind mehrere Prozesse, nicht einer.
load_burstverteilt einen Burst aufLOAD_PROCS(Standard 4) Betriebssystemprozesse, von denen jeder seinen Anteil der Threads über seinen eigenen Abschnitt des Nachrichtenbereichs laufen lässt, und führt deren JSON zusammen. Ein Python-Prozess ist ein GIL, und bei hohen Raten ist der Interpreter dasjenige, was die Zahl nicht weiter steigen lässt — womit ein Ein-Prozess-Generator aufhört, das getestete System zu messen, und anfängt, sich selbst zu messen.
Damit die eingehende Pay-to-Send-(Anti-Spam-)Stage die Last nicht abriegelt, setzen die Benchmarks den Absender vorab auf die weiße Liste (bench_whitelist_sender), sodass check-whitelist state.spam=false setzt und anti-spam kurzschließt.
Bemerkung
Die Rampe kann über SMTP keinen Rückstau aufbauen, und das ist kein Fehler der Rampe. pepsi-ingress schreibt jede Annahme synchron fest — ein 250 bedeutet, dass die Nachricht die Platte erreicht hat —, sodass auf einem Host, auf dem die Annahme das Engere von beiden ist, die Warteschlange nahezu leer bleibt, wie stark der Generator auch drückt, und die Rampe das Front-End misst. Um die Pipeline mit einer tiefen Warteschlange zu messen, fügen Sie Datensätze direkt in pepsi.workqueue mit status = 'pending' in einer Transaktion ein und messen das Leerlaufen; das Kapitel Performance zeigt, wie, und der Unterschied zwischen den beiden Zahlen ist der interessante Teil.
26.2. Die drei Benchmarks¶
``08-perstage-bench.sh`` — Kosten pro Stage gegenüber der Nachrichtengröße. Gibt eine einzige
stage × size-Matrix in µs/Nachricht aus, die jede Stage abdeckt, die Pepsi hat, gefüllt von zwei Hälften, die zwei verschiedene Fragen beantworten: Die Szenarien messen die Stages der installierten Pipeline in situ, und die isolierten Stages messen jedes Stage-Programm, durch das die installierte Pipeline keine Nachricht leitet.Die Szenarien. Eine einzelne Nachricht durchläuft nur einen Pfad und kann daher nur die Stages auf diesem Pfad messen; um die gesamte bidirektionale, verzweigte Pipeline abzudecken, sind deshalb mehrere Nachrichten nötig — eine pro Pfad. Der Benchmark treibt eine Reihe von
SCENARIOSan, jedes eine Nachricht, deren Weg eine andere Gruppe von Stages aufdeckt, und summiert ihre Kosten pro Stage in der Matrix:Szenario
Nachricht
Stages, die es neu aufdeckt
inbound-localCAROL→DAVE(fremder Eingang → lokales Maildir)init(arc),route(if),decrypt,detect-language,block-language,check-whitelist,anti-spam(Kurzschluss),list(Listen-Router),forward(dot-forward),local(maildir)inbound-relayCAROL→ALICE(eingehend → weitergeleitetes Relay)srs,dkim-sign-relay,smarthost(der nicht-lokale Empfänger wird vonlocalauf dieses Endstück abgespalten)outboundALICE→CAROL(Submission lokalen Ursprungs → direktes Relay)list-out(Listen-Router),edit-settings,auto-whitelist,delay-route(if),encrypt,dkim-sign,internet(relay-to-internet)bounceCAROL→dave-broken(lokale Zustellung schlägt fehl)bounce(der Maildir-Helper schlägt fehl → ein Bounce-SideClonewird zur Bounce-Stage geleitet →dkim-sign)bounce-languageCAROL→DAVE, französischer Nachrichtentext (Sprache auf der schwarzen Liste)bounce-language(block-languageerzeugt einen Bounce für einefr-Nachricht)payment(Opt-in)CAROL(nicht auf der weißen Liste) →DAVE(Pay-to-Send-Schranke)bounce-payment(verkürztPAYMENT_DEADLINEfür den Lauf)delay(Opt-in)ALICE→blackhole.invalid,ENVID=PEPSIDELAY(DELAY DSN)delaytest(relay-to-smarthost an einen nicht routebaren MTA)Die Standard-
SCENARIOSsind die fünf schnellen; hängen Siepaymentund/oderdelayan, um auch die letzten beiden installierten Stages zu messen (sie sind langsamer — händler- und wiederholungsgebunden). Diepayment/delay-Szenarien und die beiden Opt-in-Stages benötigen einen Absender, der nicht lokalen Ursprungs ist, sowie das defekte lokale Ziel, was alles01-deploy.sheinrichtet.SCENARIOS=""zu setzen führt allein die isolierte Hälfte aus.Die isolierten Stages.
pepsi-ingresslässt eine Nachricht immer beiinitzu (pepsi_common::stage::INITIAL_STAGE), sodass ein Szenario immer nur die Stages erreichen kann, durch die die installierte Pipeline sie leitet. Jedes Stage-Programm, das nicht in diese Pipeline verdrahtet ist, ist daher für keine Nachricht erreichbar, wie auch immer sie konstruiert ist.Deshalb hat der Lauf eine zweite Hälfte. Er hängt benchmark-eigene
[stage-bench-*]-Abschnitte an die installierte Konfiguration an — jeder mit den sinnvollen Standardwerten der betreffenden Stage, und jede Kante zeigt auf eine gemeinsamebench-discard-Senke (pepsi-stage-discard), sodass nichts, was eine isolierte Stage weiterschaltet, in die Live-Pipeline oder ins Netz entkommen kann — und injiziert dann eine Nachricht der jeweiligen Größe direkt in jede von ihnen mit der SQL-Funktionpepsi.workqueue_inject, also genau so, wie es eine Stage tut, die Mail erzeugt (StageContext::enqueue_new).workqueue_injectbenachrichtigt denworkqueue-Kanal, sodass der Dispatcher den Datensatz sofort aufgreift, die Stage als gewöhnlicher Worker läuft und ihre Kosten wie alle anderen inpepsi.stage_statslanden. Zu injizieren statt zu senden nimmt außerdempepsi-ingress, SMTP und die Zulassungsgrenzen aus der Messung heraus, sodass eine isolierte Zahl die Kosten der Stage selbst sind und nichts sonst.isolierte Stage
Programm
was konfiguriert ist und was es daher misst
bench-discardpepsi-stage-discardDISPOSITION = success,BOUNCE = no— die gemeinsame Senke, sodass ihre eigene Zeile zugleich die blanke Untergrenze der Pipeline je Nachricht istbench-aliasespepsi-stage-aliaseseine
ALIASES-Map mit drei Einträgen (eine Expansion auf zwei Ziele, ein transitiver Hop, ein@domain-Catch-All) und ein Empfänger, der expandiertbench-routepepsi-stage-routeeine ausdrückliche
ROUTES-Ausnahme plusMANAGED_STAGE; der Empfänger liegt bei einer verwalteten Domain, sodass der verwaltete Zweig genommen wirdbench-vacationpepsi-stage-vacationVACATION_RANGES, die den heutigen Tag abdecken (in der Zeitzone des Servers), undSUPPRESS_DAYS = 0, sodass jede Nachricht wirklich eine Benachrichtigung verfasst und injiziert, statt den Ausgang „niemand ist abwesend“ zu nehmenbench-autocrypt-learnpepsi-stage-autocrypt-learnStandardwerte, auf einer Nachricht ohne
Autocrypt:-Header — der Durchlauf über eine Nachricht, an der es nichts zu lernen gibt, was bis auf einen winzigen Bruchteil alle echte eingehende Mail istbench-vks-confirmpepsi-stage-vks-confirmein
VKS_HOST, der der Bench-Absender nicht ist, sodass die Nachricht durchläuft und keinem Link gefolgt wird (keine Netzwerk-E/A)bench-auto-paypepsi-stage-auto-paydas Wallet, das wie das installierte
[stage-pay]konfiguriert ist, auf einer Nachricht, die keine Zahlungsforderung ist — wieder der häufige eingehende Fall. Die zahlende Hälfte braucht einen laufenden Händler und ein Wallet;12/13treiben das an.bench-secure-linkpepsi-stage-secure-linkStandardwerte; terminal. Das globale
[pepsi-secure-link] NOTIFY_STAGEwird für den Lauf auf die Senke gerichtet, sodass das Messen der Stage keine Mail auf die Leitung bringt.bench-lmtppepsi-stage-relay-to-lmtpder lokale Dovecot-Socket und das Wegwerf-Postfach, das
11-lmtp-test.sheinrichtet; übersprungen, sofern nicht beide vorhanden sindbench-milterpepsi-stage-milterübersprungen, sofern nicht in
BENCH_MILTER_ADDRein Milter benannt ist (z. B.inet:8890@127.0.0.1)Eine isolierte Stage, deren Programm nicht installiert ist oder deren externe Abhängigkeit fehlt, wird vor dem Schreiben der Abschnitte fallen gelassen; und wenn der Dispatcher mit ihnen nicht wieder hochkommt, wird das Anhängen zurückgerollt und die isolierte Hälfte aufgegeben, statt den Host ohne seinen Koordinator zu lassen.
ISO_STAGES=""überspringt die Hälfte vollständig.Bewusst nicht isoliert:
pepsi-stage-encrypt,-decrypt,-dot-forward,-srs,-dkim-signund-if— die installierte Pipeline leitet durch sie alle, sodass die Szenarien sie bereits in situ messen, was die ehrlichere Zahl ist.Die Matrix lesen. Stages, die den Nachrichtentext lesen (arc verify, decrypt, detect-language, encrypt, dkim-sign, secure-link, relay/maildir), steigen mit der Größe; reine Metadaten-Stages (if-Zweige, srs, whitelist, aliases, route) bleiben flach. Die Relay-Stages (
smarthost,internet,delaytest,bench-lmtp) sind netzwerk- oder MDA-inklusive — sie halten die ausgehende Transaktion offen, sodass ihre Zahl der Round-Trip zum nächsten Hop ist, nicht lokale CPU-Zeit; der Benchmark gibt einen ausdrücklichen Hinweis aus, wenn eine solche Stage öfter lief, als Nachrichten injiziert wurden, d. h. wenn der nächste Hop aufgeschoben hat und die Zahl Wiederholungen mit einmittelt.``09-latency-bench.sh`` — Leerlauf-Ende-zu-Ende-Latenz. Führt die gesamte Inject→Beobachten-Schleife auf dem ADMIN-Host aus (eine Uhr, kein ssh-pro-Poll) für
Nkurze Nachrichten (Standard 200) und berichtet Min/Mittelwert/Median/p95/Max sowie die Summe der mittleren Verarbeitungszeit pro Stage als Untergrenze.POLL_MS(Standard 2) ist, wie oft nach der zugestellten Nachricht gesehen wird, und damit die Quantisierung jeder ausgegebenen Zahl; es muss daher klein gegenüber der gemessenen Latenz sein: Ein Poll-Intervall in der Nähe der echten Latenz rundet die Verteilung in wenige Eimer und lässt das Polling anstelle der Pipeline über den p95 entscheiden.``10-goodput-bench.sh`` — Sättigungs-Nutzdurchsatz, drei Lastpfade, zwei Instrumente. Die drei Pfade sind A reine Pipeline (lokales Inject → lokales Maildir), B Netzwerk + Front-End (entfernter Absender → lokales Maildir) und C ausgehendes Relay (autorisierter
ALICE-Ursprung → relay-to-internet → MX vonCAROL). Jeder wird zweimal gemessen:ein Burst-Leerlaufen mit fester Größe (
run_burst) bietetBURST_MSGSNachrichten am Anschlag an und misst, wie lange die Warteschlange zum Leerwerden braucht, und berichtet die Rate sowie CPU, Platten-%utilund Leitungsauslastung des Hosts über genau dieses Fenster;eine Rampe (
run_ramp) verdoppelt die angebotene Last alleSTEP_SECS, bis die Warteschlange überSAT_QUEUEliegt und weiter wächst.
Der Burst ist die Zahl; die Rampe ist die Gestalt. Eine Rampe schreibt Abschlüsse demjenigen festen Fenster zu, in das sie fallen, und einen Generator zu starten kostet einen ssh-Umlauf und einen Python-Start auf dem injizierenden Host — einen großen Teil eines Fünf-Sekunden-Fensters, wenn dieser Host entfernt ist, und einen noch größeren, wenn er klein ist; daher liest die Rampe für denselben Pfad deutlich weniger als der Burst. Lesen Sie die Rampe dafür, wo die Warteschlange zu wachsen beginnt und was der Host dort tut.
Der Burst jedes Szenarios läuft vor der Rampe dieses Szenarios, denn eine Rampe endet absichtlich gesättigt: Sie hinterlässt einen großen Rückstau und ein Maildir mit ebenso vielen Dateien darin, und ein Burst, der auf dieser Erholung gemessen wird, liest weit weniger als einer auf einer beruhigten Maschine.
Ein Burst, der auf ein Postfach auf einem anderen Host zielt, verwendet
BURST_MSGS_REMOTE(2 000) stattBURST_MSGS(20 000) und leert dieses Postfach danach. Beides betrifft das nächste Skript und nicht dieses: Zehntausende an ein kleines Gegenüber relayte Nachrichten lassen es lange Zeit mit Zustellen beschäftigt, und jeder Helfer, der eine Nachricht findet, tut das durch Durchsuchen des Postfachs — ein Gegenüber, das sie noch hält, lässt also die Suchen des nächsten Skripts in einen Timeout laufen, obwohl es jede Nachricht zugestellt hat.Vier Dinge darüber, wie es die Rampe fährt, sollte man wissen, bevor man eine Zahl daraus abliest.
Die Rampe ist geometrisch. Jeder Schritt multipliziert die angebotene Last mit
RAMP_FACTOR(Standard 2), anstatt eine festeRATE_UNITzu addieren. Eine lineare Rampe kann nicht beide Enden des Bereichs bedienen, auf den diese Suite gerichtet wird: Eine kleine VM und ein großer Server erreichen ihr Plateau um Größenordnungen voneinander entfernt, sodass eine Einheit, die das Knie des kleinen Hosts findet, beim großen weit zu kurz aufhört undMAX_STEPSals dessen Obergrenze meldet. Was es kostet, ist Auflösung am Knie — die angebotene Rate, die das Plateau auslöste, ist nur bis auf einen FaktorRAMP_FACTOReingegrenzt. Die nachhaltige Zahl ist davon nicht betroffen: Das ist die gemessene Abschlussrate.RAMP_FACTOR=1ergibt eine lineare Rampe inRATE_UNIT-Schritten.Sättigung ist eine wachsende Warteschlange, keine flache Rate. Die Rampe hält an, wenn die Warteschlange „über
SAT_QUEUEliegt undSTALL_STEPS(Standard 2) aufeinanderfolgende Schritte lang weiter wächst“, denn genau das ist Sättigung — mehr angenommen als abgeschlossen. Ein Plateau der verarbeiteten Rate wäre das falsche Signal: Die Rate pro Fenster schwankt so oder so noch lange weiter, nachdem die Pipeline bereits voll ausgelastet ist, sodass einem Plateau-Test die Schritte ausgehen können, während die Warteschlange schon tief ist.Es berichtet drei Auslastungen, und nur von einer darf extrapoliert werden. CPU, das
%utilder Platte, diePGDATAträgt, und die Leitungsauslastung der Netzwerkkarte werden über jeden Burst und jeden Rampenschritt erfasst. Die CPU folgt der Last; die Leitung ist weit von der Grenze entfernt, könnte es aber auf einer stärker belegten sein; Platten-%utildarf nicht verwendet werden, denn es zählt Wanduhrzeit mit nicht leerer Anfragewarteschlange und nicht Gerätesättigung, und auf einem parallelen NVMe-Gerät muss es mit dem Durchsatz überhaupt nicht steigen. Die extrapolierte Spalte der Zusammenfassung skaliert daher mit der CPU und kappt das Ergebnis auf die beste Rate, die ein rein lokaler Pfad tatsächlich erreicht hat (siehe Hochrechnen über einen entfernten Engpass hinaus).Sie erhöht die Worker-Zahl der Pipeline selbst, nicht nur die Annahmegrenzen:
BENCH_STAGE_PAR(Standard 16) wird für den Lauf alsPARALLELISMin jeden[stage-*]-Abschnitt geschrieben. Der ausgelieferte Standard von 4 existiert, damit eine Installation von der Stange zu einemmax_connectionsvon der Stange passt; ihn zu messen und die Antwort die Obergrenze der Maschine zu nennen heißt, den Standard zu messen. Da jeder Stage-Worker genau eine Datenbankverbindung hält, gibt das Skript anschließendΣ PARALLELISMplus die Pools des Dispatchers und der Server gegen PostgreSQLsmax_connectionsaus und warnt, wenn es nicht passt — ein Worker, der keine Verbindung bekommt, meldet Datenbanküberlast, der Dispatcher reiht wieder ein und bremst, und die Rampe meldet stillschweigend eine niedrigere Zahl, sodass ein ungeprüfter Budgetfehler genau wie eine Kapazitätszahl aussieht.
26.3. Ratenbegrenzungs-Berichterstattung¶
Der parallele Lastgenerator hält die SMTP-Antwort jeder Ablehnung fest, sodass die eigene Front-End-Drosselung von Pepsi ([pepsi-ingress] CONN_RATE_PER_SECOND / CONN_RATE_BURST) als 421s erscheint und mit der zu erhöhenden Stellschraube berichtet wird.
Für den ausgehenden Pfad (Szenario C), der MTAs berührt, die Pepsi nicht kontrolliert, liest das Skript Pepsis Sicht auf den nächsten Hop — state.bounce-SMTP-Codes/-Text, pepsi.tls_session, das Relay-Journal — und meldet, wenn es 4xx- / „rate“/„too many“/„try again“/„throttle“-Aufschübe sieht, externe Ratenbegrenzung mit konkreten Lösungen:
Auf dem entfernten MTA (Postfix): erhöhen Sie
smtpd_client_connection_rate_limit,smtpd_client_message_rate_limit, das*_connection_count_limit, weiten Sieanvil_rate_time_unitaus oder nehmen Sie die IP des Pepsi-Hosts übersmtpd_client_event_limit_exceptions/ einen dedizierten Transport aus.Auf der Pepsi-Seite (um unter einem Limit zu bleiben, das Sie nicht ändern können): senken Sie
[stage-internet] PARALLELISMund takten Sie Wiederholungen überRETRY_INITIAL/RETRY_MAX_INTERVAL/RETRY_FACTOR.
26.4. Nützliche Stellschrauben¶
Skript |
Stellschrauben |
|---|---|
alle |
|
08 |
|
09 |
|
10 |
|