상품 등록이 느리다는 얘기를 들었다. INSERT 자체는 금방인데 응답이 늦었다.
Table of contents
Open Table of contents
이미지 처리가 요청 안에 있었다
단계마다 microtime 을 찍어 보니 입력 검사와 DB 저장은 빠르고 이미지 처리가 느렸다. 코드를 보니 한 장마다 크기 셋을 만들고 있었다.
foreach ($images as $img) {
move_uploaded_file(...);
resize($img, 800);
resize($img, 300);
resize($img, 100);
}
상품 하나에 열 장까지 올릴 수 있으니 서른 번을 만든다. 그걸 요청 안에서 다 하므로 사용자는 서른 개가 만들어질 때까지 기다린다. 장수가 많으면 max_execution_time 에 걸려 등록 자체가 실패하기도 했다.
set_time_limit 으로 늘릴까 했는데 그러면 기다리는 시간은 그대로다. 실패를 성공으로 바꿀 뿐 느린 것은 안 고쳐진다.
덧붙이면 이 한도는 재는 대상이 좁다. PHP 는 시스템 호출로 밖에서 기다리는 시간을 여기에 안 넣는다. 이미지 변환은 CPU 를 쓰는 일이라 그대로 세어져서 걸린 것이다.
뒤로 미뤘다
요청 안에서는 move_uploaded_file 로 원본만 저장하고 응답을 준 뒤 변환은 뒤에서 하기로 했다. 할 일을 image_jobs 에 넣고 crontab 에 건 배치가 주기적으로 꺼내 처리한다.
INSERT INTO image_jobs (image_id, status) VALUES (?, 'wait');
저장하고 바로 응답하니 등록이 즉시 끝난다. 만드는 시간은 그대로지만 사용자가 그걸 기다리지 않는다.
아직 안 만들어진 것과 실패한 것
대신 새 문제가 생겼다. 등록 직후에는 변환된 이미지가 없어서 목록에 이미지가 안 나온다. 변환본이 없으면 원본을 보여 주게 했다.
$url = $thumb_exists ? $thumb : $original;
file_exists 로 thumb 이 있는지 보고 없으면 원본을 준다. 크기가 크지만 보이기는 한다. 변환이 몇 초면 끝나서 그 사이만 원본이고 대부분은 티가 안 났다.
변환이 실패하면 status 가 wait 로 남아 계속 다시 시도하는 것도 있었다. try_count 를 세어 몇 번을 넘으면 fail 로 표시하고 그만두게 했다. 그러면 실패한 것을 누가 봐야 하므로 COUNT(*) 를 매일 세는 조회를 뒀다.
실패한 것들을 보니 이미지가 아닌 파일과 너무 커서 메모리가 모자란 이미지였다. 앞의 것은 getimagesize 로, 뒤의 것은 픽셀 수를 재서 올릴 때 거를 수 있었다. 뒤에서 실패하면 사용자는 올린 줄 알고 나갔다가 나중에 없는 것을 보게 된다.
원본을 남기기로 했다
변환이 끝나면 원본을 unlink 할지 고민했다. 지우면 du 로 재는 공간이 절약되고 남기면 나중에 다른 크기가 필요할 때 다시 만들 수 있다.
크기 규격이 바뀔 수 있다고 보고 남기기로 했다. 원본이 없으면 다시 만들 방법이 없다.
나중에 실제로 목록 이미지 크기가 바뀌었고 원본이 있어서 image_jobs 에 전부 다시 넣어 만들었다. 지웠으면 올린 사람들에게 다시 올려 달라고 해야 했을 것이다.
정리
- 무거운 처리가 요청 안에 있으면 사용자가 기다린다
max_execution_time에 걸려 등록 자체가 실패하기도 한다set_time_limit으로 늘리면 실패는 줄지만 기다리는 시간은 그대로다- 그 한도는 시스템 호출 대기 시간을 안 세지만 이미지 변환은 CPU 라 세어진다
image_jobs에 넣고 응답을 먼저 준다- 아직 안 된 것은
file_exists로 갈라 원본을 보여 준다 try_count를 두고 넘으면fail로 표시해 무한 재시도를 막는다- 실패 원인 중 일부는
getimagesize로 앞에서 거를 수 있다 - 원본을 남겨 두면 규격이 바뀌었을 때
image_jobs에 다시 넣으면 된다