자본시장과 결제 · 주문의 생애주기와 시계 · 이론
시계가 셋이면 순서도 셋이다
한 줄 요약
거래소 시각·게이트웨이 수신 시각·애플리케이션 처리 시각은 서로 다른 시계이고, 어느 시계로 정렬하느냐에 따라 "취소가 먼저인가 체결이 먼저인가" 의 답이 뒤집힌다.
왜 이게 필요했나
이벤트 하나에는 대개 시각이 두 개 이상 찍혀 있습니다. 처음 보면 왜 이렇게 많은지 모르겠고, 아무거나 하나 골라 정렬하게 됩니다. 그 선택이 결론을 바꿉니다.
ts_exch 거래소가 그 일이 일어났다고 찍은 시각ts_recv 우리 게이트웨이가 그 메시지를 받은 시각ts_proc 우리 애플리케이션이 그것을 처리한 시각세 시각의 간격은 네트워크 경로와 큐 길이에 따라 달라집니다. 문제는 경로가 하나가 아니라는 것입니다. 체결 통보와 취소 응답이 서로 다른 세션, 다른 회선, 다른 프로세스를 통과해 오면, 거래소에서 먼저 일어난 일이 우리에게 나중에 도착합니다.
어떻게 동작하나
앞 레슨의 그 주문을 두 시계로 정렬해 보면 이렇게 갈립니다.
거래소 시각 순서 수신 시각 순서 cancel_req cancel_req fill (누적 300) cancelled cancelled fill (누적 300)-------------------------- --------------------------마지막 이벤트 = cancelled 마지막 이벤트 = fill결론: 취소된 주문 결론: 체결된 주문같은 여섯 줄, 같은 접는 규칙, 다른 답입니다. 그리고 이 차이는 드물지 않습니다. 취소 응답은 짧은 메시지라 빨리 오고 체결 통보는 상대적으로 느린 경로를 타는 구성이 흔해서, 취소와 체결이 몇 밀리초 안에 경합하면 거의 매번 순서가 뒤집힙니다.
어느 쪽을 근거로 삼아야 하는가. 시장에서 실제로 일어난 일의 순서는 거래소 시각입니다. 우리 수신 시각은 우리 인프라의 사정이지 사실이 아닙니다. 그래서 사후 재구성·감사·분쟁 대응은 거래소 시각으로 정렬합니다. 같은 시각에 두 이벤트가 있으면 거래소가 준 일련번호로 가릅니다. 정렬 기준이 하나뿐이면 같은 입력에서 매번 다른 답이 나옵니다.
다만 수신 시각도 버리지 않습니다. 두 시각의 차이가 지연이고, 지연이 튀는 구간을 찾는 데 쓰입니다. 그리고 실시간 처리기는 미래를 볼 수 없으므로 도착한 순서대로 처리할 수밖에 없습니다. 실시간 결과와 사후 재구성 결과가 다를 수 있다는 것을 인정하고, 사후 재구성이 정답이며 실시간 결과는 그것으로 보정한다는 절차를 두는 것이 현실적인 답입니다.
가장 좋은 설계는 순서에 결과가 좌우되지 않게 만드는 것입니다. 이번 경우 규칙을 이렇게 바꾸면 두 시계의 답이 같아집니다 — 재생이 끝난 뒤 누적 체결이 주문 수량 이상이면 상태는 체결이다. 근거는 도메인에 있습니다. 체결은 되돌릴 수 없는 사실이고 취소는 잔량에만 적용됩니다. 잔량이 0인 주문에 취소가 도착하는 것은 정상이며, 그것이 체결을 지우지 않습니다.
현장에서 만나는 모습
거래소와 분쟁이 붙으면 양쪽이 각자의 로그를 들고 나옵니다. 거래소는 자기 시각으로, 우리는 우리 시각으로 정렬한 표를 냅니다. 두 표의 순서가 다르면 그때부터는 사실 확인이 아니라 어느 시계를 인정할 것인가의 문제가 되고, 계약서와 규정은 대개 거래소 시각을 기준으로 씁니다. 그래서 우리 시각만 남기고 거래소 시각을 버리는 로그 설계는 분쟁에서 우리를 무장해제시킵니다.
또 하나, NTP 가 맞지 않는 서버 한 대가 조사를 몇 주씩 붙잡습니다. 한 곳에서 게이트웨이 두 대 중 한 대의 시계가 약 400ms 앞서 있었는데, 그 장비를 지난 주문만 취소가 체결보다 먼저인 것으로 기록됐습니다. 데이터만 보면 특정 종목·특정 시간대에 몰린 것처럼 보여서 시장 요인을 한참 찾았습니다. 시각을 다루는 조사에서는 시계 자체를 먼저 의심하는 것이 시간을 아낍니다.
다음 실습에서 할 것
같은 이벤트 로그를 거래소 시각과 수신 시각으로 각각 정렬해 두 벌의 최종 상태표를 만들고, 결론이 뒤집히는 주문을 정확히 집어냅니다. 그다음 순서에 좌우되지 않는 규칙을 넣어 두 벌이 같아지는 것을 확인합니다.