Linux TCPスタックの最適化:本番環境向けバッファ調整と混雑制御

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

2Gbpsしか出ない10Gbpsリンク

Linuxのデフォルト設定は、生のパフォーマンスではなく互換性を重視しています。これらの設定は、個人のノートPCや基本的なWebサーバーであれば問題なく動作します。

しかし、アプリケーションがスケールし、1万以上の同時接続を処理するようになると、これらはすぐに足枷となります。カーネルの設定が控えめすぎたために、最高級のハードウェアが物理的な限界を大幅に下回るレベルで頭打ちになっているのを何度も見てきました。これらの調整をトラフィックの多い本番環境に適用した結果、p99レイテンシが40%減少し、ついに10GbpsのNICを使い切ることができました。

TCPは、データの信頼性を確保するという重労働を担っています。カーネルで定義されたパラメータに基づいて、フロー制御と混雑制御を管理します。これらの制限が保守的すぎると、サーバーは確認応答(ACK)を待つためにサイクルを浪費してしまいます。データの移動ではなく、非常に小さなバッファで首が絞まっている状態です。目標は、システムメモリを使い果たすことなく、パイプを全開にすることです。

計算してみよう:帯域遅延積(BDP)

数値を盲目的に変更してはいけません。帯域遅延積(BDP)を理解する必要があります。これは、送信済みだがまだ確認応答(ACK)を受け取っていない、現在ネットワーク上にある「インフライト」のデータ総量を表します。

BDP = 帯域幅 (bits/秒) * ラウンドトリップタイム (秒)

現実的な例を挙げましょう。10Gbpsのリンクがあり、データベースへのラウンドトリップレイテンシが50msである場合、BDPはおよそ62.5MBになります。もしTCPバッファがデフォルトの4MBに制限されている場合、送信側は時間の93%をACK待ちに費やすことになります。実質的に10Gbpsのリンク料金を払っていながら、640Mbpsしか得られていないことになります。パイプを満杯に保つには、バッファサイズを増やす必要があります。

インストールとツール

スタックのチューニングは非常にシンプルです。重いサードパーティソフトウェアは必要ありません。すべては sysctl インターフェースと iproute2 を通じて行われます。

まず、現在のツールを確認してください。最小構成のイメージを使用している場合は、以下でインストールできます。

# Debian/Ubuntuの場合
sudo apt update && sudo apt install iproute2 procps -y

# RHEL/CentOS/Fedoraの場合
sudo dnf install iproute procps-ng -y

カーネルをリアルタイムで調整するには sysctl を使用します。接続への実際の性能影響を監視するには ss (socket statistics) を使用します。

ステップバイステップの設定

テストとして、sysctl -w を使用して変更を即座に適用します。パフォーマンスの向上が確認できたら、設定を永続化します。

1. 積極的なTCPバッファサイズの設定

Linuxは、コアバッファとTCP専用バッファを使用します。これら両方を増やす必要があります。1Gbps以上のネットワークをサポートするには、古いデフォルトの4MBでは不十分です。

# まず現在の制限を確認する
sysctl net.core.rmem_max net.ipv4.tcp_rmem

高帯域幅環境向けに以下の設定を適用します。

# 受信/送信バッファの最大サイズを16MBに設定
sudo sysctl -w net.core.rmem_max=16777216
sudo sysctl -w net.core.wmem_max=16777216

# TCPオートチューニング設定:最小 (4KB), デフォルト (87KB), 最大 (16MB)
sudo sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sudo sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"

カーネルは、tcp_rmem のこれら3つの値を使用して動的にスケールします。最初は小さく開始し、接続の需要がある場合にのみ最大値まで拡張します。

2. Google BBRへの切り替え

多くの古いカーネルのデフォルトは Cubic です。これは信頼性が高いですが、遠距離リンクでのパケットロスにうまく対応できません。Googleの BBR (Bottleneck Bandwidth and RTT) は画期的なアルゴリズムです。ランダムなパケットロスを無視し、実際のスループットに焦点を当てます。

BBRを正しく動作させるには、Fair Queuing (fq) キューイング規則が必要です。

# BBRを有効化
sudo sysctl -w net.core.default_qdisc=fq
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr

私たちのリージョンを跨いだデータベースレプリケーションでは、リンクで1〜2%のパケットロスが発生していても、BBRは90%のスループットを維持しました。Cubicであれば10%まで低下していたでしょう。

3. ソケットオプションの強化

トラフィックの多いAPIは、「エフェメラルポートの枯渇」に悩まされることがよくあります。これは、数千のソケットが TIME_WAIT 状態で留まることで発生します。これを修正し、制限を拡張しましょう。

# 新規接続のためにTIME_WAIT状態のソケットを再利用する
sudo sysctl -w net.ipv4.tcp_tw_reuse=1

# 最大200万のオープンファイル記述子をサポート
sudo sysctl -w fs.file-max=2097152

# 利用可能なポート範囲を拡大
sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535"

また、TCP Fast Openを有効にします。これにより、最初のハンドシェイク中にデータ転送を開始できるようになり、再訪問者のラウンドトリップタイムを1回分節約できます。

sudo sysctl -w net.ipv4.tcp_fastopen=3

4. 設定の保存

再起動時に設定を失わないよう、/etc/sysctl.d/99-performance.conf にこれらを追加します。

net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_fastopen = 3

sudo sysctl -p で適用します。

検証

正しく動作しているか確認するにはどうすればよいでしょうか? ss -ti を使用してアクティブな接続を検査します。

# 特定の接続でBBRが有効か確認する
ss -ti

出力から bbr タグと wscale 値を探してください。 wscale が高い場合、拡張されたバッファが利用されています。また、netstat -s で再送を確認してください。スループットが上昇しつつ再送が減少していれば、成功です。

警告:RAMは無制限ではありません。それぞれが16MBのバッファを使用する10万の接続がある場合、メモリ不足に陥ります。サーバーに、ピーク時の接続数にこれらの新しい制限を掛け合わせた値を処理できる十分な余裕があることを確認してください。

最後に

スタックのチューニングは反復的なプロセスです。まずはこの16MBの制限から開始し、p99レイテンシを監視してください。保守的なデフォルト設定を捨てることで、ハードウェアの性能を最大限に引き出すことができます。これであなたのLinuxサーバーは、本番環境のトラフィック負荷に耐える準備が整いました。

Share: