과거 삭제분·파기 실패분의 스토리지 객체 회수 - #223
Merged
Merged
Conversation
#219 는 "앞으로의 삭제"만 다뤘다. 두 부류가 남는다. 1. 그 이전에 지운 자료 — 행만 soft delete 됐고 S3 원본 PDF·분석 마크다운이 그대로다. 앞으로 삭제 이벤트가 다시 날 일이 없어 백필하지 않으면 영원히 남는다. V31(탈퇴자 GitHub 토큰)과 같은 종류의 누락이다. 2. #219 의 파기 실패분 — AFTER_COMMIT 파기는 실패해도 사용자 요청을 실패시키지 않는다(이미 커밋됐고 되돌릴 수도 없다). 스토리지가 잠깐 죽으면 로그 한 줄만 남기고 객체가 샌다. OrphanedObjectSweeper 가 둘을 같은 경로로 회수한다. 임베딩 청크는 DB 안에서 끝나므로 V32 가 한 번에 정리한다. 파괴적 작업이라 대상 선정이 핵심이다. 파생 쿼리 이름에 DeletedTrue 를 박아 조건이 시그니처에 드러나게 하고, 실제 DB 로 "살아있는 행은 절대 안 잡힌다"를 고정했다. 회수 성공 시에만 경로를 비우고(실패는 다음 주기 재시도), 한 건이 실패해도 나머지는 계속 회수한다. V32 는 삭제된 행을 resumes 의 타입별 locator CHECK 에서 제외한다 — 회수 완료 표시로 file_path 를 비워야 하는데 제약이 막는다. users 의 provider 식별자 CHECK(V28)·유니크 인덱스(V3·V22)가 이미 쓰는 규약이다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
문제 — #219 의 빠진 절반
#222 에서 "제약을 완화할 때 기존 데이터 백필이 필요한지 확인할 것"이라는 교훈을 얻었는데, #219 에도 같은 구멍이 있었다.
#219 는 "앞으로의 삭제"만 다룬다. 두 부류가 남는다.
1. 그 이전에 지운 자료 — 행만 soft delete 됐고 S3 의 원본 PDF·분석 마크다운,
document_embeddings청크가 그대로다. 앞으로 삭제 이벤트가 다시 발생할 일이 없으니 백필하지 않으면 영원히 남는다. V31(탈퇴자 GitHub 토큰)과 정확히 같은 종류의 누락이다.2. #219 의 파기 실패분 — AFTER_COMMIT 파기는 실패해도 사용자 요청을 실패시키지 않는다(그 시점엔 이미 커밋됐고 되돌릴 수도 없다 — #219 에서 의도한 설계다). 그래서 스토리지가 잠깐 죽어 있으면 객체가 로그 한 줄만 남기고 샌다. 그때 남긴 ERROR 로그는 "수동 회수 필요"라고 말하는데, 수동 회수 수단이 없었다.
수정
OrphanedObjectSweeper가 둘을 같은 경로로 회수한다 (기본 15분 주기).임베딩 청크는 DB 안에서 끝나므로 V32 가 한 번에 정리한다.
파괴적 작업이라는 점
이 스위퍼는 S3 객체를 실제로 지운다. 대상 선정이 하나라도 어긋나면 살아있는 사용자의 이력서 원본이 사라진다. 그래서:
DeletedTrue를 박아 조건이 메서드 시그니처에 드러나게 했다 (findTop100ByDeletedTrueAndFilePathIsNotNull)V32 가 CHECK 을 완화하는 이유
회수 완료 표시로
file_path를 비워야 하는데chk_resumes_locator_by_type이 막는다(PDF 는 file_path NOT NULL). #198 때ck_users_provider_identity가 막았던 것과 같은 상황이다.제약의 의도는 "살아있는 행은 갖춰야 한다" 이지 "지운 행도 영원히 들고 있어라" 가 아니므로, 삭제된 행을 예외로 둔다.
users의 provider 식별자 CHECK(V28)·유니크 인덱스(V3·V22)가 이미 쓰는 규약이다.경로를 비우는 것 자체도 옳다 — 객체가 없는데 키만 남으면 "아직 회수 안 됨"과 구분되지 않아 매 주기 같은 키를 다시 지우려 든다.
테스트
단위 4건 (로컬 통과): 회수 후 경로 비움 / 실패 시 경로 유지(재시도) / 한 건 실패해도 나머지 진행 / 대상 없으면 무동작
DB 3건: 이력서 조회가 살아있는 행·이미 회수된 행을 안 잡는지, 문서 조회 동일, 그리고 삭제된 행의 file_path 비우기가 CHECK 에 막히지 않는지(V32 검증)
검증 한계
로컬 Docker 데몬이 여전히 응답하지 않아 DB 테스트 3건은 CI 로 검증한다. 컴파일과 단위 테스트는 로컬에서 통과했다.
운영
STORAGE_ORPHAN_SWEEP_INTERVAL_MS(기본 900000) /STORAGE_ORPHAN_SWEEP_INITIAL_DELAY_MS(기본 60000).docs/environment.md·docs/security.md §5.1.1갱신.배포 직후 첫 주기에 과거 삭제분이 한꺼번에 회수되므로, 그때
orphaned storage object purged로그 건수로 잔존 규모를 확인할 수 있다.