7. Controllo della Congestione (Congestion Control)
Questo documento specifica un controller di congestione lato mittente per QUIC simile a TCP NewReno [RFC6582].
I segnali che QUIC fornisce per il controllo della congestione sono generici e progettati per supportare algoritmi lato mittente diversi. I mittenti possono scegliere unilateralmente di utilizzare un algoritmo diverso, come CUBIC [RFC8312].
Se un mittente utilizza un controller diverso da quello specificato in questo documento, il controller selezionato DEVE (MUST) conformarsi alle linee guida per il controllo della congestione specificate nella Sezione 3.1 di [RFC8085].
Analogamente a TCP, i pacchetti contenenti solo frame ACK non contano nei bytes in flight (byte in transito) e non sono soggetti a controllo di congestione. A differenza di TCP, QUIC può rilevare la perdita di questi pacchetti e PUÒ (MAY) utilizzare tali informazioni per regolare il controller di congestione o la velocità di invio dei pacchetti contenenti solo ACK, ma questo documento non descrive un meccanismo per farlo.
Il controller di congestione è per percorso (per path), pertanto i pacchetti inviati su altri percorsi non modificheranno il controller di congestione del percorso corrente, come descritto nella Sezione 9.4 di [QUIC-TRANSPORT].
Gli algoritmi in questo documento sono specificati in byte e utilizzano la finestra di congestione del controller.
Un endpoint NON DEVE (MUST NOT) inviare un pacchetto che causerebbe un bytes_in_flight (vedere Appendice B.2) maggiore della finestra di congestione, a meno che il pacchetto non venga inviato alla scadenza del timer PTO (vedere Sezione 6.2) o quando si entra nel periodo di recupero (vedere Sezione 7.3.2).
7.1. Notifica Esplicita di Congestione (Explicit Congestion Notification)
Se un percorso è stato convalidato per supportare l'Explicit Congestion Notification (ECN) [RFC3168] [RFC8311], QUIC tratta il codepoint Congestion Experienced (CE, Congestione Sperimentata) nell'intestazione IP come segnale di congestione. Questo documento specifica la risposta di un endpoint quando il conteggio ECN-CE riportato dal peer aumenta; vedere Sezione 13.4.2 di [QUIC-TRANSPORT].
7.2. Finestra di Congestione Iniziale e Minima (Initial and Minimum Congestion Window)
QUIC inizia ogni connessione in slow start (avvio lento) con la finestra di congestione impostata su un valore iniziale. Gli endpoint DOVREBBERO (SHOULD) utilizzare una finestra di congestione iniziale di dieci volte la dimensione massima del datagramma (max_datagram_size), limitando al contempo la finestra al maggiore tra 14.720 byte o due volte la dimensione massima del datagramma. Ciò segue l'analisi e le raccomandazioni in [RFC6928], aumentando il limite di byte per tenere conto dell'overhead di 8 byte dell'UDP che è inferiore all'overhead di 20 byte del TCP.
Se la dimensione massima del datagramma cambia durante una connessione, la finestra di congestione iniziale DOVREBBE (SHOULD) essere ricalcolata con la nuova dimensione. Se la dimensione massima del datagramma viene ridotta per completare l'handshake, la finestra di congestione DOVREBBE (SHOULD) essere impostata sulla nuova finestra di congestione iniziale.
Prima di convalidare l'indirizzo del client, il server potrebbe essere ulteriormente limitato dal limite anti-amplificazione specificato nella Sezione 8.1 di [QUIC-TRANSPORT]. Sebbene il limite anti-amplificazione possa impedire che la finestra di congestione venga utilizzata completamente e quindi rallenti la crescita della finestra di congestione, non influenza direttamente la finestra di congestione.
La finestra di congestione minima è il valore più piccolo che la finestra di congestione può raggiungere in risposta a perdita, aumento del conteggio ECN-CE riportato dal peer o congestione persistente (persistent congestion). Il valore RACCOMANDATO (RECOMMENDED) è 2 * max_datagram_size.
7.3. Stati del Controllo della Congestione (Congestion Control States)
Il controller di congestione NewReno descritto in questo documento ha tre stati distinti, come mostrato nella Figura 1.
Nuovo percorso o +------------+
congestione persistente | Slow Start |
(O)-------------------------->| |
+------------+
|
Perdita o
Aumento ECN-CE
|
v
+------------+ Perdita o +------------+
| Congestion | Aumento ECN-CE | Recovery |
| Avoidance |------------------>| (Recupero) |
+------------+ +------------+
^ |
| |
+----------------------------+
Riconoscimento dei pacchetti
inviati durante il recupero
Figura 1: Stati e Transizioni del Controllo della Congestione
Questi stati e le transizioni tra di essi sono descritti nelle sezioni successive.
7.3.1. Slow Start (Avvio Lento)
Un mittente NewReno è in slow start quando la finestra di congestione è inferiore alla soglia di slow start. Il mittente inizia in slow start perché la soglia di slow start viene inizializzata a un valore infinito.
Mentre il mittente è in slow start, la finestra di congestione aumenta del numero di byte riconosciuti quando viene elaborato ogni riconoscimento. Ciò si traduce in una crescita esponenziale della finestra di congestione.
Il mittente DEVE (MUST) uscire dallo slow start ed entrare nel periodo di recupero quando viene perso un pacchetto o quando il conteggio ECN-CE riportato dal peer aumenta.
Il mittente rientra in slow start ogni volta che la finestra di congestione è inferiore alla soglia di slow start, il che si verifica solo dopo che è stata dichiarata una congestione persistente.
7.3.2. Recovery (Recupero)
Un mittente NewReno entra in un periodo di recupero quando rileva la perdita di pacchetti o quando il conteggio ECN-CE riportato dal peer aumenta. Un mittente che è già in un periodo di recupero rimane al suo interno e non vi rientra.
All'ingresso nel periodo di recupero, il mittente DEVE (MUST) impostare la soglia di slow start alla metà del valore della finestra di congestione al momento del rilevamento della perdita. La finestra di congestione DEVE (MUST) essere impostata al valore ridotto della soglia di slow start prima di uscire dal periodo di recupero.
Le implementazioni POSSONO (MAY) ridurre la finestra di congestione immediatamente all'ingresso nel periodo di recupero o utilizzare altri meccanismi, come Proportional Rate Reduction [PRR], per ridurre gradualmente la finestra di congestione. Se la finestra di congestione viene ridotta immediatamente, è possibile inviare un singolo pacchetto prima della riduzione. Ciò velocizza il recupero dalla perdita se i dati nel pacchetto perso vengono ritrasmessi ed è simile al TCP come descritto nella Sezione 5 di [RFC6675].
Il periodo di recupero ha lo scopo di limitare le riduzioni della finestra di congestione a una per round-trip time. Pertanto, durante il recupero, la finestra di congestione non cambia in risposta a nuove perdite o aumenti del conteggio ECN-CE.
Un periodo di recupero termina e il mittente entra in congestion avoidance (evitamento della congestione) quando viene riconosciuto un pacchetto inviato durante il periodo di recupero. Ciò è leggermente diverso dalla definizione di recupero del TCP, che termina quando il segmento perso che ha avviato il recupero viene riconosciuto [RFC5681].
7.3.3. Congestion Avoidance (Evitamento della Congestione)
Un mittente NewReno è in congestion avoidance ogni volta che la finestra di congestione è uguale o maggiore della soglia di slow start e non è in un periodo di recupero.
Un mittente in congestion avoidance utilizza un approccio di aumento additivo e diminuzione moltiplicativa (Additive Increase Multiplicative Decrease, AIMD) che DEVE (MUST) limitare gli aumenti alla finestra di congestione a non più di una dimensione massima del datagramma per ogni finestra di congestione riconosciuta.
Il mittente esce dalla congestion avoidance ed entra nel recupero quando viene perso un pacchetto o quando il conteggio ECN-CE riportato dal peer aumenta.
7.4. Ignorare la Perdita di Pacchetti Non Decrittabili (Ignoring Loss of Undecryptable Packets)
Durante l'handshake, alcune chiavi di protezione dei pacchetti potrebbero non essere disponibili quando arriva un pacchetto, e il destinatario potrebbe scegliere di eliminare il pacchetto. In particolare, i pacchetti Handshake e 0-RTT non possono essere elaborati fino all'arrivo dei pacchetti Initial, e i pacchetti 1-RTT non possono essere elaborati fino al completamento dell'handshake. Gli endpoint POSSONO (MAY) ignorare la perdita di pacchetti Handshake, 0-RTT e 1-RTT che potrebbero essere arrivati prima che il peer avesse le chiavi di protezione dei pacchetti per elaborarli. Gli endpoint NON DEVONO (MUST NOT) ignorare la perdita di pacchetti inviati dopo il pacchetto riconosciuto più presto in un determinato spazio dei numeri di pacchetto.
7.5. Probe Timeout (Timeout di Sonda)
I pacchetti di sonda NON DEVONO (MUST NOT) essere bloccati dal controller di congestione. Tuttavia, un mittente DEVE (MUST) contare questi pacchetti come aggiuntivi nei byte in transito. Si noti che l'invio di pacchetti di sonda potrebbe causare che i byte in transito del mittente superino la finestra di congestione fino a quando non riceve un riconoscimento che stabilisce la perdita o la consegna del pacchetto, poiché questi pacchetti aggiungono carico di rete senza stabilire perdita di pacchetti.
7.6. Congestione Persistente (Persistent Congestion)
Quando un mittente stabilisce la perdita di tutti i pacchetti inviati in un periodo sufficientemente lungo, si considera che la rete stia sperimentando una congestione persistente (persistent congestion).
7.6.1. Durata (Duration)
La durata della congestione persistente viene calcolata come segue:
(smoothed_rtt + max(4*rttvar, kGranularity) + max_ack_delay) * kPersistentCongestionThreshold
A differenza del calcolo del PTO nella Sezione 6.2, questa durata include max_ack_delay indipendentemente dallo spazio dei numeri di pacchetto in cui è stata stabilita la perdita.
Questa durata consente a un mittente di inviare il maggior numero possibile di pacchetti prima di stabilire la congestione persistente, incluse le risposte alle scadenze del PTO, simile a come il TCP fa con le Tail Loss Probes [RFC8985] e gli RTO [RFC5681].
Un valore grande per kPersistentCongestionThreshold fa sì che il mittente sia meno reattivo alla congestione persistente nella rete, il che può provocare l'invio aggressivo in una rete congestionata. Valori troppo piccoli possono far sì che il mittente dichiari inutilmente la congestione persistente, risultando in un throughput ridotto del mittente.
Il valore RACCOMANDATO (RECOMMENDED) per kPersistentCongestionThreshold è 3, il che si traduce in un comportamento approssimativamente equivalente a un mittente TCP che dichiara un RTO dopo 2 TLP.
Questo design non stabilisce la congestione persistente utilizzando eventi PTO consecutivi perché i modelli dell'applicazione influenzano le scadenze del PTO. Ad esempio, un mittente che invia piccole quantità di dati con periodi di silenzio tra di loro riavvia il timer PTO a ogni invio, il che potrebbe impedire che il timer PTO scada per un lungo periodo, anche se non riceve riconoscimenti. L'uso della durata consente a un mittente di stabilire la congestione persistente senza fare affidamento sulle scadenze del PTO.
7.6.2. Stabilire la Congestione Persistente (Establishing Persistent Congestion)
Dopo aver ricevuto un riconoscimento, un mittente stabilisce la congestione persistente se due pacchetti ack-eliciting (che richiedono riconoscimento) vengono dichiarati persi e:
-
Attraverso tutti gli spazi dei numeri di pacchetto, nessun pacchetto inviato tra i tempi di invio di questi due pacchetti è stato riconosciuto.
-
La durata tra i tempi di invio di questi due pacchetti supera la durata della congestione persistente (Sezione 7.6.1). E
-
Un campione RTT precedente esisteva quando questi due pacchetti sono stati inviati.
Questi due pacchetti DEVONO (MUST) essere ack-eliciting, poiché un destinatario deve riconoscere solo i pacchetti ack-eliciting entro il ritardo massimo di riconoscimento; vedere Sezione 13.2 di [QUIC-TRANSPORT].
Il periodo di congestione persistente NON DOVREBBE (SHOULD NOT) iniziare finché non c'è almeno un campione RTT. Prima del primo campione RTT, un mittente imposta il timer PTO in base all'RTT iniziale (Sezione 6.2.2), che potrebbe essere sostanzialmente maggiore dell'RTT effettivo. Richiedendo un campione RTT precedente si impedisce a un mittente di stabilire la congestione persistente con potenzialmente troppo poche sonde.
Poiché il periodo di congestione persistente viene stabilito nella perdita di soli due pacchetti ack-eliciting, un mittente potrebbe dichiarare erroneamente la congestione persistente se un Ack è perso anche se i pacchetti continuano ad arrivare al destinatario. Una connessione è più resiliente alle Acks perse o ritardati se il mittente invia più pacchetti ack-eliciting prima che il timer PTO scada. Le implementazioni che rilevano ripetutamente congestione persistente possono voler abilitare QUIC's Acknowledgment Frequency extension (estensione della frequenza di riconoscimento) [ACK-FREQUENCY].
Quando la congestione persistente viene stabilita, la finestra di congestione del mittente DEVE (MUST) essere ridotta al minimo della finestra di congestione e della finestra di congestione minima. Questa risposta di congestione persistente equivale a quella di un mittente TCP che ha esperienze di ripetuti timeout di ritrasmissione (RTO) [RFC5681].
7.7. Pacing (Ritmo di Invio)
Si raccomanda (RECOMMENDED) che un mittente ritmizzi (pace) l'invio dei suoi pacchetti. Pacing è l'atto di distribuire l'invio dei pacchetti nel tempo per evitare brevi raffiche di pacchetti che causano perdite.
Un mittente DOVREBBE (SHOULD) ritmizzare i pacchetti utilizzando una velocità di pacing pari a congestion_window / smoothed_rtt, dove congestion_window è la finestra di congestione corrente del controller di congestione in byte e smoothed_rtt è l'RTT smoothed corrente.
Per evitare che una connessione inattiva non abbia alcun RTT smoothed recente per calcolare il ritmo di invio, l'RTT iniziale (Sezione 6.2.2) DOVREBBE (SHOULD) essere usato fino a quando non viene calcolato un RTT smoothed.
Quando l'invio in burst è possibile, come ad esempio quando sono state calcolate nuove stime RTT o quando il timer PTO è scaduto, le implementazioni possono inviare in burst temporaneamente per sfruttare la capacità di rete disponibile. Le implementazioni DOVREBBERO (SHOULD) utilizzare un burst iniziale non superiore a 10 pacchetti; vedere Sezione 7.2. Si raccomanda (RECOMMENDED) di ritmizzare i successivi burst di pacchetti e ridurre i burst in burst più piccoli man mano che più stime RTT sono disponibili.
Un mittente con conoscenza della rete disponibile PUÒ (MAY) utilizzare tale conoscenza come input alla propria scelta di una velocità di pacing. Tuttavia, i mittenti devono prestare attenzione a non inviare pacchetti a una velocità che causerebbero perdite sulla rete.
Le implementazioni DOVREBBERO (SHOULD) prendere in considerazione entrambe le limitazioni del controller di congestione e del ritmo di invio quando decidono quando inviare pacchetti. Un mittente PUÒ (MAY) scegliere un ritmo di invio conservativo per evitare di causare perdite se la finestra di congestione è disponibile.
7.8. Understaffed (Sottoutilizzo)
Quando i byte in transito sono inferiori alla finestra di congestione e l'invio non è ritimizzato, si dice che la finestra di congestione è sottoutilizzata (understaffed). Ciò può accadere per vari motivi, ad esempio se l'applicazione non ha dati da inviare, se la finestra di controllo del flusso è più piccola della finestra di congestione o se la velocità di ritmo di invio limita il numero di byte inviati.
Un mittente PUÒ (MAY) utilizzare il sottoutilizzo come segnale per ridurre la finestra di congestione a un valore più adatto all'applicazione o alla velocità di rete. Tuttavia, i mittenti devono prestare attenzione a non ridurre eccessivamente la finestra di congestione, poiché ciò potrebbe limitare la capacità di sfruttare la capacità di rete disponibile quando l'applicazione inizia a inviare più dati.
Un mittente in slow start NON DOVREBBE (SHOULD NOT) ridurre la finestra di congestione a causa del sottoutilizzo, poiché lo slow start è progettato per stabilire rapidamente la capacità di rete disponibile.
Quando la finestra di congestione è sottoutilizzata per un periodo prolungato e la finestra di congestione non cresce, un mittente PUÒ (MAY) implementare un controllo di congestione che permetta una crescita più rapida quando la finestra di congestione era precedentemente sottoutilizzata. Hystart++ [HYSTART] è un esempio di tale algoritmo. Tuttavia, i mittenti devono prestare attenzione a non crescere troppo rapidamente la finestra di congestione, poiché ciò potrebbe causare perdite.