한 입점사의 status = 'stopped' 상품 십여 건을 대상으로 감사를 돌렸다. 우리 쪽 기록에는 전부 중지로 돼 있는데 마켓에서는 그대로 팔리고 있었다.
Table of contents
Open Table of contents
검증 — 시계열이 드러낸 패턴
각 상품의 하위 채널상품에 대해 jobs 이력을 ORDER BY created_at으로 뽑아 stop 뒤에 update가 다시 나간 패턴을 찾았다. 중지 십여 분 뒤에 갱신이 나간 건과 바로 뒤에 나간 건이 나왔다.
갱신은 상태를 정상으로 되돌리므로 그 시점에 마켓에서 다시 살아난다. 감사 대상 십여 건 가운데 3분의 2가량에서 같은 패턴이 확인됐다.
원인 — 되살리는 세 경로
그 갱신이 어디서 나오는지를 찾아보니 경로가 세 갈래였다. 상위 상품을 고치고 마켓 연동을 누르면 하위 상태를 확인하지 않은 채 갱신이 나간다.
updated 이벤트로도 나가는데 중지 역시 상태 변경이므로 그 Observer 리스너에 그대로 걸린다. WHERE 조건이 비어 있는 market:sync-all 배치도 있어서 중지된 것까지 대상에 들어간다. 셋 다 중지 상태를 확인하지 않는다.
증상 — 이상해 보이는 조합
그 결과 products.status는 중지인데 market_products.status는 정상이고 후속 stop 작업은 없는 조합이 만들어진다. 논리적으로 이상해 보이지만 실제로 생기는데 갱신이 하위를 정상으로 돌려놓았고 중지 작업은 그 전에 이미 완료됐기 때문이다.
한 건은 하위 채널 넷이 전부 정상으로 남아 네 곳에서 동시에 팔리고 있었다. 상위 상태만 보고 감사했으면 그 넷을 전부 놓쳤을 것이다.
조치 — 바뀐 감사 기준
두 가지 방법이 있었는데 products.status가 중지인 것만 세는 것과 상위 상태와 하위 market_products.status와 마지막 job_type을 3중으로 대조하는 것이다. 뒤쪽을 골랐다. 이유는 상위 상태 하나만 보면 실제 노출 여부를 알 수 없어서다. 대신 감사 조회가 세 테이블 조인으로 무거워지는 것을 감수했다.
마지막 작업이 갱신이면 되살아났을 가능성이 높다는 것이 판정 기준이다. 그 축만으로는 감사가 성립하지 않는다.
교훈 — 1회 처리가 아닌 중지
이 건이 보여 주는 것은 판매중지가 1회 처리로 끝나지 않는다는 사실이었다. 중지를 눌렀다고 계속 중지인 것이 아니라 그 뒤에 상위를 수정하면 다시 살아난다.
운영자는 이것을 모르므로 중지했다고 말하고 그 말이 맞다. 중지했고 나중에 다른 작업이 푼 것이다. 근본 대응은 갱신 경로가 중지 상태를 확인하게 하는 것인데 앞선 조사에서 상위 레벨 검증에 last_job_type이 stop이면 거부하는 조건이 있는 것을 봤으므로 같은 보호가 세 경로 전부에 있는지를 확인하는 일이 남았다.
정리
- 중지한 것이 다른 경로로 되살아날 수 있다
- 되살리는 경로가 여럿이고 각각 중지 상태를 확인하지 않는다
- 논리적으로 이상해 보이는 상태 조합이 실제로 생긴다
- 감사는 상위 상태와 하위 상태와 마지막
job_type을 3중 대조한다 - 상위 상태만 보면 실제 노출 여부를 모른다
- 중지는 1회 처리가 아니고 이후 작업이 푼다
- 같은 보호가 모든 경로에 있는지 확인한다
- 운영자의 중지했다는 말은 사실이고 그 뒤가 문제다