← 전체 구조
PR #168 · 지정가 체결 워커

체결 워커 순회 구조

직원 한 명이 3초마다 매장을 돈다. 예약 카드는 서랍 한 곳에 모여 있고, 진열대의 물건은 한정돼 있다. 워커 코드의 대부분은 물건을 빼주는 계산이 아니라 “누구 차례인가”를 다룬다.

등장인물

진열대 order_book 가격표별로 수량이 정해져 있다. 팔리면 줄고, 3초마다 다른 직원이 새로 채운다.
예약 카드 trade_order (지정가) “30,000원 이하면 10주 사겠다.” 손님끼리 주고받지 않고, 모두 진열대에서만 가져간다. 서랍 한 곳에 모여 있다.
직원 한 명 LimitOrderExecutionWorker 3초마다 깨어나 매장을 돈다. 동시에 두 명이 돌지 않는다.

한 바퀴 — 코너를 옮겨 다닌다

코너는 종목 × 방향이다. 삼성전자 사는 줄과 파는 줄은 다른 코너다. 한 코너에서 10장까지만 처리하고 다음으로 넘어간다. 안 그러면 조건에 걸리는 카드가 300장인 코너 하나가 매장 전체를 먹는다.

3초마다 한 번 · 한 바퀴에 100장 또는 5초까지 지금 여기 삼성전자 · 매수 조건에 걸리는 카드 340장 10장 처리 후 이동 삼성전자 · 매도 조건에 걸리는 카드 12장 처리 중 카카오 · 매수 조건에 걸리는 카드 88장 대기 네이버 · 매수 조건에 걸리는 카드 5장 대기 마지막 코너까지 가면 처음부터 다시
직원은 어느 코너까지 갔는지 기억한다. 다음 바퀴는 거기서 이어진다. 한 코너에서 사고가 나도 그 자리는 이미 지나간 것으로 쳐서, 그 코너가 뒤 코너들을 막지 못한다.

한 코너 안 — 좋은 조건부터 순서대로

코너에 들어서면 진열대를 딱 한 번 확인하고 수첩에 적는다. 그 다음부터는 예약 카드를 좋은 조건 순으로 꺼내 처리한다. 같은 가격이면 먼저 온 사람이 먼저다.

예약 카드 — 좋은 조건 순 진열대 30,300원까지 · 4주 09:01 접수 · 처리 완료 30,200원까지 · 10주 09:04 접수 · 처리 완료 메모: 여기까지 함 30,100원까지 · 7주 09:07 접수 · 지금 처리 중 30,000원까지 · 20주 09:02 접수 · 대기 29,900원까지 · 3주 08:58 접수 · 대기 코너에 들어설 때 한 번만 확인 30,000원 5개 30,100원 12개 30,200원 8개 싼 칸부터 채우고, 카드 가격을 넘으면 멈춘다 조건 맞는 만큼 빼서 준다 10장 다 처리했거나 시간이 다 됐다 메모를 남기고 다음 코너로 간다. 다음 바퀴에 이 자리에서 이어서 한다. 진열대가 새로 바뀌었다 메모를 찢고 코너를 즉시 떠난다. 다음에 오면 맨 앞 카드부터 다시 훑는다. 카드 한 장을 끝낼 때마다 갈림길
두 갈래의 차이는 “메모를 남기느냐 찢느냐”다. 시간이 없어서 멈추는 건 자리를 지키고, 진열대가 바뀌어서 멈추는 건 자리를 버린다.

왜 굳이 메모를 찢나

진열대가 바뀌면 지나쳐 온 카드가 되살아난다. 아까는 못 사서 그냥 넘어갔던 카드가, 새 물건이 들어오면서 제일 좋은 조건이 되어 있을 수 있다. 메모를 그대로 두면 그 카드는 뒤에 갇힌 채 영영 차례가 오지 않는다.

아까 30,300원짜리 물건이 진열대에 없었다 30,300원까지 못 삼 → 지나침 30,100원까지 처리 완료 메모는 여기 30,000원까지 진열대 교체 지금 30,300원짜리가 새로 들어왔다 30,300원까지 이제 살 수 있음 30,100원까지 메모가 여기 있으면 위 카드는 영영 못 옴 30,000원까지 그래서 진열대가 바뀌면 메모를 찢는다 가격·시간 우선순위는 “새 진열대 기준으로” 다시 세워야 한다
진열대 교체는 코너 전체를 떠나게 만든다. 새 카드가 들어오는 경우는 조금 다른데, 그 전에 카드가 어디에 있는지부터.

카드는 코너에 쌓이지 않는다

