연결이 끊기면 Reconnect() 하나가 소켓을 열고 로그인하고 계좌와 화면 데이터를 다시 불러왔다. 실패 로그에는 “재접속 실패”만 남아 어느 단계가 문제인지 알기 어려웠다. 중간 단계가 성공했는데 처음부터 다시 시작해 중복 로그인이나 구독도 생겼다.
재접속은 한 번의 행동이 아니라 서로 다른 실패 조건을 가진 복구 과정이었다.
연결·인증·동기화 단계의 분리
현재 위치를 상태로 표현했다.
WaitingForNetwork
-> ConnectingTransport
-> RestoringSession
-> SyncingEssentialData
-> Resubscribing
-> Ready
각 단계는 필요한 입력과 성공 결과를 명확히 했다. 네트워크가 없으면 인증을 시도하지 않고, 세션이 만료됐으면 데이터 동기화로 넘어가지 않았다.
단계별 재시도 정책
전송 연결은 지수 backoff와 jitter를 사용해 제한적으로 재시도할 수 있었다. 인증 만료는 자동 재시도하지 않고 로그인으로 전환했다. 데이터 일부 실패는 핵심 데이터와 부가 데이터에 따라 Ready 진입 여부가 달랐다.
TimeSpan RetryDelay(int attempt)
{
var baseMs = Math.Min(30000, 500 * Math.Pow(2, attempt));
return TimeSpan.FromMilliseconds(baseMs + Random.Shared.Next(0, 300));
}
앱 생명주기와 사용자 취소가 오면 현재 대기를 즉시 끝냈다.
단계 전이와 원인을 포함한 로그
단순 성공/실패 대신 이전 상태, 다음 상태, 걸린 시간, 안전한 오류 분류를 기록했다. 토큰과 사용자 데이터는 남기지 않았다.
reconnect transition ConnectingTransport -> RestoringSession elapsed=820ms
reconnect stopped state=RestoringSession reason=credential-expired
운영 로그만으로 어느 계층을 먼저 봐야 하는지 좁힐 수 있었다.
제품 기능으로서의 복구 흐름
정상 연결에서 잘 되는 기능만 만들고 재접속을 예외 처리로 남겨두면 실제 사용에서 상태가 쉽게 꼬였다. 사용자는 이동 중 네트워크를 자주 잃고 앱은 언제든 백그라운드로 갈 수 있었다.
단계를 나눈 뒤 코드는 조금 늘었지만 실패 지점과 다음 행동이 보였다. 이후 긴 비동기 흐름을 만나면 함수 길이보다 의미 있는 체크포인트와 재개 조건을 먼저 찾는다.
후속 확인 항목
나중에 같은 증상을 다시 만나지 않으려고 문서에 적힌 기준으로 구현을 한 번 더 정리했다.
RFC 9110은 HTTP 메서드의 의미와 멱등성을 구분한다. 같은 요청을 다시 보내도 의도한 서버 효과가 한 번과 같아야 멱등하다고 말할 수 있다. 클라이언트에서 버튼을 잠그는 것은 중복 조작을 줄이지만, 타임아웃 뒤 재전송이나 다른 기기 요청까지 막아 주지는 않는다.
또 상태 코드만으로 화면 상태를 모두 결정하기보다 응답이 어떤 요청과 선택 조건에 대한 것인지 확인해야 한다. 네트워크 성공과 현재 화면에 적용 가능한 응답은 다른 판단이다.
다음에 재접속 단계 분리와 원인 추적 같은 문제를 보면 아래 순서부터 확인하려고 한다.
- 재귀 호출 대신 명시적 반복과 상한
- 동시에 실행되는 갱신 작업의 단일 소유자
- 이전 세대 응답을 버리는 번호
- 중단 뒤 사용자가 다시 시작하는 경로
재시도는 횟수보다 같은 요청을 식별하는 키와 서버의 중복 처리 규칙이 먼저 정해져야 한다.
공식 참고 자료
- RFC 9110 HTTP Semantics — HTTP 메서드·상태 코드·멱등성 의미