서비스에는 파일을 최대 2년간 보관한다는 정책이 있었지만, 만료된 스토리지 객체를 실제로 삭제하는 주체는 없었어요. 1억 건이 넘는 파일을 정상 파일의 오삭제 없이 정리하려면 삭제 기능만이 아니라 안전장치와 단계적인 활성화 절차도 필요했어요.
화면에서 사라진 파일은 삭제된 파일이 아니었다
코드는 파일마다 만료일을 계산하고 있었지만, 애플리케이션의 접근 경로만 사라질 뿐 스토리지 객체는 계속 남아 있었어요. 수년 전에 업로드한 파일도 여전히 보관 중이었죠. 원본 글이 삭제된 사진 수십 개를 표본으로 확인해 보니 절반 이상이 스토리지에 그대로 남아 있었어요.
이런 상황에서는 배치 작업 하나만 추가한다고 해결되지 않았어요. 정상 파일을 잘못 삭제하지 않으면서 운영 데이터로 대상 규모를 확인하고, 문제가 없을 때만 삭제 범위를 넓혀야 했어요.
오염된 저장된 만료 시각 대신 판정 근거를 다시 구성했다
먼저 기존 만료일 데이터를 믿을 수 있는지 확인했어요. 채팅 첨부파일 수천 건을 표본으로 조사해 보니 모든 저장된 만료 시각에 생성 시각 + 1시간이 들어 있었어요. 실제 보존 정책과 무관한 값이라 그대로 믿었다면 전날 업로드된 파일까지 만료 대상으로 판단할 수 있었어요.
그래서 파기 태스크는 저장된 만료 시각을 삭제 판정에 쓰지 않았어요. 실행할 때마다 다음 원칙에 따라 만료 여부를 다시 계산했어요.
만료 시점 = 생성 시각 + 파일 용도별 보존 기간
핵심은 잘못된 파생 데이터를 다른 파생 데이터로 다시 보정하는 게 아니에요. 비교적 신뢰할 수 있는 생성 시각과 현재 보존 정책을 바탕으로 판정 근거를 새로 구성하는 거예요. 이후 변경으로 이 전제가 무너지지 않도록, 오염된 저장 만료 시각을 삭제 판정에 쓰지 않는지 확인하는 회귀 테스트도 함께 추가했어요.
삭제를 표시·유예·재검증·삭제로 나눴다
스토리지에서 삭제한 파일은 되돌리기 어려워요. 그래서 만료로 판정한 직후 객체를 지우지 않고 네 단계를 거치도록 설계했어요.
표시 → 유예 → 재검증 → 실삭제
먼저 정책상 만료된 파일을 삭제 후보로 표시해요. 곧바로 삭제하지 않고 유예 기간을 둔 다음, 실제 삭제 직전에 대상이 여전히 삭제 조건을 충족하는지 다시 검증해요. 이 검증을 통과한 파일만 스토리지에서 삭제해요.
이 구조는 잘못된 판정을 완전히 없앨 수 있다고 가정하지 않아요. 판정과 삭제 사이에 되돌릴 수 있는 구간을 두고, 되돌릴 수 없는 작업을 하기 직전에 조건을 다시 확인해요. 파일 용도를 판별할 수 없거나 스토리지 경로를 파싱할 수 없는 경우처럼 판단 근거가 부족한 대상은 삭제하지 않고 보존해요. 배치 처리량을 늘리는 것보다 오삭제를 피하는 쪽을 기본 동작으로 삼았어요.
운영 설정에도 같은 원칙을 적용했어요. 환경변수로 명시해서 활성화하지 않으면 태스크는 동작하지 않아요. 모의 실행과 삭제 후 검증은 기본으로 켜 두어, 설정 누락이 곧바로 실삭제로 이어지지 않게 했어요.
첫 운영 단계에서는 측정 모드만 활성화했다
파기 태스크를 구현했더라도 운영에서 한 번도 실행하지 않았다면 실제 대상의 규모와 분포를 알 수 없어요. 특히 1억 건이 넘는 파일을 처리할 때는 코드의 판정과 운영 데이터의 실제 형태가 일치하는지 먼저 확인해야 해요.
파기 모드를 곧바로 활성화하는 대신 측정 모드만 켰어요. 측정 모드는 실제로 삭제한다면 어떤 파일이 대상이 되는지 집계하지만, DB에 쓰거나 스토리지에 삭제 요청을 보내지는 않아요. 운영 데이터로 판정 로직을 실행하되 시스템 상태는 바꾸지 않는 측정 전용 모드예요.
첫 단계에서는 이 모드를 며칠 이상 실행하면서 다음 항목을 확인하도록 했어요.
- 정책별 삭제 후보의 규모와 비율
- 예상하지 못한 버킷이 대상에 포함되는지
- 채팅 첨부파일 정책의 비율이 예상 범위인지
- 조회 결과가 잘려 나간 상태가 없는지
- 일정 수의 표본이 운영 DB와 스토리지의 실제 상태에 부합하는지
측정 결과를 확인한 뒤에는 버킷 접근 권한과 삭제 권한을 따로 점검해요. 그 점검을 마친 뒤에야 삭제 모드로 전환하고, 최초 파기 상한은 소규모로 제한해요. 이후 관측 결과를 바탕으로 상한을 단계적으로 높여요.
측정 모드 실행
→ 분포와 누락 확인
→ 일정 수의 표본 대조
→ 버킷·삭제 권한 점검
→ 삭제 모드 전환, 소규모 상한 적용
→ 상한 단계적 확대
측정 모드는 단순한 dry run 옵션이 아니에요. 테스트 환경만으로는 알 수 없었던 운영 데이터의 규모와 형태를 실삭제 전에 확인하는 롤아웃 단계예요.
삭제 경로보다 활성화 방식이 중요했다
두 차례의 변경으로 보존기간이 지난 파일을 찾아 스토리지에서 파기하는 정기 작업과, 이를 운영에서 검증할 경로를 마련했어요. 다만 첫 운영 단계에서 실제로 활성화한 것은 DB와 스토리지를 변경하지 않는 측정 모드라서, 이 단계에서는 파일이 하나도 삭제되지 않아요.
대신 삭제 후보가 어떤 정책과 버킷에 분포하는지, 조회 누락은 없는지, 실제 표본과 판정 결과가 일치하는지를 운영 데이터로 검증할 수 있게 됐어요. 신뢰할 수 없는 만료일은 원본 시각과 정책을 바탕으로 다시 계산하고, 삭제는 표시·유예·재검증을 거치도록 했으며, 운영에서는 영향이 없는 측정 단계부터 활성화했어요.
파일 보존 정책은 문서나 만료일 데이터만으로 집행할 수 없어요. 실제 스토리지 객체를 파기하는 실행기뿐 아니라 잘못된 판정을 멈출 안전장치와 단계적인 활성화 절차까지 갖춰야 비로소 운영할 수 있는 정책이 돼요.