Capital Markets and Settlement
The restarted process sent the same orders all over again
한국어 원문으로 표시합니다.
목표
죽었다 살아난 전송 프로세스의 체크포인트와 전송 로그에서 재처리 구간을 찾고, 거래소 접수 확인과 대조해 실제로 두 번 접수된 주문만 골라낸 뒤, 결정적 주문 id 와 중복 제거 창 길이를 자료에서 구해 복구 절차서를 남깁니다.
왜 중요한가
재처리는 없앨 수 없습니다. 체크포인트를 보낸 뒤에 저장하면 죽었을 때 그 구간이 다시 나가고, 보내기 전에 저장하면 그 구간이 빠집니다. 둘 사이에 안전한 지점이 없기 때문에, 전송 경로는 대개 다시 보내는 쪽을 고르고 중복은 받는 쪽에서 거릅니다. 거르려면 같은 신호가 몇 번을 다시 읽혀도 같은 주문 id 를 내야 하는데, 실행마다 새로 매기는 일련번호나 시각을 섞은 id 는 그 조건을 깨뜨립니다. 그리고 정정과 취소는 이전 id 를 가리키므로, id 규칙이 흔들리면 사슬까지 끊어져 취소하려던 주문이 남습니다.
단계
python3로/root/cap/replay/data에 여섯 개 자료 파일을 만듭니다. 생성 스크립트를 그대로 쓰세요.- 체크포인트와 전송 로그를 견줘 재처리 구간을
/root/cap/replay/crash.txt에 적습니다. - 그 구간에서 다시 나갈 주문을
/root/cap/replay/replay.csv와/root/cap/replay/replay.txt에 적습니다. - 신호의 안정된 필드만으로 주문 id 를 만들어
/root/cap/replay/clordid.csv와/root/cap/replay/idcheck.txt에 적습니다. - 접수 확인과 대조해 두 번 받아들여진 주문을
/root/cap/replay/dup.csv와/root/cap/replay/dup.txt에 적습니다. - 체크포인트 저장 시점 두 가지를 각각 계산해
/root/cap/replay/modes.csv에 한 줄씩 적습니다. - 원주문을 가리키지 못하는 취소와 정정을
/root/cap/replay/broken.csv와/root/cap/replay/broken.txt에 적습니다. - 중복 제거 창 길이를
/root/cap/replay/window.txt에, 복구 절차서를/root/cap/replay/runbook.md에 남깁니다.
참고
- 자료는
/root/cap/replay/data에 있습니다.signals.jsonl은 오프셋이 붙은 입력 신호,sent.csv는 실제로 나간 주문 메시지,acks.jsonl은 거래소 접수 확인,checkpoint.json은 어디까지 커밋했는지,incident.json은 죽은 시각과 살아난 시각,policy.json은 주문 id 규칙과 커밋 주기입니다. - 시각은 전부 자료에 못박혀 있습니다.
date로 오늘을 쓰면 같은 자료에서 날마다 다른 답이 나옵니다. - 주문 id 규칙은
policy.json의clordid에 있습니다.fields를 그 차례대로sep으로 이어 붙여hash로 요약하고, 앞hex_len글자만 잘라prefix를 붙입니다. 값은 모두 문자열로 바꿔 잇습니다. intent가hold인 신호는 문턱을 못 넘어 주문이 나가지 않았습니다. 신호 수와 주문 수는 다릅니다.- 흔한 실수 1: 전송 로그만 보고 중복을 세는 것. 접수 확인에서 거절된 건은 거래소에 남지 않았으므로 이중 접수가 아닙니다.
- 흔한 실수 2: 6단계에서 배치 경계를 잊는 것. 체크포인트는 한 건마다가 아니라
commit_every건마다 저장됩니다. - 규격: FIX Trading Community 표준, FIXimate 에서
ClOrdID와OrigClOrdID의 뜻을 확인할 수 있습니다. 이 실습의 종목 코드와 실행 이름은 모두 합성입니다.
장애가 남긴 자료 만들기
python3 로 /root/cap/replay/data 에 signals.jsonl·sent.csv·acks.jsonl·checkpoint.json·incident.json·policy.json 여섯 개를 만듭니다. 난수를 쓰지 않는 생성 스크립트를 그대로 쓰세요.
폐쇄망에는 내려받을 표본이 없으니 자료부터 직접 만듭니다. 난수를 쓰지 않아야 누가 몇 번을 돌려도 같은 자료가 나오고, 서로의 판정을 대조할 수 있습니다. 채점기는 파일 내용을 표준형으로 바꿔 지문을 대조하므로 자료를 손으로 고치면 뒤 단계가 전부 막힙니다.
어디부터 다시 읽었는지 찾기
/root/cap/replay/crash.txt 에 crashed_run·resumed_run·committed_offset·last_sent_offset·replay_from·replay_to 여섯 줄을 key=value 로 적으세요.
체크포인트는 어디까지 처리했다고 적혀 있고, 전송 로그에는 죽은 실행이 실제로 어디까지 보냈는지가 남아 있습니다. 두 값이 다르면 그 사이가 다시 읽히는 구간입니다. 재시작은 커밋한 다음 오프셋부터 읽습니다.
그대로 재시작하면 무엇이 다시 나가는지 세기
/root/cap/replay/replay.csv 에 첫 줄 offset,intent,symbol,side,qty,limit_px 를 두고 재처리 구간에서 주문이 나가는 신호를 한 줄씩 적고, /root/cap/replay/replay.txt 에 window_signals·window_orders·window_notional_krw 세 줄을 적으세요.
구간은 앞 단계에서 구한 replay_from 부터 replay_to 까지입니다. 그 안의 신호를 모두 세는 것과 주문이 나가는 신호만 세는 것은 다릅니다. 금액은 주문 수량 곱하기 지정가를 더한 값이고, 노출을 만드는 신규와 정정만 더합니다.
다시 읽어도 같은 주문 id 만들기
/root/cap/replay/clordid.csv 에 첫 줄 offset,intent,clordid,orig_clordid 를 두고 주문이 나가는 신호마다 한 줄씩 적고, /root/cap/replay/idcheck.txt 에 order_signals·distinct_deterministic_ids·offsets_sent_twice·offsets_with_two_sent_ids·chain_resolvable 다섯 줄을 적으세요.
규칙은 policy.json 의 clordid 에 있습니다. fields 를 그 차례대로 sep 으로 이어 붙여 sha256 으로 요약하고 앞 hex_len 글자에 prefix 를 붙입니다. 신규는 orig_clordid 를 빈 칸으로 두고, 취소와 정정은 ref_offset 이 가리키는 신호에 같은 규칙을 적용한 값을 적습니다. chain_resolvable 은 그렇게 만든 orig 가 이 표 안에 실재하는 취소와 정정의 수입니다.
정말 두 번 받아들여진 주문만 남기기
/root/cap/replay/dup.csv 에 첫 줄 offset,msg_type,clordid_a,clordid_b,symbol,side,qty,notional_krw 를 두고 양쪽 실행의 메시지가 모두 접수된 오프셋을 적고, /root/cap/replay/dup.txt 에 dup_accepted·dup_new·dup_cancel·dup_replace·dup_notional_krw 다섯 줄을 적으세요.
전송 로그에 두 번 나온 오프셋이 곧 이중 접수는 아닙니다. acks.jsonl 에서 두 메시지가 모두 accepted 인 것만 남기세요. clordid_a 는 실행 이름 오름차순으로 앞선 쪽, clordid_b 는 뒤선 쪽입니다. 금액은 수량 곱하기 가격이고, dup_notional_krw 는 신규 주문 줄만 더합니다.
체크포인트를 언제 저장하느냐로 갈리는 것
/root/cap/replay/modes.csv 에 첫 줄 mode,committed_offset,resume_offset,duplicate_orders,missing_orders 를 두고 commit_after 와 commit_before 두 줄을 적으세요.
체크포인트는 commit_every 건마다 저장됩니다. 보낸 뒤에 저장하는 방식이면 죽기 직전에 끝난 마지막 배치까지만 커밋되어 있고, 보내기 전에 저장하는 방식이면 지금 처리 중이던 배치의 끝 오프셋이 이미 커밋되어 있습니다. 재시작은 커밋한 다음 오프셋부터 읽습니다. 앞의 것은 중복을, 뒤의 것은 누락을 냅니다.
취소가 가리키던 주문이 사라졌다
/root/cap/replay/broken.csv 에 첫 줄 run_id,clordid,offset,msg_type,orig_clordid,reason 을 두고 원주문을 가리키지 못하는 취소와 정정을 적고, /root/cap/replay/broken.txt 에 chain_messages·broken_total·unknown_orig·orig_rejected 네 줄을 적으세요. reason 은 unknown_orig 또는 orig_rejected 입니다.
사슬이 끊기는 길은 두 가지입니다. 가리키는 주문 id 가 전송 로그 어디에도 없으면 unknown_orig 이고, 있기는 한데 거래소가 그 원주문을 거절했으면 orig_rejected 입니다. 둘 다 결과는 같습니다 — 취소하려던 주문이 남습니다. chain_messages 는 전송 로그의 취소와 정정 전부입니다.
중복 제거 창을 자료에서 구하고 절차서 쓰기
/root/cap/replay/window.txt 에 offsets_sent_twice·max_replay_delay_sec·outage_sec·recommended_window_sec 네 줄을 적고, /root/cap/replay/runbook.md 에 ## 무슨 일이 있었나, ## 왜 두 번 나갔나, ## 돈으로 얼마인가, ## 복구 절차, ## 무엇을 고쳐야 하나 다섯 절을 그 차례대로 각각 60자 이상 쓰세요. 절차서 본문에는 5단계의 dup_notional_krw 값과 이 단계의 recommended_window_sec 값을 숫자로 적습니다.
재처리 지연은 같은 오프셋이 처음 나간 시각과 다시 나간 시각의 차이입니다. 두 번 나간 오프셋 전부에서 재고 가장 큰 값을 씁니다. 권고 창은 그 값에 policy.json 의 dedup_safety_multiple 을 곱하고 dedup_round_sec 의 배수로 올림한 값입니다. outage_sec 은 incident.json 의 두 시각 차이입니다.