소켓 응용 3/5 · 상태 동기화 — 모두 같은 화면을 보는 법
실시간 이벤트를 놓친 화면이 서버 버전을 비교하고 최신 스냅숏을 받아 다시 같은 상태로 돌아오는 방법을 배웁니다.

목차
사용자 A와 B가 같은 장비 온도를 보고 있습니다. B의 와이파이가 잠시 끊긴 동안 값이 20도에서 21도, 다시 22도로 바뀌었습니다. B가 재연결하면 다음 이벤트가 올 때까지 20도를 계속 보여 줘도 될까요?
이벤트를 받는 것과 현재 정답을 아는 것은 서로 다른 문제입니다.
상태 동기화 데모의 이벤트만 전송에서는 A가 v3, B가 v1로 갈라집니다. 버전 + 스냅숏에서는 B가 마지막 버전을 알려 주고 서버의 최신 상태를 받아 두 화면이 다시 v3으로 맞춰집니다.
이벤트는 변화이고 상태는 현재 정답입니다
둘을 쉽게 구분해 봅시다.
| 데이터 | 대답하는 질문 | 예 |
|---|---|---|
| 이벤트 | 무엇이 일어났나? | 온도가 1도 올라감 |
| 상태 | 지금 값은 무엇인가? | 현재 온도 22도 |
연결이 계속 살아 있다면 이벤트를 차례로 적용해 현재 상태를 만들 수 있습니다. 하지만 중간 이벤트를 하나라도 놓치면 클라이언트가 계산한 상태가 서버의 정답과 달라질 수 있습니다.
실시간 연결은 반드시 잠시 끊깁니다. 모바일 네트워크 전환, 노트북 절전, 프록시 제한, 서버 재시작 모두 정상 운영에서 생기는 일입니다. 재연결 버튼만 만드는 것으로는 상태가 복구되지 않습니다.
버전 번호가 뒤처짐을 알려 줍니다
서버가 상태를 확정할 때마다 증가하는 version 또는 revision을 붙입니다.
{
"type": "TEMPERATURE_CHANGED",
"value": 22,
"version": 3
}
각 클라이언트는 마지막으로 온전히 적용한 버전을 기억합니다. 재연결 handshake에서 그 번호를 서버에 보냅니다.
{
"type": "RESUME",
"lastAppliedVersion": 1
}
서버의 현재 버전이 3이라면 B가 두 변경만큼 뒤처졌다는 것을 즉시 알 수 있습니다. 버전이 없으면 화면 값만 비교해야 하고, 우연히 값이 같아도 중간에 중요한 변화가 있었는지 판단하기 어렵습니다.
버전의 범위도 계약에 포함해야 합니다. 장비마다 버전을 따로 올린다면
device-7의 v3처럼 대상 ID와 함께 비교하고, 방 전체 이벤트 스트림이라면 그 방의
offset을 사용합니다. 서로 다른 대상의 v3 두 개는 앞뒤를 비교할 수 없습니다.
서버를 재설치할 때 버전이 0으로 돌아갈 수 있다면 epoch나 스냅숏 ID를 함께 두어 이전
세대의 번호와 구분합니다.
작은 차이는 재생하고 큰 차이는 스냅숏으로 맞춥니다
복구 방식은 두 가지가 있습니다.
누락 이벤트 재생
마지막 offset 이후의 이벤트를 순서대로 다시 보냅니다. 각 변화가 중요한 알림, 로그, 거래 내역처럼 이력을 보여 줘야 할 때 적합합니다.
최신 스냅숏 전송
현재 상태 전체와 그 상태의 버전을 한 번에 보냅니다. 대시보드 카드, 접속자 목록, 장비의 현재 설정처럼 최종 상태가 중요한 경우에 단순합니다.
{
"type": "SNAPSHOT",
"state": { "temperature": 22 },
"version": 3
}
실무에서는 둘을 섞습니다. 오래된 스냅숏을 받은 뒤 그 이후 이벤트를 적용하거나, 차이가 작으면 이벤트만 재생하고 보관 기간을 벗어나면 새 스냅숏을 보냅니다.
스냅숏과 이벤트 사이에 틈을 만들지 않습니다
스냅숏을 읽는 사이 새 이벤트가 발생할 수 있습니다.
스냅숏 v10 읽기 시작
이벤트 v11 발생
스냅숏 v10 전송 완료
클라이언트가 v11을 놓치지 않으려면 스냅숏과 기준 offset을 같은 시점으로 묶어야 합니다.
예를 들어 snapshot v10을 전달한 뒤 반드시 v10 이후 이벤트를 이어서 보내는 계약을
만듭니다.
Socket.IO의 connection state recovery도 세션 ID와 마지막 offset을 사용해 일시적 연결 단절 동안 놓친 패킷과 방 정보를 복구합니다. 복구가 항상 성공하는 것은 아니므로 최종적으로 전체 상태를 다시 동기화할 경로가 필요하다고 명시합니다.
화면에는 연결 상태를 솔직하게 표시합니다
오프라인인데 마지막 값을 그대로 보여 주면 사용자는 그 값이 현재 정보라고 오해할 수 있습니다.
- 값 옆에
연결 끊김과 마지막 갱신 시각을 표시합니다. - 재연결 중에는 변경 입력을 막거나 임시 저장임을 알립니다.
- 복구가 끝나기 전에는
동기화 중상태를 사용합니다. - 스냅숏 적용이 완료된 뒤에만
최신으로 표시합니다.
상태 동기화는 네트워크 코드뿐 아니라 사용자에게 정보의 신선도를 전달하는 UI 문제이기도 합니다.
스냅숏은 현재 정답 전체를 담기 때문에 이벤트 한 건보다 민감한 정보가 많을 수 있습니다. 재연결 토큰이 유효하다는 이유만으로 이전 방의 스냅숏을 보내지 말고, 현재 사용자에게 그 대상의 조회 권한이 남아 있는지 다시 확인해야 합니다.
동시에 수정하는 문제는 별도입니다
이 글은 연결이 끊겨 변경을 놓친 화면을 복구하는 문제에 집중합니다. A와 B가 같은 문서를 동시에 수정하는 경우에는 추가 정책이 필요합니다.
- 서버 도착 순서로 마지막 쓰기 채택
- 예상 버전이 다르면 수정 거절
- 필드별 병합
- OT 또는 CRDT 같은 공동 편집 알고리즘
단순 대시보드에 복잡한 CRDT를 먼저 넣을 필요는 없습니다. 데이터가 읽기 중심인지, 동시 수정이 실제로 가능한지부터 확인하세요.
데모에서 볼 순서
- 이벤트만 전송에서 처음에는 모두 v1인지 봅니다.
- B가 오프라인인 동안 서버와 A만 v3이 되는지 확인합니다.
- 재연결만 했을 때 B가 여전히 v1인지 봅니다.
- 버전 + 스냅숏으로 바꿉니다.
- B가
lastVersion=1을 보내고 스냅숏 22도 v3을 받는지 확인합니다.
요약
- 이벤트 스트림만으로는 현재 상태 복구를 항상 보장할 수 없습니다.
- 서버가 기준 상태와 증가 버전을 관리해야 합니다.
- 클라이언트는 마지막 적용 버전을 재연결할 때 보냅니다.
- 누락이 작으면 이벤트 재생, 크면 최신 스냅숏으로 맞춥니다.
다음 편에서는 서버 자체가 여러 대가 됩니다. 다중 서버 소켓 확장에서 한 채팅방이 서버 A와 B로 갈라지는 이유를 확인합니다.
자료: Socket.IO Connection state recovery, Socket.IO Delivery guarantees

