Skip to content
isdnetworks
Go back

다른 테이블에 부과된 페널티

페널티 면제 요청이 들어와서 부과된 레코드를 찾아 지우면 되는 일이었다. 이름이 맞아 보이는 테이블이 있어서 그 건의 식별자로 조회했는데 0건이 나왔다.

Table of contents

Open Table of contents

증상 — 이름이 맞는 테이블의 0건

처음 조회한 것은 delivery_penalty_calculation_histories 였다.

SELECT * FROM delivery_penalty_calculation_histories
 WHERE order_item_option_id = ?;

0건이었다. 배송 페널티 정산 이력이라는 이름이 이 건과 딱 맞아 보였고 그래서 이 표를 골랐는데 그 선택이 유일한 전제였다. 화면에는 부과됐다고 나오는데 SELECT 가 0건이면 자료가 없는 것이 아니라 내가 다른 곳을 보고 있는 것이다.

그 전제를 버리고 화면이 어디서 그 값을 가져오는지를 grep 으로 찾기로 했다. delivery_penalty_calculation_histories 는 이름만 맞았지 부과가 쌓이는 자리가 아니었다.

부과 지점 — 환불 처리 쪽

부과 지점을 코드에서 찾았다.

grep -rn "penalty" app/Services/Order/

환불 처리 쪽에서 나왔고 거기서 만들어 넣는 표는 calculation_revisions 였다. 페널티는 그 표의 한 종류로 type = 'penalty' 행으로 들어가고 있었다. 이름에 penalty 가 들어간 표가 따로 있는데 실제 부과는 정산 보정이라는 더 일반적인 이름의 표에 쌓이고 있었던 것이다.

코드에 박힌 조건과 금액

부과 여부를 정하는 조건도 그 자리에 함께 있었다.

사유가 품절 계열 두 종(OutofStock · AutoSoldOut)
AND 특정 시점 이후 주문
AND 금액 = 판매가의 5%, 최소 1,000원 최대 3,000원

OutofStockAutoSoldOut 이라는 취소 사유 코드값 두 개, 하한 1,000원과 상한 3,000원, 판매가 5% 라는 비율이 전부 코드에 상수로 박혀 있었다. 설정 테이블에도 없고 문서에도 없다.

그러면 이 건의 금액이 왜 이 값인지 알 수 있는 자리가 코드밖에 없다. calculation_revisions 를 아무리 들여다봐도 calculate_price 에 찍힌 값만 보이지 그 값이 어떤 규칙에서 나왔는지는 안 나온다.

면제는 소프트 삭제였다

면제 처리를 어떻게 하는지도 확인하고 손대야 했다. 요청받은 건을 다시 조회했다.

SELECT id, calculate_price, calculation_id, deleted_at
  FROM calculation_revisions
 WHERE type = 'penalty' AND order_item_option_id = ?;
calculate_price   -1000
calculation_id    NULL
deleted_at        2019-05-24 15:35

deleted_at 이 이미 채워져 있었다. 면제는 행을 DELETE 하는 것이 아니라 deleted_at 에 시각을 넣는 소프트 삭제였고 이 건은 이미 그렇게 처리된 뒤였다. 지워 버리면 나중에 왜 면제됐는지 확인할 수 없기 때문에 그 방식을 쓰고 있었다.

두 컬럼이 만드는 네 조합

이 표에서 상태를 나타내는 컬럼이 deleted_at 하나가 아니었다.

calculation_id  NULL     아직 정산에 안 들어감
                값 있음   정산 반영됨

deleted_at      NULL     부과 유효
                값 있음   면제됨

calculation_id 는 정산 반영 여부이고 deleted_at 은 면제 여부다. 둘이 독립이라 네 가지 조합이 나오고 조합마다 후속 조치가 달랐다.

정산 전 면제   calculation_id NULL,  deleted_at 채움
정산 후 면제   calculation_id 있음,  deleted_at 채움 (환급 필요)

정산에 이미 나간 뒤에 면제하면 deleted_at 만 채우는 것으로 끝나지 않고 정산 쪽에서 되돌리는 처리가 따로 필요했다. 요청받은 건은 calculation_idNULL 이라 후속 조치가 없는 쪽이었다.

조회는 조인 경로로

처음에 메모 컬럼을 LIKE 로 찾으려고 했던 것도 잘못된 접근이었다.

-- 피한다
WHERE memo LIKE '%품절%'

앞에 와일드카드가 붙은 LIKE 는 인덱스를 못 타서 calculation_revisions 전체를 훑고 메모는 사람이 쓰는 자리라 형식이 일정하지 않아 놓치는 것도 생긴다.

SELECT cr.* FROM calculation_revisions cr
  JOIN order_item_options oio ON oio.id = cr.order_item_option_id
 WHERE oio.id = ? AND cr.type = 'penalty';

order_item_options 에서 order_item_option_id 로 이어지는 조인 경로를 타면 인덱스로 들어간다. 이 경로와 type = 'penalty' 조건을 절차로 적어 두니 다음 면제 요청은 조회부터 바로 시작할 수 있었다.

0건이 신호였다

이 조사의 출발이 0건이었다. 0건을 부과가 없다는 뜻으로 읽었으면 화면이 잘못됐다고 회신하고 끝났을 것이다. 화면에는 있는데 SELECT 가 0건이라는 것은 두 곳이 서로 다른 표를 보고 있다는 뜻이고 그러면 내가 고른 표가 틀린 것이다.

부과 지점을 확정한 근거는 결국 코드였다. 스키마는 delivery_penalty_calculation_historiescalculation_revisions 라는 이름만 보여 주고 어느 쪽에 쓰는지는 안 알려 준다. 데이터는 0건이라 단서가 없다. 조건과 금액이 코드에만 있으면 코드가 유일한 근거다.

정리


Share this post on:

Previous Post
받아만 오면 도는 개발 환경
Next Post
화면 이름과 코드 이름의 통일