Skip to content
isdnetworks
Go back

껐는데도 새 동작이 남아 있었다

새 수수료 계산을 플래그로 감싸서 넣었다. 문제가 생기면 끄면 된다고 생각했는데 껐더니 값이 원래대로 안 돌아왔다.

Table of contents

Open Table of contents

증상 — 껐는데 값이 그대로였다

플래그가 걸린 자리를 찾았다.

if (Feature::on('fee_v2')) {
    $fee = $this->feeV2->calc($order);
} else {
    $fee = $this->feeV1->calc($order);
}
$order->fee = $fee;
$this->orders->save($order);

Feature::on 으로 계산은 갈리는데 save 는 갈리지 않는다.

플래그를 켠 동안 새 방식으로 계산한 값이 이미 orders.fee 에 들어가 있다. 끄면 앞으로 계산하는 것만 옛 방식이 되고 이미 저장된 것은 그대로 남는다.

원인 — 쓰는 쪽만 감쌌다

읽는 자리를 찾아봤다.

$fee = $order->fee;                 // 저장된 값을 읽는다

$order->fee 를 읽는 자리에는 Feature::on 이 없어서 켠 동안 쌓인 값을 계속 읽는다.

쓰는 쪽만 막고 읽는 쪽을 안 막으면 끈 상태가 켜기 전 상태와 같아지지 않는다. feeV1 로 계산이 돌아갔는데 화면에는 여전히 feeV2 가 남긴 값이 나오고 있었다.

fee_v2 플래그가 감싸는 것이 계산인지 결과인지를 구분하지 않은 것이 원인이었다. 계산만 감싸면 그 결과가 남는 자리는 감싸지지 않는다.

비교 — 되돌아가는 값과 아닌 값

플래그로 감쌀 때 값의 성격이 둘로 갈린다.

[되돌아가는 것]  요청마다 계산해서 응답에만 쓰는 값
[안 되돌아가는 것]  저장되는 값, 외부에 보낸 것, 다른 처리의 입력이 된 것

앞은 끄면 다음 요청부터 원래대로이고 뒤는 이미 남은 것이 그대로 있다.

새 수수료는 뒤쪽이었다. orders.fee 에 남고 정산의 입력이 되는 값이라 플래그로 감싼다고 끌 수 있는 것이 아니었다.

감싸기 전에 이 구분을 먼저 해야 한다. 뒤쪽이면 Feature::on 만으로는 부족하고 자리를 따로 두는 설계가 함께 필요했다.

조치 — 저장 자리 분리

저장 자리를 갈랐다.

ALTER TABLE orders ADD COLUMN fee_v2 decimal(18,8) DEFAULT NULL;
$order->fee = $this->feeV1->calc($order);
if (Feature::on('fee_v2')) {
    $order->fee_v2 = $this->feeV2->calc($order);
}

fee 는 항상 옛 방식으로 채우고 새 값은 fee_v2 에 담는다.

읽는 쪽에서 고르게 했다.

$fee = Feature::on('fee_v2') ? ($order->fee_v2 ?? $order->fee) : $order->fee;

끄면 fee 를 읽으므로 켠 동안 쌓인 주문도 옛 값으로 돌아온다.

컬럼 하나만 쓰면 어느 시점에 어느 방식으로 계산됐는지도 알 수 없다. 자리를 나눈 덕에 끄는 것도 되고 나중에 대조하는 것도 가능해졌다.

검증 — 켜기 전에 두 값을 비교

자리가 둘이 되니 예상 못 한 이득이 있었다.

읽는 쪽은 아직 fee 를 쓰게 두고 feeV2fee_v2 만 채우게 며칠 돌렸다.

SELECT COUNT(*) AS n,
       SUM(ABS(fee - fee_v2) > 0.00000001) AS diff
FROM orders WHERE reg_date >= ? AND fee_v2 IS NOT NULL;
n=12840  diff=37

n=12840diff=37 이 나왔고 켜기 전에 그 37건을 열어 봤다.

31건은 새 방식이 맞았고 6건은 새 방식의 반올림이 틀렸다. 바로 켰으면 그 6건을 운영에서 사고로 만났을 것이다.

켜고 나서 발견하는 것과 켜기 전에 아는 것은 값이 전혀 다르다. 두 값을 함께 두는 구조가 그 확인을 가능하게 했다.

제약 — 플래그를 지울 조건

끈 상태가 실제로 옛 상태인지도 밟아 봤다.

1. 끈 상태로 주문 → 값 A
2. 켠 상태로 주문 → 값 B
3. 다시 끄고 1번 주문을 조회 → 값 A 여야 한다
4. 다시 끄고 2번 주문을 조회 → 값 A 여야 한다

4번이 핵심인데 켠 동안 만들어진 주문도 fee 로 보여야 끈 것이다.

끌 수 있다는 것과 껐을 때 원래대로 돌아온다는 것은 확인하기 전까지 다른 문제였다.

feefee_v2 를 계속 두면 그것대로 짐이라 정리할 시점도 같이 정했다.

새 값이 4주간 차이 0으로 유지되면 읽는 쪽을 새 값으로 고정하고,
그다음 릴리스에서 옛 컬럼과 플래그를 지운다.

조건과 시점을 안 적으면 플래그가 영구히 남는다. 남은 플래그는 다음 사람이 지워도 되는지 판단할 근거를 잃는다.

정리


Share this post on:

Previous Post
복구 가능한 것과 잃은 것
Next Post
물려받은 실행 방식에 딸린 장치를 안 가져왔다