重启后的进程把同一批订单又发了一遍
한국어 원문으로 표시합니다.
목표
죽었다 살아난 전송 프로세스의 체크포인트와 전송 로그에서 재처리 구간을 찾고, 거래소 접수 확인과 대조해 실제로 두 번 접수된 주문만 골라낸 뒤, 결정적 주문 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 의 두 시각 차이입니다.