Contrôle de congestion HTTP/3 QUIC : 5 stratégies clés pour le réglage en production de BBR v2 vs Cubic

网络协议

Les points sensibles du contrôle de congestion : la logique TCP ne fonctionne pas pour QUIC

Le contrôle de congestion TCP traditionnel ne se transpose pas bien à QUIC : Le contrôle de congestion TCP n'est pas adapté à QUIC — QUIC implémente le contrôle de congestion en espace utilisateur ; les algorithmes TCP du noyau ne peuvent pas être réutilisés directement ; Le dilemme du choix entre BBR et Cubic — BBR v2 offre un débit élevé mais soulève des problèmes d'équité, Cubic est stable mais sous-utilise la bande passante ; Faible utilisation de la bande passante — Cubic n'utilise que 60 % à 70 % de la bande passante dans les scénarios à faible perte et à large bande ; Mauvais débit sur les réseaux à forte latence — Sur les liaisons intercontinentales avec un RTT > 200 ms, la croissance de la fenêtre de Cubic est extrêmement lente, et le débit tombe bien en dessous du BDP. En 2026, les nœuds périphériques CDN mondiaux dépassent 5 000, le trafic QUIC représente plus de 35 %, et le choix du contrôle de congestion détermine directement l'expérience utilisateur.

Aperçu des concepts clés

Concept Description
Contrôle de congestion Mécanisme algorithmique qui ajuste dynamiquement le débit d'envoi en fonction de la congestion du réseau
BBR v2 Contrôle de congestion basé sur un modèle utilisant la bande passante et le RTT ; la v2 corrige les problèmes d'équité et de réponse à la perte
Cubic Contrôle de congestion basé sur la perte utilisant une fonction cubique pour la croissance de la fenêtre ; algorithme par défaut de Linux
Reno Plus ancien algorithme de contrôle de congestion avec AIMD (augmentation linéaire, diminution multiplicative)
BDP (produit bande passante–délai) Bande passante × RTT ; détermine le volume maximal de données en vol dans le tube réseau
RTT Temps aller-retour ; BBR utilise la détection du RTT minimal pour déterminer le débit d'envoi
Récupération de perte Détection précise des pertes et retransmission sélective basées sur les ACK de QUIC
ECN Notification explicite de congestion ; les routeurs marquent la congestion au lieu de supprimer les paquets
Pacing Envoi régulier ; répartit les données uniformément sur le RTT pour éviter les rafales
cwnd Fenêtre de congestion ; quantité maximale de données que l'émetteur peut transmettre avant de recevoir un ACK

Cinq défis majeurs

  1. Stratégie de sélection d'algorithme : BBR v2 améliore le débit de 40 % dans les scénarios à faible perte et à large bande, mais peut préempter la bande passante en coexistence avec Cubic ; Cubic est plus stable dans les scénarios sans fil à forte perte mais a une faible utilisation de la bande passante
  2. Controverse sur l'équité de BBR : BBR v1 était injuste envers le trafic Cubic ; la v2 améliore la situation mais nécessite toujours la coopération ECN ; dans les environnements multi-locataires, BBR peut affamer le trafic voisin
  3. Réglage des réseaux à forte latence : Sur les liaisons intercontinentales avec RTT > 200 ms, la croissance de la fenêtre de Cubic est lente, et la phase Startup de BBR peut surexploiter les tampons provoquant des pics de délai de file d'attente
  4. Adaptabilité aux réseaux sans fil : Les réseaux 4G/5G ont des taux de perte fluctuants (0,1 %–5 %) ; BBR interprète à tort la perte comme une congestion provoquant des baisses de débit, Cubic sur-réagit et gaspille la bande passante
  5. Surveillance et métriques : Les métriques de contrôle de congestion QUIC (cwnd, débit de pacing, octets en vol) doivent être exportées au niveau applicatif ; les métriques du noyau traditionnelles ne sont pas disponibles

Stratégie 1 : Configuration de l'algorithme de congestion QUIC de Nginx

# nginx.conf - QUIC congestion control complete configuration
http {
    server {
        listen 443 quic reuseport;
        listen 443 ssl;
        http2 on;
        server_name example.com;

        ssl_certificate     /etc/nginx/ssl/server.crt;
        ssl_certificate_key /etc/nginx/ssl/server.key;
        ssl_protocols       TLSv1.3;

        add_header Alt-Svc 'h3=":443"; ma=86400';

        # Congestion control algorithm: bbr | cubic
        quic_congestion_control bbr;

        # Initial congestion window (bytes), default 10 MSS
        quic_initial_congestion_window 32768;

        # Loss detection threshold (packets)
        quic_loss_detection_threshold 3;

        # Maximum congestion window (bytes), limit bursts
        quic_max_congestion_window 16777216;

        # Enable ECN support
        quic_enable_ecn on;

        # Pacing configuration
        quic_pacing_enabled on;

        location / {
            proxy_pass http://backend;
        }
    }
}
# Verify configuration
nginx -t && systemctl reload nginx

