썸네일 규격이 바뀌어서 기존 것을 다시 만드는 작업을 했다. 300건쯤 돌다가 원본 파일이 없는 상품에서 멈췄고, 그 시점에 썸네일이 아예 없는 상품이 300개 생겼다.
Table of contents
Open Table of contents
지우기가 먼저였다
짜 놓은 순서가 문제였다. unlink 로 옛 썸네일을 먼저 지우고 GD 로 새로 만드는 흐름이라 만들기가 실패하면 아무것도 안 남는다.
foreach ($products as $p) {
@unlink($thumbDir . $p->thumb_name); // 1. 지운다
$ok = makeThumb($origDir . $p->orig_name, // 2. 만든다
$thumbDir . $p->thumb_name);
}
작업 전에는 규격이 옛것일 뿐 썸네일이 있었다. 작업 후에는 없다. 실패한 결과가 시작 상태보다 나쁘다는 것이 이 순서의 문제였다.
만들고 나서 교체했다
순서를 뒤집었다. 임시 이름으로 먼저 만들고 성공했을 때만 교체한다.
$tmp = $thumbDir . $p->thumb_name . '.new';
if (!makeThumb($origDir . $p->orig_name, $tmp)) {
log_message('error', "썸네일 생성 실패: {$p->product_no}");
continue; // 옛것을 그대로 둔다
}
rename($tmp, $thumbDir . $p->thumb_name); // 성공했을 때만 교체
실패하면 continue 로 넘어가고 옛 썸네일이 그대로 남는다. 규격은 옛것이지만 없는 것보다 낫다. 같은 파일 시스템 안의 rename 은 원자적이라 교체 중간 상태가 안 생긴다는 점도 이 순서를 쓸 수 있게 해 준다.
지우는 단계를 뒤로 미뤘다
이 원칙을 다른 작업에도 적용하려고 단계를 되돌릴 수 있는지로 나눠 봤다.
| 단계 | 되돌리기 |
|---|---|
| 파일 생성 | 지우면 됨 |
| 파일 교체 | 원본이 있으면 가능 |
| 파일 삭제 | 불가 |
| 데이터 삽입 | 지우면 됨 |
| 데이터 갱신 | 이전 값이 있으면 가능 |
| 데이터 삭제 | 불가 |
못 지우는 쪽으로 가는 단계를 가능한 한 뒤로 미는 것이 규칙이 된다. 전부 성공한 뒤에 한꺼번에 지우는 방법도 있다. 처리하면서 지울 목록만 모아 두고 마지막에 한 번에 도는 식이다.
옛 썸네일도 그래서 안 지웠다. unlink 대신 rename 으로 이름 뒤에 표시만 붙여 뒀다.
rename($thumbDir . $name, $thumbDir . $name . '.v1');
새 규격에 문제가 있으면 반대로 하면 되고 확인 기간이 지나면 표시가 붙은 것을 지운다. 디스크가 두 배로 드는데 그만한 값이었다.
사전 준비
멈춘 이유가 원본 없음이었으니 file_exists 로 그것부터 세어 봤다. 142건이었다.
foreach ($products as $p) {
if (!file_exists($origDir . $p->orig_name)) {
$missing[] = $p->product_no;
}
}
작업을 시작하기 전에 전제가 만족되는지 확인하니 실행 중 멈추는 일이 줄었다. 142건은 CSV 로 뽑아 담당자에게 넘기고 나머지만 처리했다.
전제를 확인해도 다른 이유로 멈출 수 있어서 처리한 것을 기록하게 했다. 상품마다 썸네일 버전을 MySQL 에 남기고 다음 실행에서는 버전이 낮은 것만 대상으로 잡으면 처음부터 다시 안 해도 된다.
정리
- 되돌릴 수 없는 단계가 먼저 오면 뒤가 실패했을 때 원래보다 나빠진다
GD로 만들고 나서rename으로 교체하고unlink는 마지막에 한다- 같은 파일 시스템 안의
rename은 원자적이라 중간 상태가 없다 - 단계를 되돌릴 수 있는지로 나누고 못 되돌리는 것을 뒤로 민다
- 시작 전에
file_exists로 전제를 확인하면 실행 중 멈추는 일이 준다 - 처리한 것을 기록하면 멈춰도 이어서 할 수 있다
- 옛것을
unlink하지 않고rename으로 표시만 붙여 두면 원래대로 갈 수 있다