블로그
네트워크 중단을 우아하게 처리하는 방법
네트워크는 끊임없이 실패합니다. Tactic Remote가 네트워크 중단 시 작업이나 컨텍스트를 잃지 않도록 보장하는 방법을 소개합니다.
이 카테고리의 제품 및 엔지니어링 업데이트를 확인하세요.
카테고리 보기짧은 네트워크 장애 후 스피너가 표시되고 다시는 복구되지 않는 원격 도구를 사용해 본 적이 있다면, 우리가 왜 재연결 신뢰성에 집착하는지 이해할 수 있을 것입니다. 모바일 개발 도구의 세계에서 네트워크 중단은 엣지 케이스가 아닙니다 — 일상적인 케이스입니다. 스마트폰은 Wi-Fi와 셀룰러 사이를 전환합니다. 엘리베이터는 신호를 차단합니다. 카페 네트워크는 본질적으로 불안정합니다.
Tactic Remote의 재연결 엔진은 하나의 원칙을 중심으로 설계되었습니다: 네트워크 중단은 가능한 한 사용자에게 투명해야 하며, 투명하게 처리할 수 없을 때는 우아하게 알려야 한다.
상태 머신
재연결 로직의 핵심은 6개의 상태를 가진 유한 상태 머신입니다:
CONNECTED: 정상 작동. 데이터가 자유롭게 흐릅니다.
UNSTABLE: 연결 시도가 실패했지만 일시적인 것으로 추정됩니다. 1-3초 간격으로 재시도합니다. UI에 미묘한 경고 표시기가 나타나지만 사용자 작업을 중단하지 않습니다.
RECONNECTING: 여러 번의 실패가 실제 문제를 시사합니다. 재시도 간격이 지수적으로 증가합니다(5초, 10초, 20초, 상한 30초). UI에 명확한 "재연결 중" 배너가 표시됩니다.
RECOVERING: 연결이 재설정되었지만 상태 동기화가 필요합니다. 클라이언트가 서버에 상태 델타를 요청하고 적용한 후, 조정이 완료되면 CONNECTED 상태로 전환됩니다.
DISCONNECTED: 재시도 타임아웃(기본: 5분)을 초과했습니다. 사용자가 수동으로 재연결을 트리거해야 합니다. 이는 완전히 오프라인인 기기의 무한 배터리 소모를 방지합니다.
상태 조정 프로토콜
재연결은 쉽습니다. 조정 — 클라이언트와 서버를 다시 동기화하는 것 — 이 어려운 부분입니다.
iPhone이 중단 후 재연결하면, 마지막으로 알려진 상태 벡터(마지막으로 처리한 터미널 업데이트의 시퀀스 번호, 세션 ID, 타임스탬프)를 서버에 전송합니다. 서버는 이를 사용하여 클라이언트가 놓친 것을 정확히 계산합니다.
시나리오 1: 짧은 중단(30초 미만). 서버의 출력 버퍼에 놓친 모든 업데이트가 여전히 포함되어 있습니다. 순서대로 리플레이하고 클라이언트가 순차적으로 적용합니다. 사용자 관점에서 터미널이 1초 미만에 "따라잡습니다" — 놓친 출력이 빠른 스크롤로 나타납니다.
시나리오 2: 중간 길이 중단(30초 - 5분). 출력 버퍼가 부분적으로 순환되었을 수 있습니다. 서버가 부분 리플레이에 이어 전체 터미널 스냅샷을 전송합니다. 클라이언트는 리플레이를 적용한 후 스냅샷과 대조하여 일관성을 검증합니다.
시나리오 3: 긴 중단(5분 초과). 서버가 전체 스냅샷만 전송합니다. 클라이언트가 전체 상태를 교체합니다. 이는 가장 비효율적인 경로이지만 얼마나 많은 것을 놓쳤든 정확성을 보장합니다.
조정 응답에 pendingApprovals도 포함된다는 점에 주목하세요. 스마트폰이 오프라인인 동안 Claude Code가 승인을 요청했다면, 해당 요청은 재연결의 일부로 전달됩니다. 이것은 매우 중요합니다 — 이것이 없으면 Claude Code는 사용자가 한 번도 보지 못한 승인을 계속 기다리게 됩니다.
우리를 무너뜨릴 뻔한 엣지 케이스
승인 중 연결 끊김
사용자가 승인 알림을 받고, "승인"을 탭하고, 승인이 서버에 도달하기 전에 연결이 끊깁니다. 작업이 실행될까요?
우리의 해결책: 승인은 확인 응답 프로토콜을 사용합니다. 서버는 클라이언트의 승인 메시지를 수신하고 클라이언트에게 수신 확인을 보낼 때까지 승인된 작업을 실행하지 않습니다. 승인 중 연결이 끊어지면, 재연결 시 승인이 재전송됩니다. 중복 승인은 멱등적입니다 — "요청 #47 승인"을 두 번 보내도 한 번 보낸 것과 같은 효과입니다.
오프라인 중 세션 종료 케이스
스마트폰이 오프라인인 동안 Claude Code 작업이 완료되었습니다. 재연결하면 세션이 종료 상태입니다. 클라이언트가 최종 출력을 표시해야 할까요? 완료 알림을? 둘 다?
조정 응답에 sessionEndState를 포함하여 처리합니다. 클라이언트는 최종 터미널 상태를 렌더링하고 무슨 일이 일어났는지 요약과 함께 "오프라인 중 작업이 완료되었습니다" 배너를 표시합니다. 사용자는 오래된 진행 중 출력을 바라보는 대신 즉시 결과를 확인할 수 있습니다.
Mac 재시작 케이스
가장 까다로운 시나리오: 스마트폰이 오프라인인 동안 Mac이 재시작된 경우(시스템 업데이트, 정전, 크래시 등). 스마트폰이 재연결하면 서버에는 이전 세션에 대한 기억이 없습니다.
우리의 해결책: Mac 컴패니언 앱은 세션 메타데이터(세션 ID, 프로젝트 경로, 마지막 알려진 상태)를 디스크에 영속화합니다. 재시작 후 이 메타데이터를 읽고, 가능한 경우 생존한 tmux 세션에 다시 연결합니다. 조정 프로토콜은 서버 에포크 카운터를 통해 서버 재시작을 감지하고, 증분 조정을 시도하는 대신 전체 상태 재구축을 시작합니다.
성능 특성
재연결 성능을 세 가지 지표로 측정합니다:
시각적 복구 시간 — 네트워크 복원에서 사용자가 현재 터미널 상태를 볼 때까지의 시간. 로컬 네트워크의 P95는 1.8초, Cloudflare Tunnel은 3.2초입니다.
데이터 효율성 — 조정 중 전송되는 데이터량과 전체 새로고침 대비. 60초 미만의 중단에서 조정 프로토콜은 전체 새로고침 대비 85% 적은 데이터를 전송합니다.
정확성 — 상태 불일치에 대한 무관용. 무작위 타이밍으로 수천 번의 연결 끊김/재연결 사이클을 시뮬레이션하는 지속적 통합 테스트를 실행하고, 최종 클라이언트 상태가 서버 상태와 바이트 단위로 일치하는지 검증합니다.
개발자를 위한 교훈
실시간 모바일 애플리케이션을 구축하고 있다면, 우리의 경험에서 세 가지 원칙을 제시합니다:
-
첫날부터 연결 끊김을 전제로 설계하세요. 항상 연결된 상태를 전제로 설계된 시스템에 재연결을 추가하는 것은 간헐적 연결을 일급 관심사로 구축하는 것보다 몇 자릿수 더 어렵습니다.
-
멱등성은 최고의 친구입니다. 클라이언트에서 서버로의 모든 메시지는 안전하게 리플레이할 수 있어야 합니다. 이 단일 속성만으로 재연결 버그의 전체 카테고리를 제거합니다.
-
시뮬레이터가 아닌 실제 네트워크에서 테스트하세요. 네트워크 상태 시뮬레이터는 실제 세계 장애의 전체 다양성을 재현할 수 없습니다 — 부분적 패킷 전달, DNS 해석 지연, TLS 핸드셰이크 타임아웃. 우리는 실제 셀룰러 네트워크, 엘리베이터 안, 혼잡한 컨퍼런스 Wi-Fi에서 테스트합니다.
신뢰할 수 있는 재연결은 화려한 작업이 아닙니다. 잘 작동할 때 사용자는 칭찬하지 않습니다 — 단지 중단을 인지하지 못할 뿐입니다. 하지만 실패하면 신뢰는 즉시 사라집니다. 이 투자는 가치가 있다고 생각하며, 사용자가 Tactic Remote를 프로덕션 워크플로에 기꺼이 사용한다는 것이 이를 증명합니다.