Skip to content
isdnetworks
Go back

로그가 없을 때의 시간 근거

이미지가 안 나온다는 제보를 확인하니 DB에는 미디어 행이 다섯 개 있는데 스토리지에는 파일이 하나도 없었다.

Table of contents

Open Table of contents

두 가설과 로그의 부재

가설이 둘로 갈렸다. 만들 때 업로드가 안 됐는데 DB 행만 남은 생성 실패이거나 만들 때는 됐는데 나중에 누가 지운 생성 후 삭제다. 앞이면 생성 코드를 고쳐야 하고 뒤면 삭제 주체를 찾아야 하므로 대응이 완전히 다르다.

먼저 로그를 뒤졌는데 없었다. 스토리지 접근 로그가 비활성이었고 클라우드 감사 로그의 데이터 이벤트도 꺼져 있어서 누가 언제 지웠는지 알 방법이 없었다. 요금 때문에 데이터 이벤트를 안 켜는 것은 흔한 선택이라 놀랍지는 않았다. 시간 순서를 알려 줄 기록이 없으니 남은 것은 코드였다.

try-catch가 없다는 정보

생성 코드를 열어 보니 외부 저장소 복사 호출이 트랜잭션 안에 있으면서 자체 예외 처리로 감싸여 있지 않았다.

DB::beginTransaction();
try {
    // ... 상품·미디어 행 INSERT ...
    Storage::disk('s3')->copy($src, $dst);   // ← 감싸여 있지 않다
    DB::commit();
} catch (\Exception $e) {
    DB::rollBack();
    throw $e;
}

복사가 실패하면 예외가 위로 올라가 트랜잭션이 롤백되므로 DB 행이 남지 않는다. 그러면 추론이 닫힌다. 행이 커밋돼 남아 있다는 것은 그 시점에 복사가 성공했다는 뜻이고 파일이 없다면 그 이후에 지워진 것이다. 생성 실패 가설이 로그 없이 코드만으로 배제됐다.

여기서 흥미로운 것은 예외를 삼키지 않은 코드가 사후 추론의 근거를 만들어 줬다는 점이다. 복사를 별도로 감싸서 실패를 기록만 하고 넘어갔다면 행이 있다는 사실이 아무것도 보증하지 않았을 것이다. 방어적으로 보이는 처리가 진단 가능성을 없앤다.

삭제 주체의 배제

생성 후 삭제로 좁혔으니 누가 지웠는지를 코드로 하나씩 지웠다. 이미지 변환 워커는 가져오기와 올리기 호출만 있고 삭제 호출 자체가 없어서 배제했고, 마이그레이션 정리 함수는 존재하지만 호출부를 따라가니 롤백 경로에서만 불려서 배제했으며, 모델 삭제 훅은 아예 없어서 배제했다.

셋을 지우고 남은 것이 없었다. 코드베이스 안에 지운 주체가 없다면 밖이라는 뜻인데 사람이 콘솔에서 지웠거나 다른 시스템이 지운 것이고, 그것을 확인할 로그가 없다. 조사가 여기서 끝났다.

확정 불가의 기록

보고에는 생성 실패가 아니라 생성 이후 삭제라는 것을 트랜잭션 경계를 근거로 적고, 삭제 주체는 코드베이스 내 후보 셋을 배제했으며 그 밖의 주체는 감사 로그가 비활성이라 확정할 수 없다고 적었다.

원인 불명이라고 쓰는 것과 다르다. 여기까지는 확정이고 여기부터는 불가이며 불가한 이유는 이것이라는 세 정보를 나눠 적으면 다음 사람이 무엇부터 해야 하는지가 정해진다.

원인을 못 찾았다고 아무것도 안 하는 것은 아니어서 대안 둘을 함께 냈다. 재발했을 때 확정할 수 있도록 사전 로깅을 켜고 지금 못 켜면 못 켠 이유를 남기는 것이 하나이고, 마켓 이미지가 없으면 마스터에서 다시 복사하는 폴백을 넣어 원인을 못 잡아도 증상은 막는 것이 다른 하나다.

확인 순서

DB에는 있는데 파일이 없다는 유형의 조사에서 볼 순서를 정리했다. 생성 코드의 트랜잭션 경계가 어디인지 보고, 외부 저장소 호출이 그 경계 안인지 밖인지 보고, 그 호출이 예외 처리로 감싸여 있는지 본다.

셋을 보면 행이 남아 있다는 사실이 무엇을 보증하는지가 결정된다. 보증하는 것이 있으면 시간 순서가 갈리고 없으면 안 갈린다. 로그를 먼저 뒤지는 것은 자연스럽지만 로그가 없다고 포기할 이유는 아니다. 코드에도 시간 정보가 들어 있다.

정리


Share this post on:

Previous Post
반드시 보존하라던 12기가의 실체
Next Post
건너뛸 줄 알았던 자리가 내리고 있었다