RTO로 손실을 감지하는 과정
ACK가 오지 않았다고 세그먼트 손실을 바로 확정할 수는 없습니다. 데이터나 ACK가 늦어진 것일 수도 있습니다. TCP는 가장 오래 확인되지 않은 데이터가 일정 시간 남아 있으면 재전송합니다. 이때 기다리는 시간이 **RTO(Retransmission Timeout)**입니다. 계산과 타이머 관리는 RFC 6298에 정의되어 있습니다.
고정된 시간으로 기다리지 않는 이유
RTO를 모든 연결에서 같은 값으로 정하면 경로의 차이를 반영할 수 없습니다. 같은 서버에 접속하더라도 유선망과 무선망의 왕복 시간은 다를 수 있고, 하나의 연결 안에서도 지연은 계속 변합니다.
기다리는 시간이 실제 왕복 시간보다 지나치게 짧으면 아직 이동 중인 세그먼트를 손실로 잘못 판단해 같은 데이터를 다시 보낼 수 있습니다. 반대로 너무 길면 실제 손실이 생겼을 때 복구가 늦어집니다. TCP는 최근에 측정한 RTT뿐 아니라 RTT가 얼마나 흔들렸는지도 함께 사용해 RTO를 계산합니다.
RFC 6298 §2는 다음 세 값을 사용합니다.
SRTT: 측정한 RTT를 평활한 값RTTVAR: RTT 변화 폭을 평활한 값G: 타이머가 시간을 구분할 수 있는 단위
첫 RTT 표본을 R이라고 하면 초기값은 다음과 같습니다. K는 4입니다.
SRTT = R
RTTVAR = R / 2
RTO = SRTT + max(G, K × RTTVAR)
RTT를 한 번 측정했다고 해서 그 값을 그대로 RTO로 사용하지 않습니다. 첫 표본의 절반을 편차의 초기값으로 잡고, 그 편차의 네 배를 더해 여유를 둡니다.
두 번째부터는 새 표본 R'을 기존 값에 섞습니다. RTTVAR를 먼저 계산할 때는 갱신 전의 SRTT를 사용해야 합니다.
RTTVAR = (1 - 1/4) × RTTVAR + 1/4 × |SRTT - R'|
SRTT = (1 - 1/8) × SRTT + 1/8 × R'
RTO = SRTT + max(G, 4 × RTTVAR)
SRTT는 새 표본을 1/8만 반영하므로 순간적인 변화에 바로 끌려가지 않습니다. RTTVAR는 기존 평균과 새 표본의 차이를 반영합니다. 왕복 시간이 불안정할수록 편차가 커지고 RTO도 늘어납니다.
100ms 다음에 140ms가 측정된 경우
타이머 단위 G가 계산에 영향을 주지 않을 만큼 작다고 두고 직접 계산해보겠습니다.
첫 RTT가 100ms라면 다음과 같이 초기화됩니다.
SRTT = 100ms
RTTVAR = 50ms
계산된 RTO = 100 + 4 × 50 = 300ms
그다음 RTT가 140ms로 측정되면 RTTVAR를 먼저 갱신합니다.
RTTVAR = 3/4 × 50 + 1/4 × |100 - 140|
= 37.5 + 10
= 47.5ms
SRTT = 7/8 × 100 + 1/8 × 140
= 87.5 + 17.5
= 105ms
계산된 RTO = 105 + 4 × 47.5
= 295ms
새 RTT는 40ms 늘었지만 SRTT는 5ms만 늘었습니다. 한 표본만으로 경로의 평균이 크게 바뀌지 않게 만든 결과입니다. 편차도 함께 반영되므로 RTO = RTT의 두 배처럼 고정된 비율로 설명할 수 없습니다.
RFC 6298은 계산 결과가 1초보다 작으면 1초로 올리고, RTT 표본을 얻기 전의 초기 RTO도 1초로 둘 것을 권고합니다. 실제 최솟값과 초기값은 구현에 따라 다를 수 있습니다.
재전송한 세그먼트로 RTT를 재기 어려운 이유
RTT는 데이터를 보낸 시점부터 그 데이터를 확인하는 ACK가 돌아온 시점까지 측정할 수 있습니다. 하지만 같은 데이터를 재전송한 뒤 받은 ACK에는 어느 전송에 대한 응답인지 표시되어 있지 않을 수 있습니다.
t0 원본 세그먼트 전송
t1 RTO 만료, 같은 바이트 범위 재전송
t2 ACK 수신
t2 - t0를 사용하면 원본이 늦게 도착한 경우에는 맞지만, 재전송본에 대한 ACK였다면 RTT를 너무 길게 계산합니다. t2 - t1을 사용하면 그 반대의 문제가 생깁니다. ACK만 보고 둘 중 하나를 고를 근거가 없습니다.
이 문제를 다루는 규칙이 Karn 알고리즘입니다. RFC 6298 §3는 재전송된 세그먼트에서 RTT 표본을 만들지 않도록 규정합니다. 모호한 값을 억지로 평균에 넣지 않고, 재전송되지 않은 새로운 데이터가 확인될 때 다시 RTT를 측정합니다.
TCP Timestamp를 협상한 연결에서는 TSval과 TSecr로 전송을 구분해 재전송 모호성을 줄일 수 있습니다. 이때도 SND.UNA를 전진시키는 ACK에서 RTT 표본을 얻습니다.
타임아웃이 반복되면 기다리는 시간도 늘어난다
RTO가 만료되면 TCP는 확인되지 않은 세그먼트를 전부 한꺼번에 보내지 않습니다. RFC 6298 §5의 권고 절차에서는 아직 확인되지 않은 세그먼트 중 가장 앞선 것을 재전송합니다. 그리고 현재 RTO를 두 배로 늘린 뒤 타이머를 다시 시작합니다.
RTO 만료 → 가장 앞선 미확인 세그먼트 재전송
→ RTO = RTO × 2
→ 늘어난 RTO로 타이머 재시작
이 과정을 지수 백오프라고 합니다. 예를 들어 현재 RTO가 1초이고 새로운 RTT 표본을 얻지 못한 채 타임아웃이 반복되면 대기 시간은 2초, 4초, 8초처럼 늘어납니다. 구현은 상한을 둘 수 있지만 RFC 6298은 그 상한을 적어도 60초로 두도록 합니다.
피드백이 없는 동안 같은 간격으로 재전송하면 불안정한 경로에 부담을 더할 수 있습니다. 유효한 RTT 표본을 다시 얻으면 SRTT와 RTTVAR를 갱신해 RTO를 조정합니다.
RTO 타이머는 세그먼트마다 독립된 타이머가 하나씩 붙는 형태로 이해할 필요는 없습니다. 새 데이터를 확인하는 ACK가 오면 타이머를 다시 시작하고, 보낸 데이터가 모두 확인되면 끕니다. 만료 시점에는 누적 ACK 기준으로 가장 오래 확인되지 않은 세그먼트부터 처리합니다.