명품몰에서 원본 상품을 복제한 항목의 이미지가 안 보이는데 원본 쪽은 아무 문제가 없었다. product_images 를 보니 복제본에도 레코드가 있고 filename 도 원본과 같았다.
Table of contents
Open Table of contents
원인 — 파일명이 같아도 갈리는 경로
저장 경로 규칙을 열어 보니 images/{image_id}/{filename} 처럼 이미지 식별자가 경로의 앞자리에 들어가고 있었다. 원본과 복제본은 filename 이 같아도 image_id 가 다르므로 경로가 완전히 갈린다.
실제로 복제본 쪽 경로를 오브젝트 스토리지에서 조회하니 그 아래에 아무것도 없었다. product_images 레코드는 만들어졌는데 실제 파일 복사만 안 된 상태였다.
판단 기준 — 레코드가 있는 것과 파일이 있는 것
MariaDB 관점에서는 완벽히 정상으로 보인다. 레코드가 있으니 조회에 나오고 filename 이 있으니 화면이 주소까지 만들어 낸다.
그 주소가 가리키는 자리에 파일이 없다는 것만 DB 로는 알 수 없으므로 저장소를 직접 봐야 갈린다. 조회 결과가 정상인 것과 대상이 존재하는 것은 다른 질문이었다.
검증 — 대조는 개별 확인으로 한다
두 곳을 비교하는 스크립트를 만들면서 두 가지 방법이 있었다. 버킷을 접두사 단위로 listObjects() 해서 DB 와 대조하는 것과, 각 키를 doesObjectExist() 로 하나씩 확인하는 것이다. 뒤쪽을 골랐다. 이유는 목록은 항목이 많으면 페이지가 잘리고 표시 과정에서 키가 변형될 수 있어서다. 대신 이미지 수만큼 요청이 나가는 것을 감수했다.
그래서 각 키를 하나씩 조회해서 없는 것만 출력하도록 만들었다. 확인 방법이 결과의 신뢰도를 정하는 자리였다.
복구할 때 봐야 하는 네 가지
원본 경로에 파일이 있으면 copyObject() 로 복구되는데 원본만이 아니라 thumb_300.jpg 변환본까지 함께 옮겨야 했다. 컬렉션도 대표 이미지와 추가 이미지 두 갈래로 나뉘어 있었다.
추가 이미지는 sort_order 가 있어서 원본의 몇 번째가 복제본의 몇 번째와 대응하는지도 확인해야 했다. 이름만 같고 순서가 다르면 이미지가 엉뚱한 자리에 붙게 된다.
조치 — 무엇을 완료로 보느냐
복제 함수 ProductService.duplicate() 가 레코드는 직접 만들고 파일 복사는 @Async 작업으로 넘기고 있었다. 그 작업이 실패해도 복제 자체는 성공으로 기록됐다.
그래서 복사 실패를 copy_status 에 기록하게 하고 복제 완료 판정에 파일 존재를 넣었다. 무엇을 완료로 보느냐가 이 문제의 뿌리였고 레코드만 있으면 완료가 아니라는 정의로 바꾼 것이 근본 대응이었다.
정리
- 저장 경로가
image_id로 만들어지면 파일명이 같아도 경로가 갈린다 - 레코드가 있는 것과 파일이 있는 것은 다른 질문이다
- 조회가 정상이어도 대상이 없을 수 있다
- 대조는 목록이 아니라
doesObjectExist()개별 확인으로 한다 - 확인 방법이 결과의 신뢰도를 정한다
- 복구할 때 원본과 변환본과 컬렉션과
sort_order를 다 본다 - 근본 원인은 대개 무엇을 완료로 보느냐에 있다
- 레코드만 있으면 완료가 아니라는 정의로 바꾼다