Skip to content
isdnetworks
Go back

복구 가능한 것과 잃은 것

거래 이력 페이지의 차트 이미지가 일부 깨져 있었다. 이미지는 원본을 올려 두고 크기별 변환본을 만들어 쓰는데 깨진 것은 변환본이 없는 것들이었다.

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건을 대상이 없다고 읽었으면 그대로 끝냈을 것이다. 반드시 있어야 하는 건으로 한 번 더 확인하고 나서야 조회 자체가 틀렸다는 것을 알았다.

정리


Share this post on:

Previous Post
검증이 API가 아니라 실행 시점에 있다
Next Post
껐는데도 새 동작이 남아 있었다