Test di carico SMTP con destinatari numerati: inviare ogni email in modo tracciabile
Un test di carico è valido solo quanto la sua valutazione. Con l'opzione -N, smtp-source numera ogni email tramite l'indirizzo del destinatario senza sacrificare il throughput. Come strutturare l'esecuzione, quante sessioni hanno senso e come individuare automaticamente i numeri mancanti.
Chi esegue un test di carico vuole poter rispondere a due domande al termine: tutte le email sono arrivate e, in caso contrario, quali mancano? Con email di test identiche si può solo contare, e un contatore che indica 13 messaggi mancanti non dice quando e dove siano andati persi. Se invece ogni email porta un numero progressivo, il conteggio diventa un confronto: ogni numero può essere individuato singolarmente nei log del sistema di destinazione, le lacune mostrano il momento della perdita e si può verificare l’ordine di consegna.
La reazione istintiva più diffusa è uno script che incrementa l’oggetto. Funziona, ma riduce il throughput, perché il generatore di carico smtp-source del pacchetto Postfix imposta l’oggetto in modo fisso per ogni chiamata e un ciclo con una chiamata per email impone una nuova connessione per ogni messaggio. L’identificativo migliore del messaggio è già integrato: l’opzione -N numera l’indirizzo del destinatario per ogni messaggio, all’interno di una singola chiamata con sessioni parallele. Per la valutazione, l’indirizzo del destinatario è utile quanto l’oggetto, poiché è presente in ogni log di tracciamento.
Questa configurazione di test, a differenza di un puro test funzionale di loopback, invia tramite la rete a un altro sistema. Se sul sistema sorgente non è installato Postfix, l’articolo smtp-source senza installazione di Postfix mostra come estrarre gli strumenti dall’RPM.
Le principali opzioni di smtp-source
Per orientarsi, ecco le opzioni trattate in questo articolo, tradotte liberamente dalla manpage:
L’elenco completo, comprese le opzioni TLS, LMTP e di timing, è disponibile nella manpage di smtp-source(1); la controparte per il lato ricevente è smtp-sink(1) e viene utilizzata più avanti nella valutazione.
Come -N numera i destinatari
-N attiva un contatore per processo integrato nell’indirizzo del destinatario. Tre caratteristiche determinano la configurazione del test e tutte e tre sono documentate nel codice sorgente di smtp-source.c:
In primo luogo, la forma esatta dell’indirizzo dipende dalla versione di Postfix. Postfix 3.5, distribuito da RHEL 8, antepone il numero all’intero indirizzo (RCPT TO:<%d%s>): da -t test@example.com diventano 1test@example.com, 2test@example.com e così via, e il contatore inizia da 1. Le versioni attuali di Postfix aggiungono invece il numero alla fine della parte locale e iniziano da 0 (test0@ fino a test49999@); per questa variante la manpage raccomanda l’indirizzamento con più (-t 'test+@example.com' diventa test+0@ e successivi), affinché un sistema di destinazione con sottoindirizzamento assegni tutto alla stessa casella di posta. Prima dell’esecuzione estesa, verificate il formato con una manciata di email verso un smtp-sink oppure nel log della destinazione; da questo dipendono l’insieme atteso e il modello di ricerca per la valutazione.
In secondo luogo, il contatore è condiviso a livello di processo da tutte le sessioni parallele. Con -s 8, le otto sessioni assegnano insieme i numeri e ogni numero compare esattamente una volta. L’ordine tra le sessioni non è deterministico, ma la completezza dell’insieme dei numeri è garantita.
In terzo luogo, il valore iniziale non è configurabile: 1 con Postfix 3.5, 0 con le versioni attuali. Le email hanno quindi i numeri da 1 al numero totale definito da -m, oppure da 0 al totale meno 1, e l’insieme atteso per il confronto deve corrispondere.
L’esecuzione del test in una chiamata
Il numero di email incluse nell’esecuzione non cambia la procedura; -m determina il totale e gli esempi di questo articolo usano 50’000 come segnaposto arbitrario.
smtp-source -c -d -N -s 8 -m 50000 -l 5120 \
-f lasttest@example.com \
-t test@example.com \
gateway.example.com:25
-d è determinante per il profilo di carico: senza questa opzione, smtp-source chiude la connessione dopo ogni messaggio e ne apre una nuova per il successivo; con -d, le otto connessioni restano aperte e consegnano in successione tutti i messaggi, come farebbe un mittente di massa.
Manca deliberatamente l’opzione -v, nota dai test funzionali: registra ogni singolo dialogo SMTP da HELO a QUIT e, in una grande esecuzione, genera centinaia di migliaia di righe di log senza valore aggiunto per la valutazione. -c fornisce invece il riepilogo, dal quale è possibile seguire in tempo reale l’avanzamento dell’esecuzione. Un time anteposto fornisce la durata complessiva per calcolare la velocità.
Prerequisito per l’intero approccio: il sistema di destinazione accetta gli indirizzi generati. Sono adatti un smtp-sink, un dominio catch-all, un dominio di scarto del provider oppure un gateway che risolva i destinatari solo dopo l’accettazione. Se invece la destinazione verifica ogni destinatario rispetto a una directory, rifiuta gli indirizzi numerati e resta soltanto la variante con l’oggetto.
Impostare header personalizzati
Alcuni test di carico richiedono un header personalizzato, ad esempio come marcatore con cui il gateway riconosce le email di test o per applicare una regola. smtp-source non dispone di un’opzione per questo, ma -F legge da un file un messaggio completamente preformattato, nel quale può comparire qualsiasi header desiderato. Il file è composto dalle righe di header, una riga vuota e il body, con tutte le righe terminate da \r\n:
{ printf 'X-Lasttest: aktiv\r\n'
printf 'Subject: Lasttest\r\n'
printf '\r\n'
head -c 5120 /dev/zero | tr '\0' 'x'
printf '\r\n'; } > lasttest.eml
smtp-source -c -d -N -s 8 -m 50000 -F lasttest.eml \
-f lasttest@example.com \
-t test@example.com \
gateway.example.com:25
Due conseguenze: -F sostituisce -l e -S, perché dimensione e oggetto provengono ora dal file (perciò entrambi devono esservi inclusi). -N resta invece attivo e i destinatari continuano a essere numerati; l’header è identico in tutti i messaggi, poiché proviene dal file fisso.
Quante sessioni?
Il modo più affidabile per determinare il numero di sessioni adatto è misurare, usando esattamente le stesse opzioni previste per l’esecuzione principale: stessa sorgente del messaggio (lo stesso file -F oppure lo stesso -l), stesso mittente, stessa destinazione. Solo la quantità viene ridotta a 2’000 per livello e si varia -s. Una breve esecuzione di calibrazione con un numero crescente di sessioni mostra da quando sessioni aggiuntive non apportano più benefici:
for s in 1 2 4 8 16 32; do
t0=$(date +%s%N)
smtp-source -d -N -s "$s" -m 2000 -F lasttest.eml \
-f lasttest@example.com -t '@blackhole.example.com' \
gateway.example.com:25
t1=$(date +%s%N)
echo "$s Sessions: $(( 2000000000000 / (t1 - t0) )) Mails/s"
done
Due dettagli della chiamata: si rinuncia deliberatamente a -c, affinché tra le righe di misurazione non compaiano contatori in corso; il ciclo restituisce esattamente una riga di risultato per livello. Inoltre, la parte locale vuota in -t funziona bene con la numerazione su un dominio di scarto: con il contatore anteposto di Postfix 3.5 si ottengono indirizzi destinatario puramente numerici (1@blackhole.example.com, 2@…), che mantengono chiara la valutazione nei log.
Nel dettaglio avviene quanto segue: il ciclo esterno percorre i numeri di sessione da 1 a 32, raddoppiandoli a ogni passaggio. Prima e dopo ogni esecuzione, date +%s%N registra l’ora corrente come un numero grande, ovvero i secondi Unix seguiti direttamente dalla componente in nanosecondi. Nel mezzo, smtp-source invia 2’000 messaggi (contenuto, header e dimensione provengono dal file -F) attraverso il rispettivo numero di connessioni parallele che, grazie a -d, restano aperte; il ciclo attende finché la chiamata non è completamente terminata. La riga echo converte la differenza temporale in una velocità: 2’000 email divisi per la durata in secondi, mentre la durata è espressa in nanosecondi. Da 2’000 per 10⁹ risulta quindi la costante 2000000000000. L’aritmetica Bash $(( )) usa numeri interi e tronca le cifre decimali, cosa sufficientemente precisa per questa misurazione.
Tre indicazioni pratiche: %N fornisce i nanosecondi solo con GNU date (come su RHEL e sulla maggior parte dei sistemi Linux; BusyBox e macOS non lo supportano). L’esecuzione completa invia 6 × 2’000 = 12’000 email, che necessitano anch’esse di un indirizzo destinatario controllato, e la numerazione -N ricomincia dal valore iniziale a ogni chiamata. Se una chiamata di smtp-source si interrompe con un messaggio d’errore, la velocità di quella riga non ha significato; correggete prima la causa, poi misurate di nuovo.
L’output previsto è una riga per livello. Con valori di esempio inventati ma tipici, appare così:
1 Sessions: 11 Mails/s
2 Sessions: 21 Mails/s
4 Sessions: 40 Mails/s
8 Sessions: 71 Mails/s
16 Sessions: 79 Mails/s
32 Sessions: 80 Mails/s
L’interpretazione: finché la velocità raddoppia approssimativamente con il numero di sessioni, le sessioni parallele coprono il tempo di attesa delle risposte della destinazione; il collo di bottiglia è quindi la latenza del percorso, non la capacità. Dal punto in cui la curva si appiattisce (nell’esempio tra 8 e 16 sessioni), il sistema di destinazione è saturo oppure la sorgente ha raggiunto il proprio limite. Scegliete il valore più piccolo per cui la velocità non aumenta più in modo significativo, nell’esempio quindi da 8 a 16; ulteriori sessioni aumentano solo il carico dovuto al parallelismo, non il throughput. Per l’esecuzione principale, dalla velocità misurata si può anche stimare subito la durata prevista: il totale definito da -m diviso per la velocità.
Valutazione sul lato ricevente
Se sul sistema di destinazione è disponibile un destinatario di test dedicato, smtp-sink si occupa anche della registrazione:
smtp-sink -c -d "mails/%Y%m%d-%H%M%S." 0.0.0.0:2525 200
Dopo l’esecuzione, estraete i numeri ricevuti e confrontateli con l’insieme atteso. Poiché i numeri non hanno zeri iniziali, entrambe le liste vengono portate a una lunghezza fissa prima del confronto, affinché l’ordinamento alfabetico di comm corrisponda a quello numerico. Il modello di ricerca corrisponde al formato di indirizzo di Postfix 3.5 (numero prima dell’indirizzo); per le versioni attuali usare invece test[0-9]+@ e seq a partire da 0:
grep -rhoE '[0-9]+test@example\.com' mails/ | \
grep -oE '^[0-9]+' | sort -u | \
awk '{printf "%08d\n", $1}' | sort > empfangen.txt
seq 1 50000 | awk '{printf "%08d\n", $1}' | \
comm -23 - empfangen.txt
comm -23 restituisce esattamente i numeri presenti nell’insieme atteso ma non nella lista di ricezione: le email mancanti. Un output vuoto significa consegna completa. Se alcuni numeri compaiono due volte (riconoscibile dalla differenza tra sort e sort -u), un sistema lungo il percorso ha duplicato il messaggio, e anche questo è un risultato rilevante.
Se la destinazione è un sistema vicino alla produzione invece di un smtp-sink, il suo logging assume il ruolo dei file di dump. Su un server Exchange, ad esempio, Get-MessageTrackingLog -Recipients oppure un filtro sull’indirizzo del destinatario fornisce i numeri arrivati; su un sistema Postfix, un grep su to= e sull’indirizzo base nel maillog. Questo è precisamente il vantaggio del numero nell’indirizzo: il destinatario compare in ogni tracciamento dei messaggi, mentre l’oggetto può mancare a seconda del sistema oppure deve prima essere attivato.
Quando il numero deve essere nell’oggetto
Alcune valutazioni dipendono dall’oggetto, ad esempio quando il sistema di destinazione riscrive gli indirizzi dei destinatari o i log mostrano il destinatario soltanto mascherato. In tal caso resta la variante a ciclo: una chiamata smtp-source per email con -m 1 e un oggetto incrementato dalla shell, distribuita su più worker paralleli con intervalli di numeri contigui.
worker() {
local i
for ((i = $1; i <= $2; i++)); do
smtp-source -s 1 -m 1 -l 5120 \
-S "$(printf 'Lasttest %05d' "$i")" \
-f lasttest@example.com -t test@example.com \
gateway.example.com:25 || echo "$i" >> fehlend.log
done
}
for w in 0 1 2 3; do
worker $(( w * 12500 + 1 )) $(( (w + 1) * 12500 )) &
done
wait
Il prezzo è una connessione completa per email: handshake TCP, banner, HELO, invio, QUIT. Questa esecuzione non misura quindi il throughput massimo del sistema di destinazione, ma un caso deliberatamente ad alta intensità di connessioni. Il numero di worker viene determinato in modo analogo all’esecuzione di calibrazione precedente, solo con il ciclo dei worker al posto di -s. Gli zeri iniziali nell’oggetto evitano la riformattazione necessaria per il confronto nella variante -N.
Regole per test contro altri sistemi
Non appena il test lascia il proprio sistema, si applicano tre condizioni. Primo: il gestore del sistema di destinazione ne è informato e ha approvato la finestra temporale; per qualsiasi monitoraggio, un test di carico appare come un attacco o un’ondata di spam. Secondo: l’indirizzo del destinatario termina in modo controllato, in una casella di posta di test dedicata, in una regola di scarto sulla destinazione o in un dominio di scarto predisposto dal provider; gli indirizzi produttivi non devono essere usati in un test di carico. Terzo: prima dell’avvio viene definito un criterio di interruzione, ad esempio una coda in crescita sulla destinazione o un tasso di errore oltre una soglia, e qualcuno osserva questi valori durante l’esecuzione.
Con questi tre punti e la numerazione, al termine l’esecuzione non fornisce solo un valore di throughput, ma un’affermazione dimostrabile: quali email sono arrivate, quali mancano e dove sono state viste per l’ultima volta lungo il percorso.

Commenti
I commenti vengono caricati da GitHub / Giscus.