개발

코인하우스 실시간 시세 구현기 — WebSocket과의 싸움

2021-10-26·2분 읽기

실시간 데이터의 까다로움

암호화폐 시세는 1초에도 수십 번씩 변합니다. 이걸 앱에서 실시간으로 보여주려면 HTTP 요청 방식으로는 부족합니다. 계속 서버에 요청을 보내는 폴링 방식은 과도한 트래픽과 배터리 소모를 야기합니다.

해결책은 WebSocket이었습니다. 한 번 연결을 맺으면 서버가 데이터가 바뀔 때마다 클라이언트에 푸시해주는 방식입니다.

거래소마다 다른 WebSocket 프로토콜

문제는 거래소마다 WebSocket 인터페이스가 다르다는 점입니다.

업비트는 바이너리 메시지로 데이터를 보내고, 구독할 코인 목록을 JSON 배열로 먼저 전송해야 합니다. 빗썸은 텍스트 JSON 메시지를 씁니다. 바이낸스는 스트림 이름을 URL에 포함시키는 방식입니다.

각 거래소 API 문서를 읽고, 구독 메시지 형식을 맞추고, 수신 데이터를 파싱하는 코드를 각각 작성해야 했습니다.

공통 인터페이스로 추상화

세 거래소의 WebSocket 코드를 별도로 관리하면 나중에 거래소를 추가하거나 수정할 때 복잡해질 게 뻔했습니다. 공통 인터페이스를 만들기로 했습니다.

protocol ExchangeWebSocket {
    var exchange: Exchange { get }
    func connect(coins: [String])
    func disconnect()
    var onPriceUpdate: ((PriceData) -> Void)? { get set }
}

각 거래소 구현체는 이 프로토콜을 따르고, 내부에서 알아서 연결 방식을 처리합니다. 상위 레이어에서는 거래소 구분 없이 같은 방식으로 데이터를 받을 수 있게 됐습니다.

연결 끊김 처리

WebSocket은 연결이 불안정할 수 있습니다. 서버 점검, 네트워크 전환, 앱 백그라운드 진입 등 여러 이유로 연결이 끊깁니다.

자동 재연결 로직을 추가했습니다. 연결이 끊기면 지수 백오프(exponential backoff) 방식으로 재시도합니다. 처음에는 1초 후, 다음은 2초, 4초, 최대 30초까지 대기 시간을 늘려가며 재연결을 시도합니다.

앱이 포그라운드로 돌아올 때는 즉시 재연결하도록 scenePhase 변화를 감지했습니다.

결과

세 거래소의 시세가 거의 지연 없이 실시간으로 한 화면에 업데이트되는 걸 처음 봤을 때의 쾌감이 기억납니다. 개발하면서 기술적으로 가장 도전적이었던 부분이었고, 그만큼 완성됐을 때 보람도 컸습니다.

다음 글에서는 커뮤니티 기능 개발 이야기를 합니다.