Skip to content
isdnetworks
Go back

값 하나를 바꾸면 여러 테이블이 같이 바뀌어야 했다

(수정됨)

회원의 수수료 등급을 바꿨는데도 어떤 화면에서는 여전히 옛 등급이 나오고 있었다. 등급 값을 담고 있는 자리를 찾아보니 MySQL 의 네 표에 흩어져 있었다.

members.fee_tier
member_summary.fee_tier
trades.member_fee_tier      -- 체결 시점의 등급
withdrawals.fee_tier        -- 출금 시점의 등급

Table of contents

Open Table of contents

원인 — 지금 값과 그때 값

전부 갱신하려다 멈추고 각 자리가 무엇을 뜻하는지 봤더니 성격이 둘로 갈렸다.

members.fee_tier          지금 등급. 바뀌면 갱신
member_summary.fee_tier   지금 등급의 사본. 바뀌면 갱신해야 한다
trades.member_fee_tier    체결 시점의 등급. 바뀌면 안 된다
withdrawals.fee_tier      출금 시점의 등급. 바뀌면 안 된다

trades.member_fee_tier 는 그 체결 시점의 등급이지 지금 등급이 아니었다.

그것을 지금 값으로 바꿔 버리면 과거 체결에 적용된 수수료율의 근거가 사라진다. 그때 그랬다는 사실 자체가 기록이므로 바꾸면 안 되는 값이었다. 같은 값처럼 보이는 것이 실제로는 성격이 다른 두 종류였다.

왜 사본이 있었나

각 사본이 왜 있는지도 따로 확인했는데 체결·출금 쪽은 수수료 계산에 쓰인 값을 남긴 것이라 지워서는 안 되는 것이었다.

반면 member_summary 는 체결 수와 거래 대금 합계를 미리 계산해 둔 표인데 등급까지 같이 담아 둔 것이었다. 등급은 계산 결과가 아니라 그냥 복사라서 JOIN 하나로 대신할 수 있었다.

SELECT s.*, m.fee_tier FROM member_summary s JOIN members m ON s.member_no = m.member_no;

사본을 두는 것 대신 JOIN 쪽을 골랐다. 이유는 갱신할 자리가 하나 줄고 어긋날 자리도 같이 없어져서다. 계산해서 담아 둔 것과 그냥 복사한 것을 가르니 정리할 대상이 갈렸다.

조치 — 이름으로 구분하기

남기기로 한 것은 이름에 시점을 넣어 구분되게 했다.

trades.member_fee_tier  →  trades.fee_tier_at_trade
withdrawals.fee_tier    →  withdrawals.fee_tier_at_request

fee_tier_at_trade 라고 적혀 있으면 갱신 대상이 아니라는 것이 이름만 보고 갈린다. 컬럼 주석에도 「체결 시점의 수수료 등급. 회원 등급이 바뀌어도 갱신하지 않는다」를 넣었다 — MySQL 5.7 참고 매뉴얼대로 컬럼 COMMENT 는 1024자까지이고 SHOW FULL COLUMNS 에 표시되므로 표를 여는 사람이 그 자리에서 읽는다.

전에는 둘 다 등급이라는 같은 이름이어서 매번 코드를 봐야 했다. 이름 하나 바꾸는 것이 다음 사람의 판단을 대신해 준다. 실제로 그 뒤로 같은 종류의 혼동이 없어졌다.

조치 — 바꾸는 자리를 하나로

지금 값을 UPDATE 하는 자리가 여러 곳에 흩어져 있는 것도 문제였다. 관리 화면과 월별 거래량 cron 배치와 마켓 메이커 계정 등록 처리가 각각 등급을 UPDATE 하고 있었다.

그래서 등급을 UPDATE 하는 처리를 하나로 모으고 나머지는 그것을 부르게 했다.

public function changeFeeTier(int $memberNo, string $newTier): void {
    $this->db->transaction(function () use ($memberNo, $newTier) {
        $this->db->query("UPDATE members SET fee_tier = ? WHERE member_no = ?", [$newTier, $memberNo]);
        $this->db->query("UPDATE member_summary SET fee_tier = ? WHERE member_no = ?", [$newTier, $memberNo]);
        $this->db->insert('member_fee_tier_history', [...]);
    });
}

transaction 안에서 members 와 member_summary 를 함께 UPDATE 하고 member_fee_tier_history 에 INSERT 한다. trades 와 withdrawals 는 안 건드린다. 새 자리가 생겨도 그 함수를 부르면 빠질 것이 없다.

검증 — 어긋난 것을 세고 남기기

이미 어긋나 있는 사본이 얼마나 되는지 회원 표와 JOIN 해 COUNT(*) 로 세어 봤더니 생각보다 적지 않았다. 그것들을 전부 맞춘 뒤에 같은 대조를 cron 으로 정기적으로 돌리게 했다.

그리고 등급을 바꿀 때 누가 왜 바꿨는지 member_fee_tier_history 에 INSERT 하게 했다. 값만 남으면 나중에 왜 이 등급인지 답할 수 없다. 그때 값을 어디까지 남길지도 정했는데 보관 기간이 지난 것은 요약만 남기고 정리했다.

정리


Share this post on:

Previous Post
종결 처리가 여러 자리에 걸쳐 있었다
Next Post
시작할 때 반드시 거치는 자리를 만들었다