## 왜 ### 뺀 후보를 다시 담으면 500 - prod 24시간 ERROR 19건이 전부 이 오류 - 디스코드 알림 3건은 그 19건이 시간대별로 묶여 나간 것 - 사용자 조작은 정상 - 담기·빼기 각각 200 - **원인:** 빼기가 행을 지우지 않고 `deleted_at` 만 채움 - 담기의 중복 검사는 살아 있는 행만 봄(`findAllByTournamentIdAndNotDeleted`) - 유니크 키 `uk_tournament_items (tournament_id, snapshot_id)` 는 삭제 표시된 행까지 셈 - 위시가 가리키는 snapshot 이 그대로라 같은 조합으로 insert - **결과:** `DataIntegrityViolationException` 이 전용 처리기 없이 `handleUnexpected` 까지 내려가 500 과 ERROR 로그 - **재현:** 담기 → 빼기 → 재담기 통합 테스트에서 `Duplicate entry '1-1' for key 'tournament_items.uk_tournament_items'` 확인 ### 플레이 기록 참조는 없음 - 담기·빼기 둘 다 `isPending()` 전용 - **결과:** 삭제행을 가리키는 `tournament_histories` 가 생길 수 없음 ## 무엇을 ### 유니크 키를 활성 행 한정으로 전환 - generated 컬럼으로 삭제행을 유일성 판정에서 제외 - **참조:** `uk_tournaments_active_invite_code` 선례 - **원인:** MySQL 이 partial unique index 를 지원하지 않음 - **결과:** 재담기가 새 행 insert 로 정상 성공 - 조회 인덱스는 그대로 - **원인:** 새 키의 선행 컬럼이 `tournament_id` 로 같음 | 선택지 | 재담기 후 대기실 순서 | 담은 사람 갱신 | 마이그레이션 | |---|---|---|---| | 활성 행 한정 유니크 (채택) | 맨 뒤 | 불필요 | 필요 | | 삭제행 되살리기 | 원래 자리로 복귀 | 필요 | 없음 | - **기각:** 되살린 행은 옛 id·생성시각을 유지해 `ORDER BY id ASC` 에서 재담은 후보가 원래 자리로 돌아감 ### 회귀 가드 - 담기 → 빼기 → 재담기 통합 테스트 ### 범위 - 예외 처리기 추가 없음 - **원인:** 고치면 재담기가 정상 성공 - 진짜 중복(이미 활성인 후보 담기)은 지금도 409 `duplicateTournamentItem` 으로 걸림
Activity
fix: tournament_items 유니크 키를 활성 행 한정으로 전환