마켓 점검으로 매칭 엔진을 내리면서 대량 취소가 발생해서 복원 대상을 SELECT 로 골라내야 했다. 어느 상태를 기준으로 분류할지가 문제였고 후보가 주문 표 쪽과 엔진 표 쪽 둘이었다.
Table of contents
Open Table of contents
비교 — 두 상태 컬럼의 분포
한 마켓의 수백 건에 대해 두 상태를 각각 GROUP BY 해 COUNT(*) 를 내 봤다.
orders.user_state engine_orders.state
open 절반 open 한 줌
cancel_requested 절반 canceled 거의 전부
어느 쪽을 기준으로 삼을지는 그 분포를 본 다음에 정할 일이었다.
주문 표는 절반씩인데 엔진 표는 압도적으로 취소였다. 같은 주문 집합인데 어느 쪽을 쓰느냐에 따라 대상이 열 배 넘게 달라진다는 뜻이고, 두 값이 다른 것을 뜻하고 있다는 신호였다.
이 집계에서 market 을 SELECT 목록에 같이 두었다가 한 번 튕겼다. MySQL 5.7 참고 매뉴얼의 GROUP BY 처리 절대로 ONLY_FULL_GROUP_BY 가 기본으로 켜져 있어 GROUP BY 에 없는 비집계 컬럼을 거부한 것이라 GROUP BY market, user_state 로 고쳤다.
원인 — 옛 경로와 의도의 보존
왜 갈리는지를 보려고 grep 으로 점검 취소 코드를 찾았다.
private function engineCancelAll(string $market)
{
$this->engine->cancelAll($market);
$this->db->query("UPDATE engine_orders SET state = 'canceled' WHERE market = ?", [$market]);
// orders 는 안 건드림
}
옛 경로는 engine_orders.state 만 canceled 로 바꾸고 주문 표는 전혀 안 건드렸다.
state 만 움직이므로 주문 표에는 사용자가 원래 어떤 상태를 원했는지가 그대로 남아 있게 된다. 점검 취소가 지나간 뒤에도 그 값이 안 바뀌었으므로 의도가 보존된 셈이었다.
엔진 상태는 작업의 결과다
engine_orders.state 는 그 반대로 우리가 돌린 작업의 결과를 그대로 담고 있었다. 사용자가 무엇을 원했는지가 아니라 무슨 일이 있었는지를 기록하는 자리였다.
그러니 두 값이 다른 것은 어느 한쪽이 틀렸기 때문이 아니라 애초에 서로 다른 것을 담기 때문이었다. 하나를 다른 하나로 맞추려 드는 것 자체가 잘못된 접근이었다.
판단 기준 — 의도와 호가창의 분리
용도를 갈라 보면 주문 표는 의도를 묻는 질문에 답하고 엔진 표는 지금 호가창에 걸려 있는지에 답한다. 무엇을 복원할지는 의도의 문제이므로 대상을 고르는 WHERE 는 user_state 를 봐야 했다.
엔진 상태로 고르는 것 대신 user_state 쪽을 골랐다. 이유는 복원이 답하려는 물음이 「지금 걸려 있나」가 아니라 「사용자가 유지하려 했나」라서다. 반대로 지금 호가창에 무엇이 걸려 있는지를 알아야 하는 작업에서는 엔진 값이 맞다. 같은 자료를 두고도 물어보는 것이 다르면 봐야 할 컬럼이 달라진다는 것이 이 구분의 요지였다.
혼동하면 정반대로 나간다
기준을 잘못 잡으면 작업이 조금 어긋나는 것이 아니라 정반대 방향으로 나간다. 사용자가 취소를 요청해 둔 주문을 호가창에 되살리고 유지하려던 주문은 그대로 두게 되기 때문이다.
건수가 많아서 그 방향이 뒤집히면 되돌리는 비용도 그만큼 커진다. 되살아난 주문이 그 사이에 체결되면 되돌릴 수도 없다. 분포가 정반대라는 것은 잘못 골랐을 때의 결과도 정반대라는 뜻이었다.
제약 — 경로에 따른 성립 조건
다만 이 구분은 취소가 옛 경로로 처리됐을 때만 성립한다. 주문 표까지 함께 UPDATE 하는 새 경로로 처리된 건은 의도가 이미 덮여 있으므로 user_state 를 봐도 알 수 없다.
그래서 규칙으로 적을 때 어느 경로로 처리된 건인지를 WHERE canceled_by = 'maintenance_v1' 조건으로 함께 적었다. 조건을 빼고 결론만 남기면 다음에 다른 경로에서 그대로 적용해 틀리게 된다.
정리
- 상태 컬럼이 둘이면 각각
GROUP BY해COUNT(*)를 낸다 - 분포가 정반대면 둘이 다른 것을 뜻한다
- 옛 경로가 주문 표를 안 건드리면 주문 표에 의도가 남는다
- 엔진 상태는 작업의 결과지 의도가 아니다
- 의도는 주문 표에서 호가창 노출은 엔진 표에서 본다
- 혼동하면 작업이 정반대 방향으로 나간다
- 이 구분은 어느 경로로 처리됐느냐에 따라 성립한다
- 규칙에 결론만 남기고 조건을 빼지 않는다