# Check current congestion control status
curl --http3 https://example.com -v 2>&1 | grep -i "congestion"

# Use qlog to analyze congestion control behavior
# Requires Nginx compiled with --with-http_quic_module

Stratégie 2 : Réglage des paramètres BBR v2

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/quic-go/quic-go"
	"github.com/quic-go/quic-go/congestion"
)

type bbrV2Config struct {
	maxBandwidth     congestion.ByteCount
	highGain         float64
	drainGain        float64
	cwndGain         float64
	minRTTWindow     time.Duration
	probeRTTDuration time.Duration
	probeBWMode      bool
	enableECN        bool
}

func newProductionBBRV2Config() *bbrV2Config {
	return &bbrV2Config{
		maxBandwidth:     0,
		highGain:         2.885,
		drainGain:        1.0 / 2.885,
		cwndGain:         2.0,
		minRTTWindow:     10 * time.Second,
		probeRTTDuration: 200 * time.Millisecond,
		probeBWMode:      true,
		enableECN:        true,
	}
}

func createBBRV2Connection(cfg *bbrV2Config) (*quic.Conn, error) {
	bbrSender := congestion.NewBBRSender(
		congestion.DefaultBBRMaxBandwidth,
		congestion.DefaultBBRHighGain,
	)

	quicConfig := &quic.Config{
		Allow0RTT: true,
		CongestionControlFactory: congestion.CongestionControlFactoryFunc(
			func() congestion.CongestionControl {
				return bbrSender
			},
		),
		EnableDatagrams:          false,
		MaxIdleTimeout:           60 * time.Second,
		KeepAlivePeriod:          15 * time.Second,
		DisablePathMTUDiscovery:  false,
	}

	tlsConfig := createTLSConfig()
	conn, err := quic.DialAddr(
		context.Background(),
		"example.com:443",
		tlsConfig,
		quicConfig,
	)
	if err != nil {
		return nil, fmt.Errorf("BBR v2 connect failed: %w", err)
	}

	return conn, nil
}

func monitorBBRState(conn *quic.Conn) {
	ticker := time.NewTicker(5 * time.Second)
	defer ticker.Stop()

	for range ticker.C {
		stats := conn.ConnectionState()
		fmt.Printf("[BBR v2 Monitor] RTT: %v | BytesInFlight: %d\n",
			stats.RTT, stats.BytesInFlight)
	}
}

func main() {
	cfg := newProductionBBRV2Config()
	conn, err := createBBRV2Connection(cfg)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	go monitorBBRState(conn)

	stream, err := conn.OpenStreamSync(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	data := make([]byte, 10*1024*1024)
	start := time.Now()
	stream.Write(data)
	fmt.Printf("BBR v2: 10MB transfer in %v\n", time.Since(start))
}

Stratégie 3 : Réglage des paramètres Cubic

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/quic-go/quic-go"
	"github.com/quic-go/quic-go/congestion"
)

type cubicProductionConfig struct {
	maxCwnd            congestion.ByteCount
	beta               float64
	cubicBackoffFactor float64
	hyStartEnabled     bool
	minSsthresh        congestion.ByteCount
	initialCwnd        congestion.ByteCount
}

func newCubicProductionConfig() *cubicProductionConfig {
	return &cubicProductionConfig{
		maxCwnd:            16777216,
		beta:               0.7,
		cubicBackoffFactor: 0.3,
		hyStartEnabled:     true,
		minSsthresh:        4096,
		initialCwnd:        32768,
	}
}

func createCubicConnection(cfg *cubicProductionConfig) (*quic.Conn, error) {
	cubicConfig := congestion.DefaultCubicConfig()
	cubicSender := congestion.NewCubicSenderFactory(cubicConfig)

	quicConfig := &quic.Config{
		Allow0RTT:               true,
		CongestionControlFactory: cubicSender,
		MaxIdleTimeout:           60 * time.Second,
		KeepAlivePeriod:          15 * time.Second,
		DisablePathMTUDiscovery:  false,
	}

	tlsConfig := createTLSConfig()
	conn, err := quic.DialAddr(
		context.Background(),
		"example.com:443",
		tlsConfig,
		quicConfig,
	)
	if err != nil {
		return nil, fmt.Errorf("Cubic connect failed: %w", err)
	}

	return conn, nil
}

