I banchi
Data: 24 luglio 2026 · Protocollo: MQTT 3.1.1 · Strumento: client di carico interno cmd/loadtest costruito sul codec del progetto, senza librerie MQTT di terze parti.
Le prove sono state fatte su due banchi per separare il comportamento del broker stesso dall'influenza dell'ambiente: rete contro loopback, hardware modesto contro hardware potente.
| Banco A — via rete | Banco B — loopback | |
|---|---|---|
| Processore del broker | 12 core | 2× Xeon E5-2690 v4 a 2,6 GHz (28 core / 56 thread) |
| Memoria del broker | 16 GB | 128 GB |
| Indirizzo | 192.168.1.84:1883 (LAN) | 127.0.0.1:1883 (loopback) |
| Generatore di carico | Macchina separata sulla LAN | La stessa macchina del broker |
| Porte effimere del client | 16 384 (predefinito) | 55 000 (allargato) |
Sintesi
- Latenza. Via rete, la latenza end-to-end «publisher → broker → subscriber» sotto carico normale si aggira sul 1 ms; in loopback è di 73 µs (~16 volte meno, tolta la rete).
- Connessioni. Via rete il broker ha retto 10 000 sessioni senza errori (oltre, abbiamo urtato il limite di porte del client). Su hardware potente in loopback: 30 000 senza errori e ~41 000 in un tentativo da 50 000 (il limite di una sola macchina, non del broker).
- Ingestione. Il picco via rete è stato di ≈277 000 messaggi/s; in loopback l'ingestione è più bassa (≈185 000/s) perché il generatore contende i core al broker.
- Fan-out (instradamento). Su hardware potente il broker ha consegnato ≈1 350 000 messaggi/s agli iscritti (fan-out ×100) contro ≈22 000/s via rete.
- Robustezza. Su nessuno dei due banchi il broker si è bloccato o è andato in crash. In sovraccarico i messaggi QoS 0 diretti a iscritti in ritardo vengono scartati in modo ordinato; il log del broker non contiene un solo errore in tutte le prove.
Capacità di connessioni simultanee
Ogni connessione è un ciclo completo CONNECT → CONNACK con autenticazione; poi la sessione viene tenuta aperta (verificata con PINGREQ).
Banco A — via rete
| Obiettivo | Stabilite | Fallite | Instaurazione p50 | p99 |
|---|---|---|---|---|
| 10 | 10 | 0 | 146 ms | 162 ms |
| 100 | 100 | 0 | 702 ms | 1,12 s |
| 1000 | 1000 | 0 | 4,21 s | 9,78 s |
| 5000 | 5000 | 0 | 4,64 s | 8,59 s |
| 10 000 | 10 000 | 0 | 4,92 s | 10,52 s |
| 15 000 | 14 879 | 121 | 7,43 s | 14,09 s |
Banco B — loopback (salita graduale)
| Obiettivo | Stabilite | Fallite | Instaurazione p50 | p99 |
|---|---|---|---|---|
| 1000 | 1000 | 0 | 886 ms | 1,06 s |
| 10 000 | 10 000 | 0 | 903 ms | 1,55 s |
| 30 000 | 30 000 | 0 | 923 ms | 1,90 s |
| 50 000 | 41 219 | 0 | 984 ms | 1,90 s |
Analisi
- Via rete, fino a 10 000 senza un solo errore; i 121 fallimenti a 15 000 sono l'esaurimento delle porte effimere del client (16 384). È il limite del generatore, non del broker.
- In loopback, dopo aver allargato l'intervallo di porte, il broker ha retto 30 000 sessioni senza difetti; a 50 000 ne restavano vive circa 41 000 alla fine del periodo di mantenimento, perché entrambi i lati condividono una macchina. Il log del broker è rimasto pulito per tutto il tempo, senza errori.
- Osservazione collaterale. In loopback una raffica di connessioni troppo brusca (1000
dialsimultanei) provocaconnection refused: la coda di accettazione trabocca, perché senza ritardo di rete tutti i SYN arrivano insieme. Una salita graduale (200 per volta) elimina del tutto l'effetto. Su una rete la latenza attenua la raffica da sola.
Portata in ingestione
Un gruppo di publisher pubblica il più velocemente possibile; si misura il ritmo di ingestione del broker.
QoS 0 · payload di 64 byte
| Publisher | Banco A (rete) | Banco B (loopback) |
|---|---|---|
| 10 | 271 006/s | 185 012/s |
| 100 | 236 422/s | 101 457/s |
| 500 | 103 546/s | 125 324/s |
L'ingestione è più alta via rete — non perché il broker del banco A sia più veloce, ma perché lì il generatore gira su una macchina separata. In loopback client e broker condividono gli stessi core, il che limita il ritmo complessivo.
QoS 1 (con conferma PUBACK) · 64 B
| Publisher | Banco A (rete) | Banco B (loopback) |
|---|---|---|
| 10 | 10 292/s | 63 606/s |
| 100 | 22 085/s | 48 983/s |
Il client di prova per QoS 1 è sincrono (aspetta un PUBACK dopo ogni messaggio), quindi è vincolato al tempo di andata e ritorno. In loopback quel tempo è quasi nullo, da cui il guadagno di circa 6 volte. Questo mostra con chiarezza che i numeri di QoS 1 sono dettati dalla latenza di rete, non dal broker.
Scansione delle dimensioni dei messaggi · QoS 0 · 50 publisher
| Dimensione | Banco A (rete) | Banco B (loopback) |
|---|---|---|
| 64 B | 277 198/s · 17,7 MB/s | 101 961/s · 6,5 MB/s |
| 256 B | 124 997/s · 32,0 MB/s | 102 029/s · 26,1 MB/s |
| 1024 B | 38 458/s · 39,4 MB/s | 102 151/s · 104,6 MB/s |
Due tetti diversi:
- Via rete si urta il collegamento verso ≈40 MB/s: quando il messaggio cresce, i msg/s calano mentre i MB/s restano vicini al limite.
- In loopback il collegamento è enorme, così il numero di messaggi resta stabile (~102 000/s con 50 connessioni) e il volume sale a 104,6 MB/s. Qui il collo di bottiglia è l'elaborazione per messaggio (CPU), non i byte.
Consegna end-to-end
Nel corpo del messaggio viene inserito un marcatore temporale; si misurano la consegna reale e la latenza end-to-end. Fan-out significa consegna a ogni iscritto il cui filtro corrisponde.
Banco A — via rete
| Scenario | Pubblicazione | Consegna | media / p50 / p99 |
|---|---|---|---|
| 10 sub / 10 pub / 100 msg/s — normale | 986/s | 9863/s | 1,17 / 1,0 / 3,6 ms |
| 100 sub / 10 pub / senza freno | 261 551/s | 22 351/s | 780 / 453 / 678 ms |
| 100 sub / 10 pub / 500 msg/s | 4934/s | 12 150/s | 1,87 / 1,77 / 2,83 s |
| 1000 sub / 5 pub / 100 msg/s | 494/s | 178 961/s | 2,29 s / 480 / 952 ms |
Banco B — loopback
| Scenario | Pubblicazione | Consegna | media / p50 / p99 |
|---|---|---|---|
| 10 sub / 10 pub / 100 msg/s — normale | 987/s | 9867/s | 73 / ~0 / 555 µs |
| 100 sub / 10 pub / senza freno | 47 557/s | 1 353 431/s | 514 / 135 / 251 ms |
| 100 sub / 10 pub / 500 msg/s | 3608/s | 360 834/s | 17,1 / 10,5 / 71,6 ms |
| 1000 sub / 5 pub / 100 msg/s | 494/s | 183 746/s | 4,73 / 4,54 / 15,1 ms |
Analisi
- Latenza sotto carico normale: 1,17 ms via rete contro 73 µs in loopback. La differenza è il tragitto di rete.
- Instradamento sotto carico: la macchina potente ha consegnato ≈1,35 milioni di messaggi/s agli iscritti (fan-out ×100) — due ordini di grandezza sopra il banco di rete (22 mila/s), dove sia il broker sia il collegamento sono piccoli.
- Negli scenari a fan-out elevato anche il generatore diventa il collo di bottiglia (tutti gli iscritti in un processo su una macchina), quindi i picchi di consegna sono un limite inferiore di ciò che il broker sa fare.
Confronto diretto
| Indicatore | Banco A — rete (12 core) | Banco B — loopback (28 core) |
|---|---|---|
| Connessioni senza errori | 10 000 | 30 000 |
| Picco di connessioni | 14 879 (limite del client) | ~41 000 (limite di una macchina) |
| Ingestione QoS 0 (64 B), picco | 271 006/s | 185 012/s |
| Ingestione QoS 1 (64 B), picco | 22 085/s | 63 606/s |
| Tetto di volume | ≈40 MB/s (collegamento) | ≈105 MB/s (CPU) |
| Latenza (normale), mediana | 1,0 ms | ~0 (73 µs in media) |
| Consegna con fan-out, picco | 22 351/s | 1 353 431/s |
| Errori nel log del broker | nessuno | nessuno |
Riserve di metodo
- Il ritmo di ingestione non è direttamente confrontabile tra i banchi: sul banco B generatore e broker condividono i core; sul banco A il generatore è una macchina separata.
- Su ogni banco il carico proviene da una sola macchina client; in alcuni scenari (fan-out ampio, decine di migliaia di connessioni) il collo di bottiglia è quella macchina e non il broker.
- Il client di prova per QoS 1 è sincrono — i numeri di QoS 1 sottostimano il potenziale del broker.
- Le metriche di sistema del broker (CPU e memoria durante le prove) non sono state raccolte; lo stato del server si deduce dal suo log (nessun errore) e dal comportamento lato client.
Riprodurre
Ogni prova parte da un unico binario loadtest; di seguito i comandi delle tre modalità.
go build -o loadtest ./cmd/loadtest
# Capacità di connessioni (salita graduale contro la coda di accettazione)
./loadtest -addr HOST:1883 -user myelx -pass *** \
-mode conn -levels 1000,10000,30000,50000 -hold 3s -dial-concurrency 200
# Portata in ingestione
./loadtest -addr HOST:1883 -user myelx -pass *** \
-mode pub -pubs 10 -qos 0 -payload 64 -duration 10s
# Consegna end-to-end e latenza
./loadtest -addr HOST:1883 -user myelx -pass *** \
-mode pubsub -subs 10 -pubs 10 -qos 0 -rate 100 -payload 64 -duration 10s