Skip to content
isdnetworks
Go back

자료가 없어져서 코드에서 되살렸다

수수료 정책 표가 들어 있던 테이블이 비어 있었는데 백업도 그 시점 것이 없었다.

Table of contents

Open Table of contents

증상 — 표가 비어 있었다

먼저 정말로 비어 있는지부터 확인했다.

SELECT COUNT(*) FROM fee_policy;
0

언제 없어졌는지 보려고 이력 테이블도 함께 봤다.

SELECT MAX(reg_date) FROM fee_policy_history;

fee_policy_history 도 비어 있어서 같이 지워진 것이었다.

fee_policy 백업은 일 단위였는데 지워진 것이 그 전날이라 백업에도 없었다. 그래서 되살릴 곳이 어디인지부터 목록으로 만들어 봐야 했다.

검증 — 코드에 남은 기본값

그다음으로 정책 표를 읽는 코드를 열어 봤다.

$policy = $this->feePolicy->find($market, $type);
$fee = $amount * $policy->rate;

fee_policy 가 없으면 계산이 안 되는데 find 안에 기본값이 있었다.

public function find(string $market, string $type): FeePolicy {
    $row = $this->db->row("SELECT * FROM fee_policy WHERE ...");
    if (!$row) {
        return new FeePolicy(self::DEFAULT_RATES[$type] ?? 0.001);
    }
}
private const DEFAULT_RATES = [
    'MAKER' => 0.0005,
    'TAKER' => 0.001,
];

DEFAULT_RATES 가 코드에 있어서 표가 비어도 그 값으로 돌고 있었다.

자료가 표에서 없어졌다고 해서 그 값 자체가 사라진 것은 아니었다. 그것을 쓰는 코드나 그것으로 만들어진 결과에 흔적이 남아 있다.

조치 — 계산 결과에서 되살렸다

DEFAULT_RATES 만으로는 부족했는데 시장마다 다른 값이 있었다.

SELECT market, fee_type, amount, fee, fee / amount AS rate
FROM trade_log WHERE reg_date >= '2018-10-01' LIMIT 20;
market  fee_type  rate
A       MAKER     0.00050
A       TAKER     0.00100
B       MAKER     0.00030
B       TAKER     0.00080

fee / amount 로 실제 계산에 쓰인 비율이 나왔다.

trade_log 에 남은 이 비율을 모으면 잃어버린 표가 된다.

SELECT market, fee_type, ROUND(fee / amount, 6) AS rate, COUNT(*)
FROM trade_log WHERE reg_date >= '2018-09-01'
GROUP BY market, fee_type, ROUND(fee / amount, 6);

market 별로 하나씩만 나오면 그 값이 맞고 여럿 나오면 기간에 따라 달라진 것이다.

한 시장에서 값이 둘 나왔고 언제 바뀌었는지도 그 경계로 함께 알았다. 결과에서 거꾸로 올라가는 방법이 DEFAULT_RATES 보다 정확한 값을 줬다.

판단 기준 — 적용된 값과 적용할 값

trade_log 에서 복원한 값을 그대로 넣지는 않기로 했다.

복원한 정책표를 담당 쪽에 보내 확인 요청

두 건이 달랐는데 최근에 바꾸기로 정해 놓고 아직 반영 전이던 것이었다.

계산 결과에서 되살린 것은 실제로 적용된 값이지 적용해야 할 값이 아니다. 물어보지 않았으면 그 차이를 모르고 잘못된 값을 그대로 고정할 뻔했다.

원인 — 운영에서 돈 시험용 스크립트

fee_policy 가 애초에 왜 없어졌는지도 함께 찾아봤다.

$ grep -rn "fee_policy" --include=*.php --include=*.sql . | grep -i "delete\|truncate"
scripts/reset-test-data.php:41:  $db->query("TRUNCATE fee_policy");

reset-test-data.php 가 운영에서 실수로 돌아간 것이었다.

그 스크립트 맨 앞에 환경을 확인하는 줄을 넣었다.

// 스크립트 맨 위에 넣었다
if (ENVIRONMENT === 'production') {
    exit('이 스크립트는 운영에서 실행할 수 없습니다.');
}
$host = $this->db->hostname;
if (in_array($host, PRODUCTION_HOSTS, true)) { exit('운영 DB 입니다.'); }

ENVIRONMENTPRODUCTION_HOSTS 로 두 겹으로 막았다.

환경 변수 하나에만 기대면 그것이 잘못 설정됐을 때 막을 것이 없다. 접속 정보가 운영을 가리킬 수 있으니 hostname 쪽에서도 함께 봤다.

재발 방지 — 되살릴 수 있게 두기

같은 일이 또 났을 때 빨리 되살릴 수 있게 준비했다.

// 정책표를 매일 파일로 내린다
$rows = $this->db->all('fee_policy');
file_put_contents("/backup/policy/fee_policy_" . date('Ymd') . ".json",
                  json_encode($rows, JSON_PRETTY_PRINT));

DB 백업과 별개로 작은 파일로 남겨 두면 되살릴 때 전체 백업을 풀지 않아도 된다.

되살리는 스크립트도 같이 만들어 시험 환경에서 실제로 돌려 봤다. 백업이 있다는 것과 그것으로 복구가 된다는 것은 다른 사실이다.

정리


Share this post on:

Previous Post
실제 발동 빈도를 검증한다
Next Post
처리율 저하가 스톨은 아니다