소켓 응용 1/5 · DELIVERY
답이 없을 때,
다시 보내도 될까요?
연결이 끊기면 보낸 메시지가 도착했는지 알 수 없습니다. ACK, 메시지 ID, 재전송과 중복 제거를 한 화면에서 비교합니다.
ACK까지 재시도
나쁜 결과와 안전한 결과를 비교하세요
ACK가 사라진 결제 요청을 재생합니다
보는 순서① 보호 없음 선택→② 단계 실행→③ 실행 횟수 비교
- 1요청
- 2실행
- 3재전송
- 4중복
그냥 다시 보내기 시나리오입니다. 실험 시작을 눌러 네 장면을 따라가세요.
화면에서 코드로 옮길 때
이 편의 원리 세 가지
send가 성공해도 상대 프로그램의 처리가 끝났다는 뜻은 아닙니다.
새 ID로 다시 보내면 서버는 새로운 작업으로 이해합니다.
처리한 ID와 결과를 저장해야 같은 요청에 같은 답을 돌려줄 수 있습니다.
구현 체크리스트
최소 네 조각으로 시작하세요.
데모의 시각 요소를 실제 프로토콜 필드와 런타임 동작으로 옮기면 됩니다.
- 1메시지 ID비즈니스 작업마다 고유하고 재시도 중에는 변하지 않는 ID
- 2ACK 제한 시간무한 대기하지 않고 재시도를 시작할 기준 시간
- 3중복 제거 저장소ID, 처리 상태와 이전 결과를 함께 보관
- 4재시도 정책최대 횟수, 지수 백오프와 실패 큐
이 데모에 대하여
같은 요청을 보호 없이, 그리고 보호를 켜고 재생합니다
화면에는 A 와 B 두 가지 설계가 있고 둘 다 정확히 같은 사고를 겪습니다. 결제 요청을 보냈는데 확인 응답이 돌아오는 길에 연결이 끊기는 상황입니다. 보낸 쪽은 도착 여부를 알 수 없으므로 다시 보내는 것 말고 할 수 있는 일이 없는데, 그때 서버에서 결제가 몇 번 일어나는지가 두 설계에서 갈립니다. 화면의 실행 횟수 표시가 이 데모의 결론입니다.
- A 보호 없음을 고르고 네 장면을 끝까지 실행합니다.
- 실제 결제 횟수가 몇 회로 표시되는지 확인합니다.
- 전송 시도 횟수와 실제 결제 횟수를 나란히 비교합니다.
- B 로 바꿔 같은 사고를 다시 재생합니다.
- ID 기록 칸이 언제 채워지는지 확인합니다.
- 재전송된 요청이 새 ID 가 아니라 같은 ID 를 달고 가는지 봅니다.
- 두 설계에서 전송 시도 횟수는 같은데 결제 횟수만 다른지 확인합니다.
- 연결이 끊긴 구간에서 클라이언트가 무엇을 알 수 있는지 정리합니다.
- 확인 응답 칸이 기다리는 중에서 수신으로 바뀌는 시점을 확인합니다.
- 두 설계의 같은 장면을 나란히 놓고 어느 칸이 다른지 짚어 봅니다.
- 재전송이 시작되기까지 몇 장면이 걸리는지 세어 봅니다.
화면이 세어 보여주는 값들
01
두 개의 숫자를 따로 셉니다.
전송 시도와 실제 결제를 각각 세어 표시합니다. 이 둘이 같아야 한다고 생각하기 쉽지만 사고가 나면 어긋나고, 어긋나는 방향이 문제입니다. 시도보다 실행이 적으면 요청이 유실된 것이고 많으면 중복 실행입니다. 화면을 한 숫자로 합쳤다면 이 데모가 보여주려는 것이 사라졌을 것입니다.
02
끊긴 구간에서는 아무것도 알 수 없습니다.
회선이 끊긴 것으로 표시되는 동안 클라이언트 쪽 상태는 확인 응답을 기다리는 채로 멈춰 있습니다. 요청이 서버에 닿았는지, 처리까지 끝났는지, 응답만 사라진 것인지 구분할 방법이 없습니다. 재시도 여부를 판단할 근거가 없다는 사실 자체가 이 설계 문제의 출발점입니다.
03
같은 ID 로 다시 보내는 것이 조건입니다.
B 에서 재전송되는 요청은 처음과 같은 식별자를 달고 갑니다. 새 식별자로 보내면 서버 입장에서는 완전히 다른 요청이므로 중복인지 알아볼 방법이 없습니다. 화면에서 재전송 표시에 같은 ID 라는 점을 함께 적어 둔 것은, 재시도만 구현하고 식별자를 매번 새로 만들면 아무것도 해결되지 않기 때문입니다.
04
서버가 결과를 기억해야 합니다.
ID 기록 칸이 채워진 뒤에 같은 요청이 다시 오면 서버는 새로 처리하지 않고 기억해 둔 결과를 그대로 돌려줍니다. 중복을 그냥 무시하는 것과는 다른데, 무시하면 클라이언트는 여전히 답을 못 받습니다. 처리는 한 번만 하되 응답은 매번 주는 것이 이 구조의 핵심입니다.
05
네 장면으로 고정했습니다.
두 설계 모두 같은 수의 장면으로 되어 있어서 같은 시점끼리 나란히 비교할 수 있습니다. 시나리오 길이가 다르면 어느 시점에서 갈라졌는지 짚기 어려워지기 때문에, 사고가 나는 지점과 대응이 일어나는 지점을 같은 위치에 맞춰 두었습니다.
06
화면 상태를 판정과 함께 둡니다.
각 장면은 위험 · 주의 · 양호 중 하나의 판정을 함께 들고 있고 화면 색이 그에 따라 바뀝니다. 숫자만 보고 이것이 문제인지 아닌지 판단하려면 배경 지식이 필요한데, 판정을 함께 표시하면 처음 보는 사람도 어느 장면이 사고인지 바로 알 수 있습니다.
07
정확히 한 번이라는 말은 피했습니다.
메시지가 정확히 한 번 전달되도록 만들 수 있다는 표현은 오해를 부릅니다. 실제로 만들 수 있는 것은 여러 번 도착해도 결과가 한 번만 생기는 구조이고, 이 데모가 보여주는 것도 그쪽입니다. 전달 횟수를 통제하려 하지 않고 결과를 통제한다는 점이 설계의 방향을 정합니다.
08
사용자에게는 한 번으로 보여야 합니다.
화면이 결제 버튼을 누른 사람의 시점을 함께 보여주는 이유는, 내부적으로 요청이 몇 번 오갔든 사용자가 확인하는 결과는 하나여야 하기 때문입니다. 재시도가 진행되는 동안 버튼을 다시 누를 수 있게 두면 사용자가 만든 중복까지 겹쳐 문제가 커집니다. 통신 구간의 중복 제거와 화면 쪽의 중복 방지는 서로를 대신하지 못하고 둘 다 필요합니다.
09
실패를 어디에 남길지도 설계입니다.
재시도를 몇 번 하고도 끝내 확인을 받지 못하면 그 요청은 어딘가에 남아야 합니다. 조용히 버리면 사용자는 결제가 됐는지 안 됐는지 모른 채로 남고, 무한히 재시도하면 서버에 부담이 쌓입니다. 이 화면은 성공하는 경우까지만 보여주지만, 실제 구현에서는 포기하는 조건과 포기한 뒤의 처리를 함께 정해 두어야 완성됩니다.
개념을 재생하는 화면입니다.
브라우저는 어떤 서버와도 통신하지 않고 미리 정해 둔 장면을 순서대로 보여줍니다. 사고는 무작위로 일어나지 않고 정해진 시점에만 일어납니다. 실제 구현에는 여기 없는 것이 많은데, 기억해 둔 식별자를 언제까지 보관할지, 그 저장소가 서버 여러 대에서 공유되는지, 응답을 기다리는 제한 시간을 얼마로 잡을지, 재시도 간격을 어떻게 늘려 갈지, 끝내 실패한 요청을 어디에 모아 둘지가 모두 별도로 정해져야 합니다. 특히 결제처럼 되돌리기 어려운 작업에서는 이 화면이 다루는 중복 제거만으로 충분하지 않고, 처리 중인 상태와 완료된 상태를 구분해 저장하는 설계가 함께 필요합니다. 화면의 결제 예시는 결과가 눈에 보이는 작업이라 고른 것이고, 실제로는 알림 발송이나 외부 API 호출처럼 중복이 조용히 쌓이는 작업에서 이 문제가 더 늦게 발견됩니다. 사용자가 항의해야 알게 되는 종류의 사고라, 실행 횟수를 세어 두는 지표가 있으면 훨씬 빨리 잡을 수 있습니다.
참고 자료RFC 6455 — WebSocket 프로토콜 ↗RFC 9293 — Transport Control Protocol (확인 응답) ↗
실험을 마쳤다면