## 관련 명세 - `CLAUDE.md` 배치 (셰드락 필수): 매일 00:10 휴지통 30일 지난 템플릿 영구 삭제 - `docs/erd.md` 5장 배치 작업, 인덱스 `idx_templates_deleted_at` (deleted_at) - 보관 기간: `TrashRetention` (휴지통 목록의 `purgeAt`과 같은 기간을 쓴다) ## 작업 내용 ### 1. 스케줄러·셰드락 공통 설정 (global) - 스케줄 활성화와 셰드락 설정을 `global/config`에 둔다 - 셰드락 의존성 추가: 서버가 여러 대가 되어도 같은 배치가 한 번만 돌게 하기 위해서다 (CLAUDE.md 배치 규칙) - 셰드락 잠금 테이블은 플라이웨이 새 마이그레이션 파일로 추가한다 - 04:00 재발급 토큰 정리 배치도 이 설정을 그대로 쓸 수 있게 한다 ### 2. 휴지통 정리 배치 (template) - 매일 00:10(KST), 휴지통으로 옮긴 지 30일 지난 템플릿을 영구 삭제한다 - 현재 시각은 `Clock` 빈에서 얻는다 - 엔티티를 읽지 않고 삭제 쿼리로 지운다 (#70과 같은 이유: 본문 복호화에 실패하는 행도 지울 수 있어야 한다) ## 설계 시 반드시 볼 것: 복구와의 교착 (#78에서 재현) #78에서 휴지통 비우기와 복구가 동시에 실행되면 교착이 나서 한쪽이 500이 되는 것을 동시성 테스트로 재현했다. - 비우기는 `(user_id, deleted_at)` 인덱스 → 기본 키 순서로 행을 잠근다 - 복구는 기본 키를 잠근 뒤, 저장할 때 같은 인덱스를 고친다 - #78은 영구 삭제·비우기도 `lockUser`를 먼저 불러 같은 회원의 휴지통 쓰기를 줄 세워 해결했다 정리 배치는 여러 회원의 행을 `deleted_at` 범위로 지우므로, 회원을 잠그지 않으면 같은 교착이 날 수 있다. 아래 중 하나 이상으로 정하고 근거를 PR에 남긴다. - 대상 회원별로 `lockUser` → 삭제 (잠금 순서를 복구와 맞춤) - 일정 개수씩 나눠 삭제 (한 번에 잡는 잠금 범위·시간 축소) - 교착이 나면 재시도 ## 함께 고칠 주석 #78은 "정리 배치는 회원을 잠그지 않는다"는 전제로 주석을 남겼다. 배치 설계가 정해지면 아래를 실제 동작에 맞게 고친다. - `TemplateRepository.findTrashedByIdAndUserIdForUpdate` 주석: "회원을 잠그지 않는 정리 배치가 먼저 지우고 있으면…" - `TrashCommandService.restore` 주석: "정리 배치는 회원을 잠그지 않아…" ## 완료 조건 - [ ] 스케줄러·셰드락 설정, 잠금 테이블 마이그레이션 - [ ] 같은 배치가 동시에 두 번 실행되지 않음 - [ ] 30일 경계: 딱 30일 지난 행은 삭제, 덜 지난 행은 유지 - [ ] 활성 템플릿은 삭제하지 않음 - [ ] 복호화가 실패하는 행도 삭제 - [ ] 동시성: 배치와 복구가 겹쳐도 500 없이 끝나고, 결과가 둘 중 하나의 성공과 일치 - [ ] 위 두 주석 수정 - [ ] `./gradlew build` 통과 ## 참고 - 교착 발견·재현: #78 배치 PR 리뷰는 휴지통 담당에게 요청한다 - 04:00 재발급 토큰 정리 배치는 이 이슈 범위가 아니다 - 변경이 400줄을 넘으면 공통 설정과 배치를 PR 두 개로 나눈다
Activity