func main() {
	cfg := newCubicProductionConfig()
	conn, err := createCubicConnection(cfg)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	stream, err := conn.OpenStreamSync(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	data := make([]byte, 10*1024*1024)
	start := time.Now()
	stream.Write(data)
	fmt.Printf("Cubic: 10MB transfer in %v\n", time.Since(start))
}

Stratégie 4 : Basculement adaptatif d'algorithme

package main

import (
	"context"
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/quic-go/quic-go"
	"github.com/quic-go/quic-go/congestion"
)

type NetworkProfile struct {
	Name      string
	LossRate  float64
	RTT       time.Duration
	Bandwidth congestion.ByteCount
	Algorithm string
}

var profiles = []NetworkProfile{
	{Name: "lowLossHighBW", LossRate: 0.001, RTT: 30 * time.Millisecond, Bandwidth: 100_000_000, Algorithm: "bbr"},
	{Name: "highLoss", LossRate: 0.03, RTT: 80 * time.Millisecond, Bandwidth: 20_000_000, Algorithm: "cubic"},
	{Name: "highLatency", LossRate: 0.005, RTT: 250 * time.Millisecond, Bandwidth: 50_000_000, Algorithm: "bbr"},
	{Name: "wireless", LossRate: 0.02, RTT: 60 * time.Millisecond, Bandwidth: 30_000_000, Algorithm: "cubic"},
}

type AdaptiveCongestionManager struct {
	mu          sync.Mutex
	currentAlgo string
	lossWindow  []float64
	rttWindow   []time.Duration
	switchCount int
}

func NewAdaptiveManager() *AdaptiveCongestionManager {
	return &AdaptiveCongestionManager{
		currentAlgo: "cubic",
		lossWindow:  make([]float64, 0, 20),
		rttWindow:   make([]time.Duration, 0, 20),
	}
}

func (m *AdaptiveCongestionManager) RecordSample(lossRate float64, rtt time.Duration) {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.lossWindow = append(m.lossWindow, lossRate)
	m.rttWindow = append(m.rttWindow, rtt)

	if len(m.lossWindow) > 20 {
		m.lossWindow = m.lossWindow[1:]
	}
	if len(m.rttWindow) > 20 {
		m.rttWindow = m.rttWindow[1:]
	}

	m.evaluate()
}

func (m *AdaptiveCongestionManager) evaluate() {
	if len(m.lossWindow) < 10 {
		return
	}

	avgLoss := m.avgLoss()
	avgRTT := m.avgRTT()

	newAlgo := "cubic"
	if avgLoss < 0.005 && avgRTT < 100*time.Millisecond {
		newAlgo = "bbr"
	} else if avgLoss < 0.01 && avgRTT > 150*time.Millisecond {
		newAlgo = "bbr"
	}

	if newAlgo != m.currentAlgo {
		fmt.Printf("[Adaptive] Switching %s -> %s (avgLoss=%.4f avgRTT=%v)\n",
			m.currentAlgo, newAlgo, avgLoss, avgRTT)
		m.currentAlgo = newAlgo
		m.switchCount++
	}
}

func (m *AdaptiveCongestionManager) avgLoss() float64 {
	var sum float64
	for _, l := range m.lossWindow {
		sum += l
	}
	return sum / float64(len(m.lossWindow))
}

func (m *AdaptiveCongestionManager) avgRTT() time.Duration {
	var sum time.Duration
	for _, r := range m.rttWindow {
		sum += r
	}
	return sum / time.Duration(len(m.rttWindow))
}

func (m *AdaptiveCongestionManager) GetFactory() congestion.CongestionControlFactory {
	m.mu.Lock()
	algo := m.currentAlgo
	m.mu.Unlock()

	if algo == "bbr" {
		return congestion.CongestionControlFactoryFunc(
			func() congestion.CongestionControl {
				return congestion.NewBBRSender(
					congestion.DefaultBBRMaxBandwidth,
					congestion.DefaultBBRHighGain,
				)
			},
		)
	}
	return congestion.NewCubicSenderFactory(congestion.DefaultCubicConfig())
}

func main() {
	manager := NewAdaptiveManager()

	samples := []struct {
		loss float64
		rtt  time.Duration
	}{
		{0.001, 30 * time.Millisecond},
		{0.002, 35 * time.Millisecond},
		{0.001, 28 * time.Millisecond},
		{0.015, 80 * time.Millisecond},
		{0.025, 90 * time.Millisecond},
		{0.030, 85 * time.Millisecond},
	}

	for _, s := range samples {
		manager.RecordSample(s.loss, s.rtt)
		time.Sleep(100 * time.Millisecond)
	}

	fmt.Printf("Final algorithm: %s (switches: %d)\n",
		manager.currentAlgo, manager.switchCount)
}

Stratégie 5 : Benchmark et comparaison de performance

#!/bin/bash
# benchmark-congestion-control.sh - BBR v2 vs Cubic performance comparison

TARGET="https://example.com"
RUNS=30
PAYLOAD_SIZE="10M"

echo "=== QUIC Congestion Control Benchmark ==="
echo "Target: $TARGET | Runs: $RUNS | Payload: $PAYLOAD_SIZE"
echo ""

for algo in bbr cubic; do
  total_ttfb=0
  total_throughput=0
  total_retransmit=0

  for i in $(seq 1 $RUNS); do
    result=$(curl --http3 $TARGET \
      -w "%{time_starttransfer} %{speed_download} %{num_connects}" \
      -o /dev/null -s 2>/dev/null)

    ttfb=$(echo $result | awk '{print $1}')
    throughput=$(echo $result | awk '{print $2}')
    retransmit=$(echo $result | awk '{print $3}')

    total_ttfb=$(echo "$total_ttfb + $ttfb" | bc)
    total_throughput=$(echo "$total_throughput + $throughput" | bc)
    total_retransmit=$(echo "$total_retransmit + $retransmit" | bc)
  done

  avg_ttfb=$(echo "scale=4; $total_ttfb / $RUNS" | bc)
  avg_throughput=$(echo "scale=0; $total_throughput / $RUNS" | bc)

  echo "[$algo]"
  echo "  Avg TTFB: ${avg_ttfb}s"
  echo "  Avg Throughput: ${avg_throughput} bytes/s"
  echo "  Avg Retransmits: $(echo "scale=1; $total_retransmit / $RUNS" | bc)"
  echo ""
done
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/quic-go/quic-go"
	"github.com/quic-go/quic-go/congestion"
)

