정합성 감사 중에 우리가 판매중지한 상품이 외부 채널에서는 판매 중인 건을 셌더니 864건이 나왔다.
Table of contents
Open Table of contents
시스템 정상과 일부 누락
먼저 중지 작업 자체가 도는지 확인했다. 그 채널의 중지 작업이 8만 건 넘게 쌓여 있었고 전날 오후에도 실행된 기록이 있었으므로 배치는 멀쩡했다.
시스템은 정상인데 특정 864건만 작업이 아예 생성되지 않은 상태였다. 이 모순이 조사의 출발점이 됐다. 배치가 죽었으면 전체가 밀렸을 텐데 그게 아니라면 그 864건에만 있는 공통점이 있다는 뜻이다.
상태 이름과 사실
864건의 연동 상태를 보니 전부 등록 대기를 뜻하는 값이었다. 이름대로라면 아직 채널에 안 올라간 것들이므로 판매 중일 리가 없다.
그래서 이름 대신 사실로 확인했다. 등록 작업 완료 기록이 있고 채널 상품번호도 있으며 채널에서 실제로 판매 중이었다. 전부 등록이 끝난 것들인데 상태만 초기값에 멈춰 있었다.
상태를 다음 단계로 옮기는 것이 판매중지 이벤트인데 그 이벤트가 안 왔기 때문에 초기 상태로 남은 것이다. 상태 이름을 믿고 아직 등록 안 된 것들로 분류했으면 조사가 거기서 끝났다. 이름은 설계 당시의 의도이지 지금 그 행의 상태가 아니다.
3개월 전 수정 커밋
이벤트가 왜 안 왔는지 찾으러 상품 수정 서비스의 이력을 뒤졌더니 3개월 전 커밋이 나왔다.
fix: modifyProduct가 상태 변경 이벤트를 발행하지 않는 문제
수정 함수가 값을 채우고 저장만 하면서 상태 변경 전용 메서드를 우회하고 있었고 그래서 상태 변경 이벤트가 안 나갔으며 그 이벤트를 받아 중지 작업을 만드는 리스너도 안 돌았다. 버그는 이미 고쳐져 있었고 864건의 최종 수정 시각은 전부 그 수정 이전이었다.
없던 backfill
수정 커밋을 다시 보니 코드 변경만 있고 마이그레이션이 없었다.
그러면 수정 이후에 판매중지된 상품은 이벤트가 발행되고 중지 작업이 생겨 정상으로 흐르지만 수정 이전에 판매중지된 상품은 이벤트가 없었으므로 작업도 없이 그대로 방치된다. 버그는 멈췄는데 이미 생긴 피해는 그대로 남았고 그것은 아무도 다시 보지 않는다. 고쳤다는 커밋이 있기 때문이다.
확인 항목
이 패턴을 만난 뒤로 과거 수정 커밋을 찾으면 소급 조치가 함께 있었는지를 본다. 코드 변경만 있으면 그 시점 이전 데이터는 어떻게 됐는지를 반드시 묻는다.
상태 이름으로 추측하지 않고 완료 기록이나 외부 식별자 같은 사실로 확인한다. 시스템은 정상인데 일부만 누락이라는 모순은 그 일부의 공통점을 찾으라는 단서이고 대개 시간대를 가리킨다.
소급 조치를 할 때는 원래 게이트를 재현한다. 중지 작업을 나중에 넣을 때 정상 경로가 통과시키는 검증 조건을 그대로 재현하지 않으면 원래는 안 들어갔어야 할 것까지 넣게 된다.
수정에 붙어야 할 한 줄
수정 PR에 한 줄이 있었으면 끝날 일이었다. 이 수정 이전에 영향받은 데이터가 몇 건이고 소급 스크립트를 별첨하거나 소급이 불필요하다는 것과 그 사유를 적는 것이다.
불필요하다고 적는 것도 답이다. 적혀 있으면 다음 사람이 다시 조사하지 않고 안 적혀 있으면 몇 달 뒤에 누가 864건을 발견하고 처음부터 판다.
버그 수정은 미래를 막는 것과 과거를 되돌리는 것 두 부분인데 앞의 것만 하고 끝내는 것이 기본값이 되기 쉽다. 그러면 데이터에 흉터가 남는다.
정리
- 버그 수정 커밋을 찾으면 소급 조치가 있었는지 함께 본다
- 코드만 고치고 데이터를 안 고치면 수정 이전 피해가 그대로 남는다
- 고쳤다는 커밋이 있으면 아무도 다시 보지 않는다
- 상태 이름은 설계 의도이지 지금 그 행의 상태가 아니다
- 완료 기록과 외부 식별자 같은 사실로 확인한다
- 시스템은 정상인데 일부만 누락이면 그 일부의 공통점을 찾는다
- 소급 조치는 원래 게이트를 재현한다
- 수정 PR에 영향 데이터와 소급 여부를 한 줄로 남긴다