회원 등급을 바꿨는데도 어떤 화면에서는 여전히 옛 등급이 나오고 있었다. 등급 값을 담고 있는 자리를 찾아보니 MySQL 의 네 표에 흩어져 있었다.
member.grade
member_summary.grade
order.member_grade -- 주문 시점의 등급
point_log.grade -- 적립 시점의 등급
Table of contents
Open Table of contents
원인 — 지금 값과 그때 값
전부 갱신하려다 멈추고 각 자리가 무엇을 뜻하는지 봤더니 성격이 둘로 갈렸다.
member.grade 지금 등급. 바뀌면 갱신
member_summary.grade 지금 등급의 사본. 바뀌면 갱신해야 한다
order.member_grade 주문 시점의 등급. 바뀌면 안 된다
point_log.grade 적립 시점의 등급. 바뀌면 안 된다
order.member_grade 는 그 주문 시점의 등급이지 지금 등급이 아니었다.
그것을 지금 값으로 바꿔 버리면 과거 주문의 할인율 근거가 사라진다. 그때 그랬다는 사실 자체가 기록이므로 바꾸면 안 되는 값이었다. 같은 값처럼 보이는 것이 실제로는 성격이 다른 두 종류였다.
왜 사본이 있었나
각 사본이 왜 있는지도 따로 확인했는데 정산 쪽은 계산에 쓰인 값을 남긴 것이라 지워서는 안 되는 것이었다.
반면 member_summary 는 주문 수와 금액 합계를 미리 계산해 둔 표인데 등급까지 같이 담아 둔 것이었다. 등급은 계산 결과가 아니라 그냥 복사라서 JOIN 하나로 대신할 수 있었다.
SELECT s.*, m.grade FROM member_summary s JOIN member m ON s.member_no = m.no;
사본을 빼니 갱신할 자리가 하나 줄었다. 계산해서 담아 둔 것과 그냥 복사한 것을 가르니 정리할 대상이 갈렸다.
이름으로 구분하기
남기기로 한 것은 이름에 시점을 넣어 구분되게 했다.
order.member_grade → order.grade_at_order
point_log.grade → point_log.grade_at_earn
grade_at_order 라고 적혀 있으면 갱신 대상이 아니라는 것이 이름만 보고 갈린다. 컬럼 주석에도 「주문 시점의 회원 등급. 회원 등급이 바뀌어도 갱신하지 않는다」를 넣었다.
전에는 둘 다 등급이라는 같은 이름이어서 매번 코드를 봐야 했다. 이름 하나 바꾸는 것이 다음 사람의 판단을 대신해 준다. 실제로 그 뒤로 같은 종류의 혼동이 없어졌다.
바꾸는 자리를 하나로
지금 값을 UPDATE 하는 자리가 여러 곳에 흩어져 있는 것도 문제였다. 관리 화면과 cron 배치와 자동 승급 처리가 각각 등급을 UPDATE 하고 있었다.
그래서 등급을 UPDATE 하는 처리를 하나로 모으고 나머지는 그것을 부르게 했다.
public function changeGrade(int $memberNo, string $newGrade): void {
$this->db->transaction(function () use ($memberNo, $newGrade) {
$this->db->query("UPDATE member SET grade = ? WHERE no = ?", [$newGrade, $memberNo]);
$this->db->query("UPDATE member_summary SET grade = ? WHERE member_no = ?", [$newGrade, $memberNo]);
$this->db->insert('member_grade_history', [...]);
});
}
transaction 안에서 member 와 member_summary 를 함께 UPDATE 하고 member_grade_history 에 INSERT 한다. order 와 point_log 는 안 건드린다. 새 자리가 생겨도 그 함수를 부르면 빠질 것이 없다.
어긋난 것을 세고 남기기
이미 어긋나 있는 사본이 얼마나 되는지 회원 표와 JOIN 해 COUNT(*) 로 세어 봤더니 생각보다 적지 않았다. 그것들을 전부 맞춘 뒤에 같은 대조를 cron 으로 정기적으로 돌리게 했다.
그리고 등급을 바꿀 때 누가 왜 바꿨는지 member_grade_history 에 INSERT 하게 했다. 값만 남으면 나중에 왜 이 등급인지 답할 수 없다. 그때 값을 어디까지 남길지도 정했는데 보관 기간이 지난 것은 요약만 남기고 정리했다.
정리
- 같은 값이 여러 곳에 있으면 지금 값인지 그때 값인지 가른다
- 그때 값은 바꾸면 안 되는 기록이다
- 사본이 왜 있는지 각각 확인한다
- 계산 결과를 남긴 것과 편의로 복사한 것은 다르다
- 이름에 시점을 넣으면 갱신 대상인지 이름으로 갈린다
UPDATE하는 자리를 하나로 모은다- 어긋난 사본을
COUNT(*)로 세고cron으로 대조한다 - 누가 왜 바꿨는지 이력 표에
INSERT한다