func benchmarkAlgorithms() {
	algorithms := []struct {
		name    string
		factory congestion.CongestionControlFactory
	}{
		{"BBR v2", congestion.CongestionControlFactoryFunc(
			func() congestion.CongestionControl {
				return congestion.NewBBRSender(
					congestion.DefaultBBRMaxBandwidth,
					congestion.DefaultBBRHighGain,
				)
			},
		)},
		{"Cubic", congestion.NewCubicSenderFactory(congestion.DefaultCubicConfig())},
	}

	payloadSizes := []int{1024 * 1024, 10 * 1024 * 1024}

	for _, algo := range algorithms {
		for _, size := range payloadSizes {
			quicConfig := &quic.Config{
				Allow0RTT:               true,
				CongestionControlFactory: algo.factory,
			}

			start := time.Now()
			conn, err := quic.DialAddr(
				context.Background(),
				"example.com:443",
				createTLSConfig(),
				quicConfig,
			)
			if err != nil {
				log.Printf("[%s] connect failed: %v", algo.name, err)
				continue
			}

			stream, _ := conn.OpenStreamSync(context.Background())
			stream.Write(make([]byte, size))
			elapsed := time.Since(start)

			throughput := float64(size) / elapsed.Seconds() / 1024 / 1024
			fmt.Printf("[%s] %dKB: %v (%.1f MB/s)\n",
				algo.name, size/1024, elapsed, throughput)
			conn.Close()
		}
	}
}

func main() {
	benchmarkAlgorithms()
}

Guide des pièges

Mauvaise pratique Bonne pratique
❌ Choisir aveuglément BBR v2 pour tous les scénarios ✅ Utiliser BBR v2 pour les scénarios à faible perte et à large bande, Cubic pour les sans fil à forte perte ; choisir selon les caractéristiques du réseau
❌ Ignorer l'équité de coexistence BBR et Cubic ✅ Activer ECN, définir une limite supérieure du cwnd BBR, utiliser le mode ProbeBW pour réduire la préemption de bande passante
❌ Garder la fenêtre de congestion initiale par défaut à 10 MSS ✅ Augmenter le cwnd initial à 32 Ko–64 Ko sur les liaisons à fort BDP pour accélérer la phase Startup
❌ Ne pas surveiller les métriques de contrôle de congestion QUIC ✅ Exporter cwnd, débit de pacing, octets en vol vers Prometheus et définir des alertes
❌ Désactiver Pacing autorisant les envois en rafale ✅ Doit activer Pacing pour répartir les données uniformément sur le RTT, évitant la perte de paquets des routeurs intermédiaires

