종합 연습 — TCP 의 동작을 패킷의 시점에서 읽어낸다
실무의 장애 조사를 본뜬 8 가지 케이스를, 증상 → 어느 약속의 이야기인가 → 봐야 할 숫자, 의 순서로 구분한다. 막히면 대응하는 장으로 돌아가 복습한다.
마무리의 8 문제 — 증상을 「어느 약속의 이야기인가」로 번역한다
마지막 장은 해설 없는 종합 연습입니다. 실무의 장애 조사를 본뜬 8 가지 케이스에, 전 6 장의 지식을 횡단하여 답합니다. 헤매면 이 강좌의 표어로 되돌아가 주세요 — 지금 깨지려 하는 것은, 도달의 약속인가? 순서인가? 페이스 맞추기인가? 그리고 증상을 숫자로 번역해 주세요: RST 인가 침묵인가, 중복 ACK 의 번호는 어디를 가리키는가, min 을 차지하고 있는 창은 어느 것인가.
| 막히면 | 돌아갈 장 |
|---|---|
| IP 의 성질·TCP/UDP 의 분담·4 요소 조합 | 제 1 장 |
| 핸드셰이크·TIME_WAIT·RST 와 FIN | 제 2 장 |
| seq/ack 의 계산·RTO·중복 ACK 와 빠른 재전송 | 제 3 장 |
| 윈도우 광고·in-flight·윈도우 ÷ RTT·제로 윈도우 | 제 4 장 |
| cwnd·슬로 스타트·로스에 대한 2 단계 반응 | 제 5 장 |
| 바이트 스트림·프레이밍 | 제 6 장 |
이 강좌에서 만든 구분의 지도. 증상 → 패킷의 증거 → 어느 약속의 이야기인가, 의 순서로 내려가면, 무작정 재기동에서 졸업할 수 있습니다.
케이스 7-1 — 연결되지 않는다·끊어진다
제 1〜2 장의 범위입니다. 「즉시 거부인가, 침묵인가」「FIN 인가 RST 인가」로 번역해 주세요.
Q1. 케이스: 새로 배포한 API 서버에 접속할 수 없습니다. 클라이언트 A 에서는 Connection refused 가 즉시 돌아오고, 클라이언트 B 에서는 30 초 기다린 뒤 접속 타임아웃이 됩니다. 각각의 상황에 대한 해석으로 올바른 것은 어느 것입니까.
즉시의 refused 는 RST 가 돌아온 증거 — 호스트에는 도착하고 있는데, 그 포트에서 아무도 리슨하고 있지 않다(프로세스 미기동, 포트 번호 차이 등). 침묵 끝의 타임아웃은 응답이 돌아오지 않는다 — 경로 단절, 호스트 정지, 또는 말없이 버리는 방화벽입니다. A 의 존재로부터 「호스트는 살아 있고 포트에 문제」「B 와의 경로 어딘가에서 차단」까지 좁힐 수 있습니다. 도착한 뒤의 거부와, 도착하지 않는 침묵 — 제 2 장의 구분의 정석입니다.
Q2. 케이스: 로드 밸런서 경유로 백엔드에 대량의 짧은 접속을 맺는 프록시 서버에서, netstat 을 보니 TIME_WAIT 상태의 커넥션이 수만 건 줄지어 있었습니다. 이 상황의 이해로 올바른 것은 어느 것입니까.
TIME_WAIT 는 먼저 FIN 을 보낸 쪽이, 마지막 ACK 의 보험과 오래된 세그먼트의 독 빼기를 위해 남는 정상적인 상태입니다(제 2 장). 단명 접속을 초당 수백 건이나 맺고 버리면, 수십 초의 체류만으로 수만 건이 됩니다. 해는 커넥션 자원(포트 등)의 소비이며, 근본 대책은 접속을 매번 다시 맺지 않고 재사용하는 것. 「이상한 상태를 지운다」가 아니라 「정상적인 상태가 대량 발생하는 설계를 고친다」고 읽는 것이 정답입니다.
Q3. 케이스: 데이터베이스로의 상시 접속이, 심야의 배치 처리에서 몇 시간 만에 쿼리를 보낸 순간에 RST 로 절단되었습니다. 접속 확립부터 유휴까지의 사이에 이상은 없습니다. 가장 의심스러운 원인은 어느 것입니까.
TCP 자체는 유휴 커넥션을 무기한 유지할 수 있습니다(양 끝의 장부가 남아 있으면 된다 — 제 2 장). 하지만 경로 상의 방화벽이나 NAT 는 4 요소 조합마다의 상태 표를 갖고 있어, 유휴가 계속된 항목을 타임아웃으로 지웁니다. 그 후에 도착한 세그먼트는 「소속 불명」이 되어, 파기나 RST 가 됩니다. 대책은 TCP 킵얼라이브나 앱 계층의 정기적인 생존 확인으로 「사용 중」이라고 계속 보여 주는 것입니다. 심야의 혼잡이라면 로스나 재전송이 먼저 보일 것입니다.
케이스 7-2 — 느리다·막힌다
제 3〜5 장의 범위입니다. 「재전송은 어느 쪽의 유형인가」「min 을 차지하고 있는 것은 어느 창인가」로 번역해 주세요.
Q4. 케이스: 전송이 느리다는 신고로 캡처를 보니, 같은 ack 번호의 ACK 가 대량으로 줄지어 있었습니다. 수신 측은 바이트 8000 까지 연속 수신 완료이고, 8001〜9000 의 세그먼트만이 손실되어, 후속(9001 이후)은 계속 도착하고 있습니다. 줄지어 있는 중복 ACK 의 ack 번호는 얼마입니까.
ack 번호는 「다음에 원하는 바이트」이고, 게다가 누적 확인 응답이므로, 구멍(8001〜9000)이 메워질 때까지는 후속이 몇 통 도착해도 ack = 8001 그대로입니다(제 3 장). 즉 중복 ACK 의 번호는 손실된 세그먼트의 선두 바이트를 직접 가리키고 있습니다. 캡처의 중복 ACK 는, 어디를 재전송해야 하는가의 화살표로 읽을 수 있는 것입니다.
Q5. 케이스: 대역폭 1 Gbps(= 125 MB/초)의 전용선으로 해외 거점(RTT 50 밀리초)에 파일을 전송하면 4 MB/초밖에 나오지 않습니다. 조사해 보니, 수신 측 설정에서 윈도우가 200 KB(200,000 바이트)로 고정되어 있었습니다. 이 설정에서의 스루풋 상한은 몇 MB/초입니까. (1 MB = 100 만 바이트)
상한 = 윈도우 ÷ RTT = 200,000 바이트 ÷ 0.05 초 = 4,000,000 바이트/초 = 4 MB/초. 관측값과 딱 일치하므로, 범인은 윈도우 설정으로 확정할 수 있습니다(제 4 장). 125 MB/초의 대역폭을 다 쓰려면 BDP = 125 MB/초 × 0.05 초 = 6.25 MB 이상의 윈도우가 필요합니다. 「회선은 굵은데 느리다」의 첫 번째 용의자는 언제나 윈도우 ÷ RTT 입니다.
Q6. 케이스: 로그 수집 서버로의 전송이 주기적으로 멈춥니다. 캡처에는 수신 측으로부터의 윈도우 0 의 광고와, 송신 측으로부터의 작은 문의 패킷의 반복이 줄지어 있었습니다. 다음에 조사해야 할 곳은 어느 것입니까.
제로 윈도우 + 윈도우 프로브의 조합은 「수신 버퍼가 가득 차서, 앱이 recv 로 읽어내기를 전원이 기다리고 있다」는 상태의 교과서적인 캡처입니다(제 4 장). 네트워크는 올바르게 브레이크를 전하고 있을 뿐이고, 범인은 수신 측 앱의 처리 정체(디스크 쓰기의 느림, GC, 락 등). 로스나 혼잡이라면 재전송이나 중복 ACK 가 보일 것이고, 윈도우 0 과는 다른 풍경이 됩니다.
케이스 7-3 — 데이터가 이상하다
제 6 장의 범위입니다. 「TCP 의 약속 밖에서 무엇을 가정해 버렸는가」를 찾아 주세요.
Q7. 케이스: 소켓에서 수신한 JSON 을 parse 하는 서비스가, 프로덕션에서만 한 달에 몇 번 parse 에러를 냅니다. 에러 시의 데이터를 보면, JSON 이 도중에 잘려 있는 것과, 2 개의 JSON 이 연결되어 있는 것의 양쪽이 있었습니다. 올바른 대처는 어느 것입니까.
도중에 잘림(분할)과 연결(결합)이 양쪽 다 나오는 것은, 「1 recv = 1 메시지」 전제의 전형적인 증상입니다(제 6 장). TCP 는 바이트열과 순서를 완전히 지키고 있으며, 네트워크에도 프로토콜에도 이상은 없습니다. 리트라이는 깨진 잘라내기를 반복할 뿐입니다. 경계는 앱이 만든다 — 프레이밍의 도입과 「쌓고·잘라내고·넘긴다」 루프로의 수정이 유일한 근치입니다.
Q8. 케이스: 전송이 몇 초간 완전히 침묵하고, 그 후에 같은 세그먼트의 재전송부터 재개한다, 는 패턴이 반복되고 있었습니다. 중복 ACK 는 거의 보이지 않습니다. 이 동안 송신 측의 혼잡 윈도우(cwnd)에 일어나고 있는 일은 어느 것입니까.
중복 ACK 가 거의 없음 + 침묵 후의 재전송은, 빠른 재전송이 아니라 RTO 만료형의 재전송입니다(제 3 장). 이때 TCP 는 중증으로 판단하고, cwnd 를 1 MSS 로 되돌려 슬로 스타트부터 다시 합니다(제 5 장). 회복에 몇 RTT 나 걸리므로, 체감은 「몇 초 굳었다가는 느릿느릿 달리기 시작한다」의 반복이 됩니다. 중복 ACK 형(반감으로 계속)과는 증상도 심각도도 다르다 — 재전송의 유형을 구별하는 것이, 성능 문제 해석의 갈림길입니다.
강좌의 끝에 — 숫자의 시점에서 읽는다
수고하셨습니다. 이 강좌에서 암기한 항목은 사실 아주 적습니다. 대신 가져갔으면 하는 것은, 캡처나 장애 로그의 어떤 장면에서도 통용되는 읽기 방식입니다.
- 반드시 도착한다(도착하지 않으면 알아챈다) — ack 는 다음에 원하는 바이트. 중복 ACK 는 결손의 선두를 가리키는 화살표. 재전송에는 경증형(중복 ACK)과 중증형(RTO)이 있다
- 순서대로 — 누적 확인 응답과 수신 버퍼가 흐트러짐을 바로잡는다. 다만 경계는 약속의 밖 — 경계는 앱이 프레이밍으로 만든다
- 상대와 네트워크에 맞춘다 — 상한은 min(rwnd, cwnd) ÷ RTT. 제로 윈도우는 상대의 정체, cwnd 의 낭떠러지는 경로의 혼잡
「어쩐지 타임아웃, 일단 재기동」에서 멈춰 있던 조사가, 어느 약속이·어느 패킷에서·어떻게 깨지려 했는가를 가리킬 수 있는 조사로 바뀌었다면, 이 강좌의 목표는 달성입니다. 다음에 캡처를 열 때, seq 와 ack 와 win 의 나열이 분명 문장처럼 읽힐 것입니다.