거래 이력 페이지의 차트 이미지가 일부 깨져 있었다. 이미지는 원본을 올려 두고 크기별 변환본을 만들어 쓰는데 깨진 것은 변환본이 없는 것들이었다.
Table of contents
Open Table of contents
상황 — 깨진 변환본
변환본이 없으면 원본에서 다시 만들면 된다.
원본을 읽는다 → 변환한다 → 저장한다
SELECT 으로 대상을 뽑아 배치를 돌리면 끝날 일로 보였다.
깨진 것이 수백 건이라 손으로 할 양이 아니어서 배치 한 번으로 정리하려고 대상 목록부터 뽑았다.
원인 — 원본이 없는 것이 있었다
돌리기 전에 표본 몇 개의 원본을 확인해 봤다.
file_exists 로 보니 일부는 변환본만 없는 것이 아니라 원본 자체가 없었다. 재료가 없으므로 배치를 아무리 돌려도 그 건은 성공할 수 없다.
표본을 안 봤으면 이 사실을 배치가 끝난 뒤에 알았을 것이고 그때는 실패 목록만 남아 왜 실패했는지를 다시 조사해야 한다.
제약 — 대체 소스의 동반 소실
같은 이미지를 다른 경로에서 가져올 수 있는지 file_exists 로 찾아봤다.
상위 항목의 이미지 → 없음
같은 계열의 형제 → 없음
상위 항목 과 형제들이 같은 원본을 공유하고 있어서 함께 사라져 있었다.
하나가 없어지면 그것을 참조하던 것이 전부 없어지는 구조라 공유가 공간을 아끼는 대신 소실의 폭을 키운 셈이다.
원본 없음 + 대체 소스 없음
↓
변환본을 만들 재료가 없다
↓
기술적으로 복구 불가
남은 길은 원본을 다시 올리는 것뿐이고 그것은 우리 쪽에서 할 수 있는 일이 아니었다.
조치 — 두 그룹으로 나눈다
file_exists 결과로 진단을 두 그룹으로 갈랐다.
그룹 A — 원본 있음, 변환본 없음 → 백필로 해결된다
그룹 B — 원본 소실 → 복구 불가, 재업로드 대상
나누는 조건은 file_exists 가 참인가 하나였다.
file_exists 한 번이면 되고 내용을 받을 필요도 없다. 원격이면 헤더만 확인하는 요청으로 갈린다.
안 나누고 전체를 돌렸으면 이렇게 됐을 것이다.
전체를 대상으로 백필 실행
↓
A 는 성공, B 는 실패
↓
"전부 복구했다"고 보고
↓
B 는 여전히 깨져 있다
복구 불가를 안 가른 하나의 원인에서 헛수고와 오보가 함께 난다.
제약 — 끝나지 않는 재시도
이 상태가 만든 다른 문제도 있었다.
외부 시스템이 영영 없을 파일을 계속 받으려다 404 로 실패하고 있었다.
요청 → 404 → 재시도 → 404 → ...
끝나지 않는 재시도가 error_log 를 채우고 있었다.
status 를 복구 불가로 확정하고 대상 WHERE 에서 빼야 이 재시도도 멈춘다. 확정한다는 것은 더 시도하지 않겠다는 결정이므로 근거를 함께 남겨야 했다.
원본과 대체 소스를 각각 file_exists 로 어떻게 확인했는지를 함께 적어 뒀다. 그래야 나중에 그 판단이 맞았는지 다시 볼 수 있다.
결과 — 보고를 두 숫자로
COUNT(*) 두 개를 내는 쪽으로 보고 형태도 바꿨다.
백필로 해결됨 N건
복구 불가 M건 → 재업로드 필요 (목록 첨부)
전부 복구라고 쓰면 사실이 아니고 COUNT(*) 없이 일부라고만 쓰면 얼마인지 모른다.
복구 불가 로 센 건에 대해 우리가 할 수 있는 것이 없다는 것도 명시했다. 할 수 없는 것을 할 수 없다고 적는 편이 그 상황에서 가장 정확한 보고였다.
조사 중에 SELECT 쪽에서도 한 번 걸렸다. 대상을 뽑는 원시 쿼리가 0건을 냈는데 조건에 들어간 클래스 이름에 역슬래시가 있었다.
'App\Models\Product\Product'
셸과 SQL을 거치면서 \ 의 이스케이프가 꼬여 조건이 안 맞은 것이었다.
프레임워크의 질의 메서드를 쓰면 \ 이스케이프를 그쪽이 처리한다. 0건을 대상이 없다고 읽었으면 그대로 끝냈을 것이다. 반드시 있어야 하는 건으로 한 번 더 확인하고 나서야 조회 자체가 틀렸다는 것을 알았다.
정리
- 변환본을 재생성하려면 원본이 있어야 한다
- 대체 소스를 공유하면 함께 사라진다
- 공유는 공간을 아끼는 대신 소실의 폭을 키운다
- 진단을 복구 가능과 복구 불가 두 그룹으로 나눈다
- 안 나누면 헛수고와 오보가 함께 난다
- 나누는 조건은 원본 존재 확인 하나다
- 배치 전에 그 확인을 먼저 돌린다
- 복구 불가 건은 끝나지 않는 재시도를 만든다
- 확정하고 목록에서 빼되 근거를 함께 남긴다
- 보고는 전부 복구가 아니라 두 숫자로 낸다
- 조회가 0건이면 대상이 없는 것인지 조건이 틀린 것인지 먼저 가른다