왜 재정렬이 문제인가

TCP는 순서 보장을 위해 누적 ACK를 쓴다. 중간 세그먼트 하나가 늦게 도착하면(재정렬), 수신 측은 뒤에 온 데이터를 받아도 이전 ACK 번호만 반복해서 보낸다. 송신 측은 이 중복 ACK를 손실 신호로 오해한다. dupACK가 임계치(기본 3개)를 넘으면 빠른 재전송이 발동하고, 혼잡 윈도우(cwnd)가 절반으로 깎인다. 실제로는 패킷이 유실된 게 아니라 순서만 바뀌었을 뿐인데 처리량이 급락하는 것이다.

재정렬은 멀티패스 ECMP 라우팅, 본딩(LACP) 해시 불균형, NIC의 다중 큐(RSS)와 인터럽트 분산, 그리고 GRO/LRO 처리 지연에서 흔히 발생한다. 데이터센터 내부 대역폭이 커질수록 이 문제는 더 도드라진다.

SACK가 하는 일

SACK(Selective Acknowledgment, RFC 2018)는 수신 측이 "어떤 구간을 받았는지"를 명시적으로 알려준다. 송신 측은 실제로 빠진 구간만 재전송하면 되므로, 재정렬로 인한 불필요한 전체 재전송을 크게 줄인다. DSACK(중복 SACK)는 이미 받은 세그먼트를 다시 받았음을 알려, 커널이 재정렬 여부를 학습하게 한다.

현재 상태 확인

sysctl net.ipv4.tcp_sack net.ipv4.tcp_dsack net.ipv4.tcp_reordering

# 재정렬/재전송 관련 카운터 관찰
nstat -az | grep -Ei 'TCPSACKReorder|TCPRenoReorder|TCPFastRetrans|TCPLostRetransmit|TCPDSACK'

# 특정 연결의 재정렬 상태와 cwnd
ss -tin dst 10.0.1.20 | grep -Eo 'reordering:[0-9]+|cwnd:[0-9]+|retrans:[0-9/]+'

TCPSACKReorder가 꾸준히 증가하면 커널이 재정렬을 감지·보정하고 있다는 뜻이다. 반대로 TCPFastRetrans만 급증하고 실제 링크 손실률은 낮다면, 재정렬을 손실로 오판하는 상황을 의심해야 한다.

커널 파라미터 조정

SACK는 기본 활성화 상태이니 함부로 끄지 않는다. 재정렬이 잦은 환경에서는 RACK-TLP 기반 손실 탐지를 쓰는 것이 dupACK 카운트 방식보다 안정적이다. RACK은 시간 기반으로 손실을 판단해 재정렬에 관대하다.

# /etc/sysctl.d/99-tcp-reorder.conf
net.ipv4.tcp_sack = 1
net.ipv4.tcp_dsack = 1
net.ipv4.tcp_fack = 0            # RACK과 충돌, 최신 커널에선 비활성 권장

# RACK 기반 손실 복구 (커널 4.18+ 기본 동작, 명시적으로 확인)
net.ipv4.tcp_recovery = 1        # bit0: RACK 활성

# 재정렬 허용치 상한 (커널이 동적 학습, 상한만 여유있게)
net.ipv4.tcp_reordering = 3
net.ipv4.tcp_max_reordering = 300

# 혼잡제어는 손실 무관한 BBR 검토 가능
net.ipv4.tcp_congestion_control = bbr

적용은 sysctl --system. tcp_reordering은 시작값일 뿐이며 커널이 관측치에 따라 tcp_max_reordering까지 올린다. 상한을 무작정 키우면 실제 손실 복구가 늦어지니 링크 특성에 맞춰야 한다.

손실 기반 vs 시간 기반 복구

구분dupACK(전통)RACK-TLP
손실 판단 근거중복 ACK 3개세그먼트 전송 후 경과 시간
재정렬 내성낮음(오탐 잦음)높음
tail loss 대응RTO 대기(느림)TLP로 빠른 탐지
권장 환경단순·저재정렬 링크ECMP·본딩·고대역 DC

NIC·오프로드 측면 점검

커널 튜닝 전에 하드웨어 레벨의 재정렬 원인을 먼저 제거한다. RSS 큐가 같은 플로우를 여러 CPU로 흩뿌리거나, GRO가 꺼져 세그먼트가 개별 처리되면 재정렬이 늘어난다.

# GRO 확인/활성 (수신 측 병합으로 재정렬 완충)
ethtool -k eth0 | grep -E 'generic-receive-offload|large-receive'
ethtool -K eth0 gro on

# RSS가 플로우 단위로 안정 해시되는지 (같은 5-tuple은 같은 큐로)
ethtool -x eth0

# 실측: 재정렬로 인한 재전송인지 손실인지 구분
ss -tin | grep -c 'reord'

LRO(large-receive-offload)는 GRO보다 공격적으로 병합해 라우터/브리지 역할 호스트에서는 패킷 왜곡을 유발할 수 있으니, 포워딩 노드에서는 끄는 편이 안전하다.

주의점

첫째, SACK를 끄는 것은 거의 항상 나쁜 선택이다. 과거 SACK 관련 CVE(CVE-2019-11477 등) 대응으로 SACK를 비활성화하는 사례가 있었으나, 정상적 해법은 커널 패치이지 기능 제거가 아니다. 둘째, tcp_max_reordering을 과도하게 키우면 재정렬 오탐은 줄지만 진짜 손실 복구가 지연돼 tail latency가 나빠진다. 셋째, BBR·RACK 도입 시 반드시 A/B로 처리량과 p99 지연을 함께 측정한다. 재전송 카운터 개선이 반드시 애플리케이션 지연 개선을 뜻하지는 않기 때문이다. 마지막으로 컨테이너 환경에서는 호스트 sysctl과 netns별 설정이 다를 수 있으니 실제 트래픽이 지나는 네임스페이스에서 값을 확인해야 한다.