Skip to content
isdnetworks
Go back

처리 중에 바뀌는 대상 집합

크롤링한 자료를 정리하는 배치를 만들어 cron 에 걸었다. 돌릴 때마다 처리 건수가 달랐고, 로그에는 대상이 1,204건이라고 찍혔는데 실제로는 1,251건을 처리했다.

Table of contents

Open Table of contents

세는 것과 가져오는 것이 따로였다

COUNT(*) 를 한 번 돌려 로그에 적고, 그다음 같은 조건으로 LIMIT 100 씩 가져와 처리하는 구조였다. 두 쿼리가 각각 도니 그 사이에 대상이 변할 수 있다.

크롤러가 동시에 돌면서 새 자료를 계속 넣고 있었다. 세고 나서 가져오기 시작하는 사이에 조건에 맞는 행이 늘어난 것이다. 로그의 숫자와 실제 처리한 숫자가 다른 이유가 여기 있었다.

표시부터 했다

처리를 시작할 때 대상에 회차 표시를 남기게 바꿨다.

$batch = date('YmdHis');
$this->db->query(
    "UPDATE crawl_data SET batch_id = ? WHERE status = 'raw' AND batch_id IS NULL",
    [$batch]
);
$total = $this->db->affected_rows();

affected_rows() 가 곧 표시된 개수다. UPDATE 한 문장이 세는 일과 표시하는 일을 같이 하므로 그 사이가 없다.

가져올 때는 그 표시가 붙은 것만 본다. 표시한 뒤에 들어온 자료는 batch_id 가 비어 있으니 이번 회차 대상이 아니다.

이전 구조에는 다른 문제도 있었다. 유입이 처리보다 빠르면 조건에 맞는 행이 계속 있어서 반복문이 안 끝났다. 표시로 집합을 고정하니 대상이 정해진 개수가 된다. 그것만 처리하면 끝나고 나머지는 다음 회차가 가져간다.

남은 표시를 회수하는 기준

중간에 죽으면 batch_id 만 찍히고 처리는 안 된 행이 남는다. 다음 실행에서 DATE_SUB 로 시간을 재서 회수하게 했다.

$this->db->query(
    "UPDATE crawl_data SET batch_id = NULL
     WHERE status = 'raw' AND batch_id IS NOT NULL
       AND batch_date < DATE_SUB(NOW(), INTERVAL 30 MINUTE)"
);

30분은 실제 처리 시간을 재서 정했다. 가장 오래 걸린 회차가 8분이라 거기에 여유를 뒀다. 너무 짧게 잡으면 아직 도는 중인 것을 회수해서 두 번 처리하고, 너무 길게 잡으면 복구가 늦어진다.

회차별로 볼 수 있게 됐다

표시가 데이터에 남으니 회차 단위로 결과를 조회할 수 있게 됐다.

batch_id        total  done  pending  failed
20131223-0300    1204  1198        0       6
20131222-0300    1180  1180        0       0

어느 회차에 실패가 몰렸는지 보인다. 이전에는 전체 실패 수만 알 수 있어서 특정 회차의 문제인지 계속 나는 문제인지 갈리지 않았다.

겹쳐 도는 것도 같이 막았다. 두 개가 동시에 돌면 서로 표시를 나눠 가지니 결과는 맞는데 부하가 두 배가 된다. GET_LOCK 에 대기 시간을 0으로 줘서 못 잡으면 바로 끝내게 했다. 이 잠금은 세션이 끊기면 알아서 풀린다. 표시는 남는데 잠금은 안 남으니 회수는 표시 쪽만 하면 됐다.

정리


Share this post on:

Previous Post
값은 옮겼는데 코드 체계가 달랐다
Next Post
PHP에 없는 것들