Dépannage des erreurs

Message d'erreur Cause Solution
congestion: BBR ProbeRTT stuck La fenêtre cwnd de la phase ProbeRTT est trop petite pour se rétablir Augmenter probeRTTDuration ou réduire minRTTWindow
cwnd growth stalled Croissance de la fenêtre Cubic lente sur les réseaux à faible RTT Augmenter initialCwnd, activer l'accélération HyStart
quic: excessive retransmits Seuil de détection de perte trop bas provoquant des faux positifs Augmenter quic_loss_detection_threshold à 5
pacing rate too low Sondage de bande passante BBR insuffisant Vérifier le paramètre highGain, s'assurer que le cycle ProbeBW est normal
ECN marked but no loss ECN en conflit avec BBR, réduisant à tort le débit d'envoi Activer la réponse ECN de BBR v2 ; Cubic doit ignorer les marques ECN pures
congestion window overflow cwnd dépassant la limite maximale Augmenter quic_max_congestion_window
BBR bandwidth estimate stale Aucune mise à jour de bande passante sur une longue période Vérifier la longueur de fenêtre MaxBandwidthFilter
Cubic beta too aggressive Repli excessif après une perte de paquets Ajuster beta de 0,7 à 0,8 pour réduire le repli
path MTU discovery failed Paquets de sondage MTU supprimés Désactiver DisablePathMTUDiscovery ou réduire le pas de sondage
fairness: BBR starving Cubic BBR préempte la bande passante de Cubic Activer le plancher ProbeBW de BBR v2, définir une protection du partage de bande passante

Optimisation avancée

  1. Intégration BBR v2 + ECN : Avec ECN activé, BBR v2 peut distinguer les marques de congestion des pertes de paquets réelles, évitant les réductions de débit erronées ; le débit s'améliore de 15 % à 25 % dans les réseaux contrôlés
  2. Optimisation Cubic HyStart++ : HyStart++ sonde rapidement la bande passante disponible lors du démarrage de la connexion, évitant le sur-envoi du Slow Start qui provoque des pertes ; Go quic-go l'intègre nativement
  3. Contrôle de congestion Multipath QUIC : MP-QUIC (RFC 9483) prend en charge la transmission multipath concurrente avec un contrôle de congestion indépendant par chemin ; une planification couplée est nécessaire pour éviter la surcharge d'un seul chemin
  4. Exploration de l'algorithme COPA : COPA détecte la congestion via le gradient de délai, plus équitable que BBR, adapté aux liaisons partagées multi-locataires ; quiche propose un support expérimental
  5. Export standardisé qlog : RFC 9484 définit le format de journal d'événements QUIC pour les transitions complètes de la machine à états de contrôle de congestion, permettant une analyse et un réglage hors ligne

Analyse comparative

Métrique BBR v2 Cubic Reno COPA
Mécanisme central Modèle bande passante+RTT AIMD piloté par la perte AIMD piloté par la perte Piloté par le gradient de délai
Utilisation de la bande passante 90 %–98 % 60 %–75 % 40 %–60 % 80 %–90 %
Équité (coexistence Cubic) Moyenne (v2 améliorée) Référence Bonne Bonne
Performance à forte perte Faible (interprète mal la perte) Moyenne Faible Bonne
Performance à forte latence Excellente Faible (croissance lente de la fenêtre) Faible Moyenne
Adaptabilité sans fil Moyenne Bonne Faible Bonne
Support ECN Natif en v2 Partiel Aucun Natif
Complexité d'implémentation Élevée Moyenne Faible Élevée
Maturité en production Élevée (Google/Cloudflare) Élevée (défaut Linux) Élevée Expérimental

Résumé et perspectives

Le contrôle de congestion QUIC est le champ de bataille central de l'optimisation des performances réseau en 2026. BBR v2 améliore le débit de 40 % dans les scénarios à faible perte et à large bande, Cubic est plus stable dans les scénarios sans fil à forte perte, et le basculement adaptatif est la solution de production optimale. À mesure que l'algorithme COPA mûrit, il offrira des options plus équitables pour les scénarios multi-locataires, et le contrôle de congestion multipath MP-QUIC améliorera encore l'efficacité de transmission dans les scénarios de calcul en périphérie.

Outils en ligne recommandés

Essayez ces outils exécutés localement dans le navigateur — aucune inscription requise →

#QUIC拥塞控制#BBR v2#Cubic#网络性能#协议调优#2026#网络协议