재전송
보낸 것이 상대에게 닿지 않았다고 판단하면 같은 것을 한 번 더 내보냅니다. 이 다시 보내는 일이 재전송입니다. 데이터를 잃어버리는 길 위에서 전달을 보장하려면 이 동작이 밑에 깔려 있어야 합니다.
상세
주문을 넣고 음식을 기다립니다. 빠뜨려졌는지 그냥 늦는 것인지는 앉아서는 알 수 없어서, 붐비는 날이면 이만큼은 걸리겠거니 하고 기다려 본 뒤에 같은 주문을 한 번 더 말합니다. 몇 분마다 손을 들면 홀이 거기 붙잡혀 다른 상까지 늦어집니다.
재전송은 이미 한 번 보낸 데이터를 잃어버렸다고 판단하고 같은 데이터를 다시 내보내는 동작입니다. 성립하려면 두 가지가 붙어 있어야 합니다. 하나는 보낸 것을 잘 받았다는 신호가 돌아올 때까지 손에 들고 있는 것입니다. 다른 하나는 잃어버렸다고 판단할 근거입니다. 들고 있지 않으면 다시 보낼 것이 없고, 판단할 근거가 없으면 언제 보낼지를 정할 수 없습니다.
다시 나가는 것은 같은 데이터입니다. 데이터를 감싸는 것은 새로 매겨집니다. 기다리는 시계는 다시 걸리고, 몇 번째 시도인지는 보내는 쪽이 따로 셉니다. 보내는 쪽만의 동작이라는 점도 중요합니다. 받는 쪽은 같은 것이 두 번 와도 어긋나지 않도록 준비되어 있어야 합니다.
재시도와 갈리는 선이 있습니다. 재시도는 그 일을 다시 요청하는 것입니다. 새 요청이라 내용이 달라질 수도 있습니다. 재전송은 이미 보낸 것과 같은 바이트를 그대로 다시 내보내는 것입니다. 다시 나가는 것이 같은 바이트냐 새 요청이냐가 두 낱말을 가릅니다.
이름이 같은 다른 것도 있습니다. 보안에서 말하는 재전송 공격은 남이 가로챈 메시지를 그대로 다시 흘려보내 인증을 통과하려는 공격입니다. 여기서 다루는 재전송은 유실을 메우는 쪽이라 그것과 다른 이야기입니다.
배경
데이터를 나르는 길은 데이터를 잃어버립니다. 중간 장비의 대기열이 차면 새로 들어온 것을 버립니다. 전파가 흔들리면 받은 비트가 깨집니다. 깨진 것은 받는 쪽이 버립니다. 이때 보낸 쪽에게 버렸다고 알려주는 절차는 없습니다. 보낸 쪽 입장에서 유실은 아무 일도 일어나지 않은 것과 똑같이 보입니다.
그래서 보낸 쪽이 스스로 알아내야 했습니다. 알아내려면 잘 받았다는 신호가 돌아와야 하고, 돌아오지 않았을 때 무엇을 할지가 정해져 있어야 합니다. 손에 들고 있다가 다시 내보내는 것이 그 답입니다. 유실이 있는 길 위에 신뢰할 수 있는 전달을 얹으려는 자리마다 같은 답이 나왔고, 그 동작에 재전송이라는 이름이 붙었습니다.
문제는 기다리는 시간으로 옮겨갑니다. 얼마를 기다렸다 다시 보낼지는 길마다 다릅니다. 너무 짧게 잡으면 멀쩡히 가고 있는 것을 또 보내 길을 더 메웁니다. 너무 길게 잡으면 잃어버린 것을 되찾는 데 그만큼 시간이 걸립니다. 이 시간을 하나의 고정값으로 못 박을 수 없다는 것이 뒤에 오는 규칙 대부분의 출발점입니다.
갈래
축은 둘입니다. 하나는 무엇을 신호로 삼아 잃어버렸다고 판단하느냐입니다. 다른 하나는 다시 보내는 단위가 무엇이냐입니다. 신호가 달라지면 다시 보내는 시점과 양이 달라집니다. 단위가 달라지면 다시 나가는 것의 겉모습이 달라집니다. 앞의 세 소절이 첫째 축 위에 서고, 마지막 소절이 둘째 축을 드러냅니다.
타이머 만료
기다리다 시간이 다 되면 다시 보냅니다. 인터넷 표준 문서인 RFC(Request for Comments) 9293 은 TCP(Transmission Control Protocol, 전송 제어 규약) 를 정의하는 문서입니다. 이 문서는 재전송 큐에 있는 세그먼트의 재전송 타임아웃이 만료되면 재전송 큐 맨 앞의 세그먼트를 다시 보내고 재전송 타이머를 다시 건다고 적습니다. 이 기다리는 시간이 RTO(Retransmission Timeout, 재전송 타임아웃) 입니다. 같은 문서는 인터넷을 이루는 망이 제각각이고 TCP 연결의 용도가 넓어서 RTO 를 동적으로 정해야 한다고 적습니다.
RFC 6298 은 타이머가 만료됐을 때 할 일을 순서로 적습니다. 받는 쪽이 아직 확인해 주지 않은 것 중 가장 먼저 보낸 세그먼트를 다시 보냅니다. 그 다음 RTO 를 두 배로 늘려야 한다고 못 박습니다. 문서는 이것을 타이머를 뒤로 물린다고 표현합니다. 그렇게 두 배가 된 값으로 타이머를 다시 겁니다. 두 배로 늘리는 이 되풀이에는 상한을 둘 수 있습니다.
sequenceDiagram
participant 보내는쪽
participant 받는쪽
보내는쪽-x받는쪽: 세그먼트 · 유실
Note over 보내는쪽: 타이머 시작 · RTO 만료
보내는쪽->>받는쪽: 같은 세그먼트 다시
Note over 보내는쪽: RTO 를 두 배로 · 타이머 다시
받는쪽-->>보내는쪽: 확인 응답
중복 확인 응답
타이머를 기다리지 않고 보내는 길도 있습니다. RFC 5681 은 들어오는 중복 확인 응답에 근거해 유실을 찾아내고 메우는 fast retransmit 알고리즘을 쓸 것을 권합니다. 이 알고리즘은 중복 확인 응답 세 개가 도착한 것을 세그먼트 하나가 없어졌다는 표시로 삼습니다. 세 개를 받으면 재전송 타이머가 만료되기를 기다리지 않고, 없어진 것으로 보이는 세그먼트를 다시 보냅니다.
세 개를 셀 때는 조건이 붙습니다. 그 사이에 확인 응답의 위치를 앞으로 밀어주는 응답이 끼어 있으면 안 됩니다. 위치가 밀렸다는 것은 빈자리가 메워졌다는 뜻이라 유실 신호가 되지 않습니다.
sequenceDiagram
participant 보내는쪽
participant 받는쪽
보내는쪽-x받는쪽: 2번 세그먼트 · 유실
보내는쪽->>받는쪽: 3번 · 4번 · 5번 세그먼트
받는쪽-->>보내는쪽: 중복 확인 응답 3개
Note over 보내는쪽: 타이머를 기다리지 않는다
보내는쪽->>받는쪽: 2번 세그먼트 다시
선택적 확인 응답
한 번에 여러 개가 없어지면 앞의 두 신호로는 부족합니다. RFC 2018 은 한 윈도 분량의 데이터에서 여러 패킷이 없어지면 TCP 의 성능이 떨어질 수 있다고 적습니다. 누적 확인 응답이 주는 정보는 제한적이라, 보내는 쪽은 왕복 시간 한 번에 없어진 패킷 하나에 대해서만 알 수 있습니다. 그렇다고 공격적으로 미리 다시 보내면, 다시 보낸 세그먼트가 이미 잘 도착한 것일 수도 있습니다.
SACK(Selective Acknowledgment, 선택적 확인 응답) 은 이 한계를 넘는 데 도움이 됩니다. 받는 쪽이 무엇을 받았는지를 알려주는 패킷을 보내는 쪽으로 돌려보냅니다. 그러면 보내는 쪽은 빠진 데이터 세그먼트만 골라 다시 보낼 수 있습니다. RFC 2018 은 이 기법을 선택적 반복 재전송 정책과 함께 쓰는 것으로 적습니다.
새 패킷에 다시 싣기
같은 것을 다시 보내지 않는 길도 있습니다. RFC 9002 는 TCP 가 보내는 쪽의 전송 순서와 받는 쪽의 전달 순서를 한데 뭉뚱그려서 재전송 모호성 문제가 생긴다고 적습니다. 어떤 응답이 처음 보낸 것에 대한 것인지 다시 보낸 것에 대한 것인지 가릴 수 없다는 뜻입니다. QUIC 은 두 순서를 떼어 놓습니다. 패킷 번호가 전송 순서를 나타내고, 전달 순서는 STREAM 프레임 안의 스트림 오프셋이 정합니다. 패킷 번호는 한 번호 공간 안에서 계속 커지기만 합니다.
그래서 다시 보내는 단위가 달라집니다. 확인 응답을 끌어내는 프레임을 담은 패킷이 유실로 판정되면, QUIC 은 필요한 프레임을 새 패킷 번호를 가진 새 패킷에 담아 보냅니다. 같은 패킷이 두 번 나가는 일이 없으므로 어느 패킷에 대한 확인 응답인지가 모호해지지 않습니다. RFC 9002 는 그 덕에 더 정확한 왕복 시간 측정이 가능해지고, 헛되이 다시 보낸 것을 손쉽게 찾아낼 수 있으며, fast retransmit 같은 기법을 패킷 번호만으로 두루 적용할 수 있다고 적습니다. 이 갈래가 재전송의 단위가 무엇이냐를 드러냅니다. 다시 나가는 것은 패킷이 아니라 그 안에 실린 내용입니다.
예시
TCP 초기 재전송 타임아웃
RFC 6298 은 보내는 쪽과 받는 쪽 사이에 오간 세그먼트로 왕복 시간을 한 번도 재보지 못한 동안에는 RTO 를 1초로 두라고 권합니다. 이 문서의 이전 판은 초기 RTO 로 3초를 썼고, 구현이 그 값이나 1초보다 큰 다른 값을 여전히 써도 된다고 적습니다. 계산된 RTO 가 1초보다 작으면 1초로 올림하라고 권합니다. 최대값을 둘 수도 있는데, 두려면 최소 60초 이상이어야 합니다.
초기값 RTO <- 1 second 왕복 시간을 아직 못 쟀을 때
하한 RTO <- 1 second 계산 결과가 1초 미만이면 올림
상한 >= 60 seconds 최대값을 둔다면 이 이상
만료할 때마다 RTO <- RTO * 2 타이머를 뒤로 물린다
숫자들이 정하는 것은 하나입니다. 첫 유실을 알아채기까지 얼마나 걸리는가, 그리고 되풀이될수록 그 간격이 얼마나 벌어지는가입니다.
DNS 질의 재전송
전송 계층 밖에서도 같은 동작이 나옵니다. RFC 1035 는 DNS(Domain Name System, 도메인 이름 체계) 의 표준 질의에 UDP(User Datagram Protocol, 사용자 데이터그램 규약) 를 권합니다. 같은 문서는 UDP 로 보낸 질의는 유실될 수 있으므로 재전송 전략이 필요하다고 적습니다. 질의나 응답의 순서가 망이나 네임 서버의 처리 과정에서 뒤바뀔 수 있으니, 리졸버는 순서대로 돌아온다고 기대하면 안 된다고도 적습니다.
최적의 재전송 정책은 인터넷의 상태와 클라이언트의 요구에 따라 달라진다고 적습니다. 이어서 두 가지를 권고합니다. 하나는 어떤 서버의 특정 주소로 질의를 되풀이하기 전에 다른 서버와 다른 주소를 먼저 시도하라는 것입니다. 다른 하나는 재전송 간격을 가능하면 이전 통계에 근거해 정하라는 것입니다. 너무 공격적인 재전송은 커뮤니티 전체의 응답을 쉽게 늦출 수 있다고도 적습니다. 클라이언트가 기대 서버에 얼마나 잘 연결되어 있느냐에 따라 다르지만, 최소 재전송 간격은 2~5초를 권합니다.
MQTT DUP 플래그
다시 보낸 것임을 받는 쪽에 알려주는 비트를 둔 자리도 있습니다. 메시지 전달 규약인 MQTT 의 5.0 명세는 PUBLISH 패킷 첫 바이트의 3번 비트를 DUP(Duplicate, 중복) 플래그로 정합니다.
DUP = 0 이 PUBLISH 패킷을 보내려는 첫 시도
DUP = 1 앞선 시도의 재전달일 수 있다
PUBLISH 패킷을 다시 보내려 할 때는 이 비트를 1로 두어야 합니다. QoS(Quality of Service, 서비스 품질) 0 메시지에서는 모두 0 이어야 합니다. 서버가 구독자에게 내보내는 PUBLISH 의 DUP 값은 들어온 PUBLISH 의 값을 물려받지 않습니다. 나가는 그 패킷 자신이 재전송인지 아닌지만으로 정해집니다.
리눅스 tcp_retries2
몇 번까지 다시 보내고 포기할지는 운영 손잡이로 나와 있습니다. 리눅스 커널 문서는
tcp_retries2 를 RTO 재전송이 확인되지 않은 채로 남아 있을 때 살아 있는 TCP 연결의 타임아웃에
영향을 주는 값으로 적습니다. N 을 주면, 초기 RTO 가 TCP_RTO_MIN 인 채로 지수 백오프를
따르는 가상의 연결이 N 번 다시 보낸 뒤 N+1 번째 RTO 에서 연결을 죽입니다.
tcp_retries2 = 15 기본값 · 가상 타임아웃 924.6초
tcp_retries2 >= 8 RFC 1122 가 권하는 최소 100초에 해당
기본값 15 가 내는 924.6초는 실효 타임아웃의 하한입니다. 실제로는 그 가상 타임아웃을 넘어서는 첫 RTO 에서 연결이 끊깁니다. 같은 문서는 RTO 의 최대값을 줄이면 이 값도 같이 바꿔줄 것을 권합니다.
관련 항목
유실을 판단하는 신호
확인 응답 · 중복 확인 응답 · 선택적 확인 응답 · 누적 확인 응답
다시 보낼 시점을 정하는 시간 값
왕복 시간 · 재전송 타임아웃 · 타임아웃 · 지수 백오프
함께 도는 흐름·혼잡 제어 알고리즘
슬라이딩 윈도 · 혼잡 제어 · 혼잡 윈도 · fast retransmit · 선택적 반복
다시 보낼 데이터를 가리키는 단위
재전송 큐 · 세그먼트 · 패킷 번호 공간 · 스트림 오프셋
이름이 겹치는 이웃
재전송이 남기는 문제와 대응
이 동작을 쓰는 프로토콜
TCP · UDP · QUIC · DNS · MQTT · 존 전송
다시 보냄을 표시·관리하는 요소
DUP 플래그 · QoS · 커널
재전송을 정의하는 표준·문서
RFC 9293 · RFC 6298 · RFC 5681 · RFC 2018 · RFC 9002 · RFC 1035 · RFC 1122
다른 이름: retransmission · 재송신