회원 수수료율을 일괄로 보정하면서 관리 화면에 변경 내역이 보이도록 이력도 남겨야 했다. audit_logs 에 몇 건을 어떤 내용으로 넣어야 하는지가 문제였다.
Table of contents
Open Table of contents
검증 — 이력을 몇 건 넣어야 하는가
두 가지 방법이 있었다. FeeService 부터 코드를 따라가 이력이 어디서 몇 번 남는지 추적하는 것과, 화면에서 정상으로 수정된 회원 하나를 골라 audit_logs 를 조회하는 것이다. 뒤쪽을 골랐다. 이유는 수수료 수정 흐름이 서비스 셋을 거쳐서 코드로는 반나절이 걸릴 자리라서다. 대신 그 회원 한 명이 대표한다는 전제를 감수했다.
조회하니 한 번의 수정에 대해 이력이 세 건 남아 있었다. 한 건은 before·after 값이 담긴 것이고 다른 한 건은 같은 문구인데 data 가 비어 있었으며 마지막은 「수수료 정보 수정」이라는 별개 문구였다. 수동으로 재현할 때 개수를 안 맞추면 화면에서 정상 건과 다르게 보인다.
정상 건에서 나온 세 건
이 대조로 넣어야 할 이력의 개수와 각각의 문구와 데이터 형태가 한 번에 나왔다. 코드를 따라가는 것보다 훨씬 짧은 길이었다.
정상 경로가 건드리는 테이블도 audit_logs 와 member_fee_history 와 members.fee_rate 와 알림 큐 작업까지 넷이었다. 하나라도 빠지면 화면이나 후속 처리가 어긋난다.
원인 — 이름이 거짓말한 컬럼
세 번째 이력의 data 를 열어 보니 알림 발송 여부를 뜻하는 notified 필드에 큰 정수가 들어 있었다. 참과 거짓을 담는 자리라면 나올 수 없는 값이었다.
찾아보니 방금 만들어진 member_fee_history 레코드의 id 였다. 이름은 불리언을 암시하는데 내용은 다른 테이블의 식별자였다.
오해하면 조용히 끊긴다
이것을 확인하지 않고 발송했으니 1 을 넣었다면 그 자리에 엉뚱한 수가 들어간다. 삽입은 성공하고 화면 이력 목록에도 정상으로 뜬다.
눌러서 상세를 열 때만 연결이 없다는 것이 드러나고 넣는 시점에는 아무 신호가 없다. 이름이 값의 종류를 암시해도 SELECT notified, COUNT(*) … GROUP BY 1 로 실제 값의 분포를 한 줄로 확인하는 습관이 여기서 나왔다.
조치 — 대조가 가장 싼 방법인 이유
수수료 수정 흐름은 여러 서비스를 거치므로 코드로 추적하면 이력이 어디서 몇 번 남는지 따라가는 데 시간이 든다. 정상 건 하나를 조회하면 그 결과가 바로 나온다.
그래서 수동 작업 절차를 members.fee_rate 갱신과 member_fee_history 삽입과 그 id 를 담은 audit_logs 삽입 순서로 정했다. 앞 단계에서 나온 식별자가 뒤 단계에 들어가므로 순서 자체가 고정된다.
정리
- 한 번의 변경이 이력 레코드 여러 건을 만들 수 있다
- 수동 재현은 개수와 문구와 데이터 형태까지 맞춘다
- 정상 경로로 처리된 건을 대조하는 것이 가장 싸다
- 정상 경로가 건드리는 테이블 전부를 함께 본다
- 컬럼 이름이 내용과 다를 수 있다
notified처럼 불리언으로 생긴 이름에 다른 테이블의 식별자가 들어갈 수 있다- 잘못 넣어도 오류가 안 나고 연결만 조용히 끊긴다
- 이름이 암시해도 값의 분포를 직접 확인한다