TCP 송수신 버퍼 튜닝과 처리량

같은 애플리케이션, 같은 네트워크 대역폭인데 서버 위치나 커널 파라미터만 바뀌었을 뿐인데 처리량이 절반으로 떨어지는 경우가 있다. 원인이 대역폭이 아니라 TCP 송수신 버퍼 크기인 경우가 많다. TCP는 상대가 확인응답(ACK)을 보내기 전까지 보낼 수 있는 데이터량이 버퍼(윈도)로 제한되는데, 이 버퍼가 대역폭-지연 곱(BDP, Bandwidth-Delay Product)보다 작으면 실제 링크 속도를 다 못 채우고 병목이 생긴다. 이 글에서는 net.core.rmem_max/net.ipv4.tcp_rmem 같은 버퍼 관련 sysctl과 처리량의 관계를 iperf3로 직접 측정한 값으로 정리한다.

버퍼 관련 sysctl과 BDP

수신 측 처리량은 이론상 수신 윈도 크기 ÷ RTT를 넘을 수 없다. 관련 sysctl은 다음과 같다.

sysctl의미
net.core.rmem_max / wmem_max애플리케이션이 setsockopt(SO_RCVBUF/SO_SNDBUF)로 명시적으로 요청할 수 있는 버퍼 상한
net.ipv4.tcp_rmem / tcp_wmemmin 기본값 max 3개 값. 애플리케이션이 버퍼 크기를 직접 지정하지 않으면(대부분의 경우) 커널이 이 범위 안에서 자동으로 늘려가며 조절(autotuning)한다
net.ipv4.tcp_moderate_rcvbuf1이면 수신 버퍼 autotuning 활성화(기본값). 0이면 tcp_rmem의 기본값에 고정

중요한 점은, iperf3처럼 소켓 버퍼를 직접 지정하지 않는 일반적인 애플리케이션에서는 autotuning의 상한인 tcp_rmem/tcp_wmem의 세 번째 값이 실질적인 천장이고, rmem_max/wmem_max는 여기에 관여하지 않는다는 것이다(명시적으로 버퍼를 지정하는 애플리케이션에만 rmem_max/wmem_max가 상한으로 작용한다). 두 종류의 파라미터를 헷갈려서 rmem_max만 올려놓고 왜 처리량이 그대로냐고 헤매는 경우가 흔하다.

실측: 버퍼를 줄이면 처리량이 실제로 떨어진다

같은 LAN 안(왕복 지연 약 0.5ms)의 두 호스트에서 iperf3로 기본 설정 처리량을 먼저 측정했다.

$ ping -c 5 192.168.0.42
rtt min/avg/max/mdev = 0.451/0.547/0.693/0.080 ms

$ sysctl net.core.rmem_max net.ipv4.tcp_rmem
net.core.rmem_max = 4194304
net.ipv4.tcp_rmem = 4096	131072	33554432

$ iperf3 -c 192.168.0.42 -t 5
[  5]   0.00-5.00   sec  1.21 GBytes  2.07 Gbits/sec  144             sender
[  5]   0.00-5.46   sec  1.20 GBytes  1.89 Gbits/sec                  receiver

기본값(autotuning 상한 32MB)에서는 2.07 Gbits/sec이 나왔다. 이제 수신 측(iperf3 서버) 호스트의 tcp_rmem 상한을 32MB에서 32KB로 극단적으로 낮춰봤다.

[서버 측]
$ sudo sysctl -w net.core.rmem_max=32768
$ sudo sysctl -w net.ipv4.tcp_rmem="4096 87380 32768"
$ iperf3 -c 192.168.0.42 -t 5
[  5]   0.00-1.00   sec  95.8 MBytes   802 Mbits/sec    3   86.3 KBytes
[  5]   1.00-2.00   sec   109 MBytes   917 Mbits/sec    2   86.3 KBytes
[  5]   2.00-3.00   sec   119 MBytes  1.00 Gbits/sec    1   86.3 KBytes
[  5]   0.00-5.00   sec   566 MBytes   950 Mbits/sec    9             sender
[  5]   0.00-5.30   sec   566 MBytes   895 Mbits/sec                  receiver

처리량이 2.07Gbps에서 950Mbps로 절반 이하로 떨어졌다. RTT 0.5ms 기준으로 32KB 윈도가 낼 수 있는 이론상 최대치는 32768바이트 × 8 ÷ 0.0005초 ≈ 524Mbps인데, TCP의 실제 윈도 크기는 설정값보다 다소 여유 있게 잡히는 데다(Cwnd 컬럼이 86.3KB로 찍힘) 커널의 다른 버퍼링 경로도 있어 이론치보다는 높게 나왔지만, 방향성은 명확하다. 같은 서버에서 상한을 16MB로 다시 올리면 처리량은 기본값 수준으로 회복된다.

[서버 측]
$ sudo sysctl -w net.core.rmem_max=16777216
$ sudo sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
$ iperf3 -c 192.168.0.42 -t 5
[  5]   0.00-1.00   sec   142 MBytes  1.19 Gbits/sec   37    464 KBytes
[  5]   1.00-2.00   sec   274 MBytes  2.30 Gbits/sec   16    421 KBytes
[  5]   0.00-5.00   sec  1.15 GBytes  1.98 Gbits/sec  180             sender
[  5]   0.00-5.43   sec  1.15 GBytes  1.82 Gbits/sec                  receiver

1.98Gbps로 기본값(2.07Gbps)과 사실상 같은 수준을 회복했다. 이 LAN 환경은 RTT가 0.5ms로 워낙 짧아 기본 상한(32MB)만으로도 넉넉하지만, 리전 간 통신처럼 RTT가 수십~수백 ms로 커지는 환경에서는 같은 32MB 상한도 부족해질 수 있다 — RTT 100ms라면 필요한 윈도는 2.5Gbps × 0.1초 ÷ 8 ≈ 31MB로 기본 상한에 이미 근접한다.

주의사항

항목내용
병목은 양쪽 중 더 작은 쪽송신 측 tcp_wmem과 수신 측 tcp_rmem 중 더 작은 윈도가 실제 처리량을 결정한다. 한쪽만 늘려도 다른 쪽이 작으면 효과가 없다
rmem_max/wmem_max와 tcp_rmem/tcp_wmem 혼동대부분의 애플리케이션(autotuning 사용)은 tcp_rmem/tcp_wmem의 max 값이 실질 상한이다. rmem_max/wmem_max는 소켓 버퍼를 명시적으로 지정하는 애플리케이션에만 적용된다
버퍼를 무조건 키우는 것도 능사는 아님버퍼가 크면 링크에 지연(버퍼블로트)이 쌓여 다른 트래픽의 지연시간이 늘 수 있다. 실제 RTT와 필요 대역폭 기준으로 BDP를 계산해 필요한 만큼만 늘리는 편이 안전하다
재부팅 시 초기화sysctl -w는 즉시 적용되지만 재부팅하면 사라진다. 영구 적용은 /etc/sysctl.d/에 파일로 저장

마무리

TCP 처리량이 대역폭보다 낮게 나온다면 먼저 ping으로 RTT를 재고 필요 윈도(BDP)를 계산한 뒤, tcp_rmem/tcp_wmem의 max 값이 그 이상인지 확인하는 순서가 효율적이다. 이번 실측처럼 상한을 32KB로 좁히면 처리량이 절반 이하로 떨어지고, 다시 넉넉하게 늘리면 회복되는 걸 직접 확인할 수 있다.

참고

Kernel.org – IP sysctl 문서

답글 남기기