여기까지 코너마다 카드가 쌓여 있는 것처럼 그렸지만, 실제로는 아니다. 예약 카드는 전부 서랍 하나에 들어 있고, “코너”는 물리적인 장소가 아니라 검색 조건이다. 직원이 삼성전자 매수 코너에 간다는 건, 서랍에 대고 “삼성전자 · 매수 · 아직 안 끝난 · 안 만료된 카드를 좋은 조건 순으로 50장 주세요”라고 묻는 것이다.

코너는 장소가 아니라 검색 조건이다 서랍 모든 예약 카드가 여기 한 곳에 섞여 있다 삼성 · 매수 카카오 · 매수 삼성 · 매도 삼성 · 매수 네이버 · 매수 카카오 · 매수 카카오 · 매수 삼성 · 매수 삼성 · 매도 네이버 · 매수 체결·취소·만료된 카드는 조건에서 빠져 보이지 않는다 조건 검색 조건 검색 삼성전자 · 매수 코너 방금 꺼내 온 한 뭉치 — 최대 50장 카카오 · 매수 코너 방금 꺼내 온 한 뭉치 — 최대 50장
직원은 대기열을 들고 있지 않다. 코너에 갈 때마다 서랍에 다시 묻는다 — stock_id · side · 미체결 · 안 만료로 걸러 가격 → 접수 시각 → ID 순으로 50장.
큐가 없다는 게 중요하다. 직원이 퇴근했다 다시 와도(배포·재시작) 잃을 게 없다. 메모·접수함 같은 직원의 기억은 전부 “빨리 하기 위한 힌트”라서, 날아가면 처음부터 훑을 뿐 정확성은 안 깨진다.

도중에 새 카드가 들어오면

손님이 주문하면 서랍에 카드 한 장이 들어간다. 그게 전부다 — 어딘가 줄을 세우는 동작이 없다. 대신 “이런 카드 들어왔음”이라는 쪽지가 직원에게 간다. 직원이 그 코너를 안 보고 있으면 쪽지는 그냥 버린다. 어차피 다음에 갈 때 서랍에서 새로 꺼내니까.

보고 있는 중이라면 쪽지를 접수함에 넣어두고, 지금 손님을 끝낸 뒤 다음 카드를 꺼낼 때 확인한다. 쪽지에는 가격과 접수 시각이 적혀 있어서 서랍을 다시 뒤지지 않고도 메모보다 앞인지 뒤인지 알 수 있다.

새 카드가 메모보다 앞 더 좋은 조건으로 방금 들어왔다 30,300원까지 완료 30,250원까지 방금 접수 30,200원까지 완료 메모 30,100원까지 메모를 버린다 맨 앞 카드부터 다시 훑는다 새 카드가 메모보다 뒤 순서상 아직 차례가 아니다 30,300원까지 완료 30,200원까지 완료 메모 그대로 30,100원까지 30,050원까지 방금 접수 메모는 그대로 둔다 대신 손에 든 뭉치를 새로 꺼내 온다
오른쪽이 놓치기 쉬운 쪽이다. 순서는 안 바뀌었지만, 직원이 이미 손에 쥔 50장짜리 뭉치는 그 카드가 서랍에 들어오기 전에 꺼낸 것이다. 그 뭉치에는 새 카드가 없다.
그래서 접수 쪽지가 하나라도 있었으면 메모를 되돌리지 않더라도 뭉치는 버린다. 코드에서는 커서에 붙은 token을 새로 발급하는 것으로 처리한다 — 토큰이 달라지면 visit()이 페이지를 다시 조회한다. 쪽지가 여러 장 몰리면 가장 앞선 한 장만 남긴다. 메모를 되돌릴지 판단하는 데는 그거면 충분하고, 실제 카드 목록은 어차피 서랍에서 다시 꺼내기 때문이다.
단, 하던 일은 끊지 않는다. 쪽지가 와도 지금 처리 중인 손님은 끝까지 마친다. 물건을 빼주다 중간에 멈추면 계산이 깨지기 때문이다 — 시간 예산이 다 돼도 마찬가지다.

이름 대응

매장코드
서랍 (모든 예약 카드)trade_order 테이블
조건 검색으로 꺼낸 한 뭉치page(cursor, 50)
“이런 카드 들어왔음” 쪽지LimitOrderAcceptedEvent
손에 든 뭉치 무효화Position.token 재발급
직원의 한 바퀴tick()
한 코너에서 하는 일visit(group, allowance)
코너 (종목 × 방향)Group(stockId, side)
어느 코너까지 갔는지afterGroup
진열대 확인해서 수첩에 적기prepare() → LimitExecutionAttempt
어디까지 했는지 메모LimitExecutionProgress
수첩이 아직 맞는지 확인확정 계층의 재검증
“메모 찢고 떠나라” 신호BOOK_CHANGED · PRIORITY_CHANGED · …
접수함pendingAcceptances
코너당 10장 · 바퀴당 100장 · 5초group-attempts · max-attempts · start-budget