Skip to content
isdnetworks
Go back

상태를 바꾸면 대상마다 할 일이 생겼다

거래 정지를 걸면 그 종목의 미체결 주문을 전부 취소해야 했다. 정지 요청은 한 번인데 취소 대상은 수천 건이었다. 처음에는 그 요청 안에서 전부 처리하려고 했다.

Table of contents

Open Table of contents

원인 — 한 요청에서 다 하려던 문제

처음에는 suspend() 안에서 findOpen 을 돌며 하나씩 cancel 하고 있었다.

public function suspend(string $symbol): void {
    $this->market->updateState($symbol, 'SUSPENDED');
    foreach ($this->orders->findOpen($symbol) as $o) {
        $this->orders->cancel($o->no);
    }
}

미체결이 8,000건이라 요청이 시간 초과로 끊겼고 정지는 됐는데 주문은 반만 취소된 채로 남았다.

게다가 취소한 행마다 걸린 InnoDB 배타 잠금이 COMMIT 까지 유지돼서 다른 처리까지 함께 밀렸다. 요청 하나가 감당할 수 있는 크기를 이미 넘어선 것이었다. 상태 변경 자체는 한 번인데 그것이 만드는 일이 그만큼 많은 구조였다.

할 일을 만들어 두는 방식

그래서 updateStatecreateBulk 까지만 한 트랜잭션에 넣고 실제 취소는 워커가 하게 바꿨다.

$this->db->transaction(function () use ($symbol) {
    $this->market->updateState($symbol, 'SUSPENDED');
    $this->tasks->createBulk('CANCEL_OPEN_ORDER', $this->orders->openIds($symbol), [
        'symbol' => $symbol,
        'reason' => 'MARKET_SUSPENDED',
    ]);
});

그러면 요청은 금방 끝나고 취소는 뒤에서 차례로 진행된다.

job 이 행으로 남으니 중간에 끊겨도 어디까지 했는지가 그대로 남는다. 다시 시작하면 남은 것부터 이어서 처리한다. 요청 안에서 다 하는 것과 목록을 만들어 두는 것의 차이가 이 안정성이었다.

진행을 볼 수 있게 했다

전에는 취소가 끝났는지 알 방법이 없어서 화면을 계속 새로 고치고 있었다. task 가 행으로 남으니 stateGROUP BY 하면 진행률이 나온다.

SELECT state, COUNT(*) FROM task
WHERE type = 'CANCEL_OPEN_ORDER' AND ref = 'BTC' GROUP BY state;
DONE     6,204
PENDING  1,796
FAILED       0

화면에도 「거래 정지: 진행 중 (6,204 / 8,000)」으로 냈고 기다리는 사람이 그동안 다른 일을 할 수 있다. 진행을 보여 주는 것이 처리 속도를 바꾸지는 않지만 체감을 크게 바꿨다. 그리고 그 값이 그대로 완료를 판정하는 근거로도 쓰였다.

끝났다는 기준

무엇을 끝난 것으로 볼지도 정해야 했다.

public function isSuspendComplete(string $symbol): bool {
    return $this->tasks->countPending('CANCEL_OPEN_ORDER', $symbol) === 0
        && $this->orders->countOpen($symbol) === 0;
}

countPending 이 0인 것과 countOpen 이 0인 것은 다른 상태라 둘을 다 봤다.

countPending 이 0인데 countOpen 이 남아 있으면 처리 중에 새 주문이 들어온 것이다. 그래서 만들어진 task 수를 함께 남겨서 0건이면 그 사실이 드러나게 했다. 0건인 것이 정상인지 조건이 잘못된 것인지를 그 값으로 갈랐다.

순서와 중복

상태를 먼저 바꾸고 할 일을 만드는 순서가 중요했다. 반대로 하면 그 사이에 들어온 새 주문이 취소 대상에서 그대로 빠진다.

먼저 막고 나서 이미 있는 것을 정리하는 순서여야 새는 것이 없다. 같은 정지를 두 번 걸어도 할 일이 두 배가 되지 않도록 이미 만들어진 것이 있으면 건너뛰게 했다. 실패한 것을 다룰 때도 진짜 실패와 그 사이에 이미 체결되어 취소할 필요가 없어진 것을 나눴다.

정리


Share this post on:

Previous Post
저장소 안에 있다는 것과 추적된다는 것
Next Post
한 축이 아니라 쌍으로 분기한다