가격이 안 맞는다는 문의가 왔다.
우리 DB 32,900
저쪽 화면 26,800
할인을 지우고 가격을 올렸는데 반영이 안 된 것으로 보였다. 전송 이력을 SELECT 해 보니 update_success 였다.
SELECT status, completed_at FROM distribution_product_jobs WHERE ...;
Table of contents
Open Table of contents
증상 — 성공한 작업과 안 맞는 가격
성공했는데 값이 다르다는 것이 처음에는 모순으로 보였고 성공이면 저쪽에 우리 값이 들어가 있어야 할 것 같았다.
그런데 그 성공이 무엇을 뜻하는지를 따져 보니 모순이 아니라 두 문장이 함께 참일 수 있는 구조였다.
전송 성공이 뜻하는 것
update_success 는 그때 그 값을 보냈고 저쪽이 받았다는 뜻이지 지금 MySQL 값과 같다는 뜻이 아니다.
보낸 값이 26,800이었으면
↓
26,800으로 성공
↓
지금 DB는 32,900
보낸 뒤에 우리 값이 바뀌었으므로 저쪽에는 26,800 이 남았고 성공이면서 불일치인 상태가 성립한다.
두 컬럼 비교로 끝나는 진단
이 진단을 매번 job 이력을 뒤져서 하고 있었다. 두 가지 방법이 있었는데 이력에서 마지막 성공 건의 보낸 값을 매번 조인해 오는 것과 컬럼을 하나 더해 두는 것이다. 뒤쪽을 골랐다. 이유는 진단이 SELECT 한 번으로 끝나야 문의를 받는 자리에서 바로 답할 수 있다는 점이다. 대신 같은 값이 두 곳에 남아 전송 처리가 그 컬럼을 빠뜨리면 다시 어긋나는 것을 감수했다. 그래서 ALTER TABLE 로 컬럼을 하나 더해 마지막으로 보낸 값을 거기 저장하게 바꿨다. 그러면 현재 값과 그 컬럼을 한 SELECT 에 나란히 놓는 것으로 진단이 끝난다.
두 값이 같은데 저쪽이 다르면 저쪽 문제이고 두 값이 다르면 우리가 아직 안 보낸 것이다. 경계를 넘어가는 값은 무엇을 보냈는지를 남겨 둬야 이 구분이 가능했다.
원인 — 왜 이전 값이 나갔나
왜 이전 값이 나갔는지를 보니 값을 UPDATE 하는 처리와 전송하는 처리의 순서가 어긋난 경우가 있었다. 전송 대상이 먼저 SELECT 되고 그 뒤에 값이 바뀌면 옛 값이 그대로 나간다.
다만 항상 그런 것은 아니었고 두 처리가 잡히는 시점에 따라 갈렸다. MySQL 5.7 매뉴얼(Consistent Nonlocking Reads)은 기본 격리 수준 REPEATABLE READ 에서 트랜잭션의 첫 읽기가 만든 스냅숏을 그 뒤 읽기에도 쓴다고 적으므로, 전송 처리가 트랜잭션 안에서 대상을 먼저 읽었으면 그 사이에 커밋된 UPDATE 도 안 보인다. 그래서 재현하려고 하면 될 때도 있고 안 될 때도 있어서 다른 원인처럼 보였다.
재현 안 되는 순서 문제
어느 쪽인지 확정하려면 updated_at 과 전송 시각을 나란히 놓고 봐야 했다. 어느 것이 먼저 잡혔는지가 그 건의 결과를 정하기 때문이다.
시각을 보고 나서야 재현이 안 되던 것이 순서 문제라는 것이 확정됐다. 상태값이 무엇인지와 값이 같은지는 서로 다른 질문이라 상태값만 보고는 답이 안 나온다.
정리
- 전송 성공은 그때 그 값을 보냈다는 뜻이다
- 성공이면서 불일치인 상태가 성립한다
- 마지막으로 보낸 값을 컬럼에 저장해 두면 대조가 가능해진다
- 진단이 두 컬럼 비교로 끝난다
- 두 값이 같은데 저쪽이 다르면 저쪽 문제다
- 순서 문제는 시점에 따라 갈려 재현이 안 되는 것처럼 보인다
- 확정하려면
updated_at과 전송 시각을 나란히 본다 - 상태값과 값 비교는 다른 질문이다