Skip to content
isdnetworks
Go back

예약 작업이 두 번 돌았다

정산 자료가 두 배로 들어간 날이 있었다. 로그를 보니 같은 crontab 항목이 두 대에서 돌았다.

Table of contents

Open Table of contents

서버 두 대에 등록돼 있었다

배치 서버를 이중화하면서 두 대에 같은 예약을 넣었다.

srv-01  0 3 * * * /opt/batch/settle.sh
srv-02  0 3 * * * /opt/batch/settle.sh

한 대가 죽어도 돌게 하려던 것인데 둘 다 살아 있으면 둘 다 돈다. 이중화하려고 한 것이 중복 실행이 됐다.

공유하는 자원으로 조정했다

처음에는 flock 을 걸었다. 그런데 그건 같은 서버 안에서 겹치는 것만 막는다. 다른 서버는 자기 디스크의 잠금 파일을 보므로 서로 모른다.

여러 대에 걸친 조정은 두 대가 함께 보는 자원으로 해야 했다. 두 대가 같은 MySQL 인스턴스를 보고 있어서 GET_LOCK 을 썼다. 이 함수는 그 한 인스턴스 안에서만 잠금을 세우므로 서버가 여럿이어도 DB 가 하나일 때만 쓸 수 있다.

$r = $this->db->query("SELECT GET_LOCK('settle_batch', 0) AS ok")->row();
if ($r->ok != 1) {
    log_message('info', '다른 서버에서 실행 중. 종료');
    return;
}

try {
    $this->run();
} finally {
    $this->db->query("SELECT RELEASE_LOCK('settle_batch')");
}

두 번째 인자 0 은 기다리지 않고 바로 판정하라는 뜻이다. 먼저 잡은 쪽이 돌고 나머지는 조용히 끝난다. 매뉴얼대로 세션이 끝나면 정상이든 아니든 잠금이 풀리므로 프로세스가 죽어도 남지 않는다.

한 대에만 crontab 을 두는 방법도 있었는데 그러면 그 대가 죽었을 때 안 돈다. 잠금 방식은 두 대가 다 등록돼 있고 하나가 죽어도 나머지가 돈다.

어느 서버가 돌았는지도 gethostname 으로 기록하게 했다. 두 대가 다 시도했고 한 대가 건너뛰었다는 것이 보인다.

settle  srv-01  2015-02-24 03:00  03:12  done
settle  srv-02  2015-02-24 03:00  03:00  skipped

두 번 돌아도 결과가 같게 만들었다

잠금이 있어도 완벽하지는 않았다. 연결이 끊겨 잠금이 풀린 뒤에 다른 쪽이 시작할 수 있다.

배치 자체를 여러 번 돌려도 결과가 같게 바꿨다.

INSERT INTO settle_daily (settle_date, provider_no, amount)
SELECT ...
ON DUPLICATE KEY UPDATE amount = VALUES(amount);

ALTER TABLE settle_daily ADD UNIQUE KEY uk_date_provider (settle_date, provider_no);

uk_date_provider 조합이 이미 있으면 ON DUPLICATE KEY UPDATE 가 덮으므로 두 번 돌아도 두 배가 안 된다. 유일 키가 그것을 보장한다. GET_LOCK 은 겹치지 않게 하는 방지고 여러 번 돌려도 같은 결과가 나오게 하는 것은 방어다.

이미 들어간 중복

GROUP BYHAVING COUNT(*) > 1 을 붙여 확인하니 그날 것만 나왔고 다른 날은 없었다.

SELECT settle_date, provider_no, COUNT(*)
FROM settle_daily GROUP BY settle_date, provider_no HAVING COUNT(*) > 1;

지우기 전에 CREATE TABLE ... LIKE 로 사본을 뜨고 조합마다 하나만 남겼다. settle_no 가 큰 쪽이 나중에 들어간 것이라 그쪽을 지웠다.

DELETE s1 FROM settle_daily s1
JOIN settle_daily s2
  ON s1.settle_date = s2.settle_date
 AND s1.provider_no = s2.provider_no
 AND s1.settle_no > s2.settle_no;

정리한 뒤에 ALTER TABLE 로 유일 키를 걸었다. 순서가 반대면 중복 때문에 ER_DUP_ENTRY 로 안 걸린다.

정리


Share this post on:

Previous Post
파일 이름이 사용자에게서 왔다
Next Post
설정 하나가 없어서 전체가 멈췄다