Skip to content
isdnetworks
Go back

늦게 대답하는 상대

(수정됨)

외부 유동성 공급 거래소의 WebSocket 주문 게이트웨이에 주문을 보내는 클라이언트를 만들었는데 가끔 동작이 이상했다. 주문을 연달아 두 번 보내면 첫 번째 응답이 두 번째 뒤에 오는 경우가 있었다.

Table of contents

Open Table of contents

원인 — 방금 읽은 응답이 방금 것이라는 가정

코드는 프레임을 쓰고 바로 읽어서 그 응답을 방금 보낸 주문의 것으로 다루고 있었다. HTTP 처럼 요청과 응답이 한 연결에 하나씩 묶이는 환경에서는 대체로 맞는 가정이다.

한 WebSocket 위에 여러 요청을 실으면 응답이 늦게 오거나 순서가 바뀌거나 이전 주문의 응답이 지금 도착할 수 있다. 그 가정 하나가 화면을 엉뚱한 체결 상태로 바꾸고 있었다.

조치 — 요청과 응답에 붙인 번호

두 가지 방법이 있었다. 요청 하나가 끝날 때까지 다음 요청을 막아 순서를 강제하는 것과, 요청과 응답에 같은 id 를 넣어 번호로 짝을 맞추는 것이다. 뒤쪽을 골랐다. 이유는 JSON-RPC 2.0 명세가 id 를 「요청과 응답의 맥락을 잇는 데 쓰는 값」으로 이미 정해 두고 있고 상대 API 도 그 형식이라서다. 대신 보낸 id 를 기억하는 pending 맵을 클라이언트가 관리해야 하는 것을 감수했다.

id 가 안 맞는 응답은 버리므로 늦게 온 옛 응답이 화면을 바꾸지 못한다. 순서를 보장할 수 없으면 순서에 기대지 않는 방식으로 바꾸는 것이 답이었다.

측정 — 타임아웃을 실측으로 정한다

응답이 아예 안 오는 경우가 있어서 기다릴 시간을 정해야 했다. 정상 상태에서 응답 시간을 여러 번 재서 분포를 봤다.

대부분은 매우 짧고 가끔 한참 걸리는 꼬리가 있었다. 감으로 정하면 짧을 때는 정상 응답을 실패로 처리하고 길 때는 주문이 오래 걸리게 하므로 p99 를 보고 꼬리를 어디까지 받아들일지로 정했다.

주의 — 재시도가 위험한 명령

응답이 없을 때 재시도하고 싶지만 주문은 도착했고 응답만 유실됐을 수 있다. 그러면 재시도가 두 번 주문이 된다.

명령을 성질별로 나누니 client_order_id 를 붙인 신규 주문과 조회는 두 번 해도 결과가 같고 수량 증감과 취소 토글은 위험했다. 위험한 것들을 절대값을 주는 replace 명령으로 바꿔서 클라이언트가 아니라 프로토콜 쪽에서 재시도를 안전하게 만들었다.

연결 상태와 클라이언트 안 로그

WebSocket 은 자주 끊기므로 연결을 상태로 다루고 관리 화면에 표시했다. 끊기면 자동으로 다시 붙되 장이 열려 있지 않은 시간에는 시도하지 않게 해서 상대 쪽 연결 한도를 아꼈다.

운영 중에 나는 문제는 재현이 어려워서 최근 사건을 담는 ArrayDeque 순환 버퍼 로그를 클라이언트 안에 뒀다. 시각과 id 가 함께 남으니 문의가 왔을 때 무슨 일이 있었는지를 재구성할 수 있었다.

정리


Share this post on:

Previous Post
스토어 심사가 실제로 보는 것
Next Post
요약 한 줄과 상세 자료