이미지 교체 배치를 돌리면서 안전을 위해 매 회차마다 mysqldump 스냅숏을 뜨고 있었다. 한 회차의 스냅숏에서 한 상품만 media 의 상세 이미지가 0행으로 나왔다.
Table of contents
Open Table of contents
원인 — 다형 참조가 만든 0행
다른 상품들은 상세 이미지가 있는데 이 상품만 0이라 백업이 잘못 떴거나 데이터가 깨진 것으로 보였다. media 구조를 보니 mediable_type 과 mediable_id 로 어느 모델에 붙었는지를 담는 다형 참조였다.
상품 이미지는 Product 에 붙고 상세 이미지는 ProductContent 에 붙는다. 그 상품의 상세 본문을 보니 이미지를 외부 주소로 참조하고 있어서 우리 미디어로 가져오지 않은 상태였다.
배경 — 편집 도구가 만든 패턴
이 상품은 특정 편집 도구로 상세를 만든 것이었다. 그 도구는 자기 서버에 이미지를 올리고 본문에는 그 주소를 넣는다.
우리 시스템으로 가져오는 단계가 없으므로 미디어 레코드가 안 생긴다. 데이터 이상이 아니라 그 경로로 만들어진 상품의 정상 상태였다.
교체 배치가 이런 상품을 어떻게 처리하는지도 확인했다. 새 본문을 만들어 요청하면 ContentImporter 가 외부 주소 이미지를 가져와 S3 에 올리면서 media 를 새로 생성하므로 이 경우도 안전하게 처리된다. 같은 패턴의 다른 상품들이 이미 처리되고 렌더 검증까지 통과한 선례도 있었다.
검증 — 재계산 가능한 값
같은 배치에서 다른 것도 걸렸다. 교체 전에 변환본 재생성을 강제하려고 content_hash 를 NULL 로 비우는데 한 상품은 이미 대부분 비어 있었다.
해시가 원본 데이터가 아니라 파일에서 재계산 가능한 검증값이라는 점이 판단을 갈랐다. 원본은 없어지면 복구가 안 되지만 검증값은 다시 계산되므로 비어 있어도 무해하다.
조치 — 목적으로 본 영향 범위
비우는 목적을 다시 확인하니 데이터 삭제가 아니라 변환본 재생성 강제였다. 해시가 있으면 같은 파일로 보고 재사용하고 없으면 새로 처리한다.
이미 비어 있으면 그 목적이 이미 달성된 상태이므로 영향 범위에서 빼야 했다. WHERE content_hash IS NOT NULL 을 붙여 세니 수백 행을 비운다고 보면 영향이 수백 행이지만 실제로 새로 비우는 것은 수십 행이었다.
주의 — 멈춰서 확인하고 기록하기
이 회차에서 걸린 둘이 성격이 같았는데 예상과 다른 값을 만나면 이상으로 보게 된다는 것이다. 그런데 왜 그런지 확인하면 정상인 경우가 많았다.
두 가지 방법이 있었는데 스냅숏만 믿고 그냥 진행하는 것과 회차를 멈추고 확인하는 것이다. 뒤쪽을 골랐다. 이유는 그냥 진행하면 나중에 문제가 되고 이상으로 판단하면 없는 문제를 쫓게 돼서다. 대신 회차마다 확인이 붙어 배치 전체가 며칠 늦어지는 것을 감수했다. 확인해서 정상이면 진행하고 그 판단을 BATCH_LOG.md 에 남겼는데 100회가 넘는 반복 배치라 한 번의 판단 기록이 이후 회차에서 계속 쓰였다.
정리
- 다형 테이블에서 어느 모델에 붙었는지에 따라 집계가 달라진다
- 외부 주소로만 참조되면 미디어 레코드가 안 생긴다
- 그런 경우 0행이 정상이다
- 요청이 새로 생성하므로 그 경우도 안전하게 처리된다
- 해시는 재계산 가능한 검증값이라 비어 있어도 무해하다
- 비우는 목적이 삭제가 아니라 재생성 강제다
- 이미 비어 있는 행은 영향 범위에서 제외한다
- 예상과 다른 값을 만나면 멈춰서 확인하고 판단을 기록한다