휘발성 레코드 보존 정리 (멱등 레코드·만료 refresh token) - #224
Merged
Merged
Conversation
이 프로젝트는 Redis 를 쓰지 않고 "휘발성 데이터는 PostgreSQL 의 short-lived 레코드"로 처리하기로 했다(루트 CLAUDE.md). 그런데 아래 둘은 지우는 쪽이 없어 사실상 영구 보관이었다 — short-lived 라는 전제가 코드로 지켜지지 않았다. - processed_messages: AI 콜백마다 한 행(질문·꼬리질문·피드백·분석·음성·TTS). idx_processed_messages_processed_at 인덱스가 처음부터 있는데 그 컬럼으로 조회하는 코드가 하나도 없었다 — 보존 정리를 전제로 만든 인덱스인데 정작 정리가 없었던 셈이다. - refresh_tokens: 로그인마다 한 행. 만료된 것은 검증에서 항상 걸러지므로 남겨둬도 무효인데 쌓이기만 한다. 보존 기간은 재전달 창보다 길어야 한다 — 너무 일찍 지우면 DLQ 에서 늦게 재주입된 메시지가 "처음 보는 메시지"가 되어 다시 처리된다(질문 중복·피드백 재생성). 기본 30일. 스위퍼를 도메인별로 나눴다. 하나로 묶으면 common 이 auth.domain 을 참조하게 되는데 common 은 최하위 공용 레이어라 방향이 거꾸로다(ArchUnit 은 통과했지만 설계상 아님). oauth_states 는 발급 때마다 만료분을 지워 스스로 자정하므로 대상이 아니다.
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.
문제
루트 CLAUDE.md 는 "Redis 미사용 — 휘발성 데이터는 PostgreSQL 의 short-lived 레코드" 로 처리한다고 명시한다. 그런데 두 테이블은 지우는 쪽이 없어 사실상 영구 보관이었다. short-lived 라는 전제가 코드로는 지켜지지 않았다.
processed_messages
AI 콜백마다 한 행씩 쌓인다 — 질문 풀·꼬리질문·피드백·분석 4종·음성·TTS. 면접 한 번에 수십 행이다.
의도는 분명히 있었다. 테이블 생성(V4) 때
idx_processed_messages_processed_at인덱스를 함께 만들었는데, 그 컬럼으로 조회하는 코드가 하나도 없다. 리포지토리에는existsById(PK 조회) 뿐이다. 보존 정리를 전제로 만든 인덱스인데 정작 정리하는 쪽이 안 만들어진 것이다.refresh_tokens
로그인마다 한 행. 만료된 토큰은 검증에서 항상 걸러지므로 남겨둬도 무효인데 쌓이기만 한다.
수정
도메인별 스위퍼 2개 (기본 24시간 주기).
보존 기간이 이 변경의 핵심 판단이다. 멱등 레코드를 너무 일찍 지우면 DLQ 에서 늦게 재주입된 메시지가 "처음 보는 메시지" 가 되어 다시 처리된다 — 질문이 두 번 붙거나 피드백이 재생성될 수 있다. 즉 이 정리는 "공간 회수"보다 "멱등성을 언제까지 보장하느냐" 의 문제다. 기본 30일은 현실적인 재전달·수동 재주입 창보다 충분히 길다.
revoked이지만 아직 만료 전인 refresh token 은 남긴다 — 탈퇴·로그아웃 시 revoke 한 기록이 만료 시각까지는 "이 토큰은 무효"라는 판단 근거로 필요하다.왜 스위퍼를 둘로 나눴나
하나로 묶으면
common.messaging.application이auth.domain.RefreshTokenRepository를 참조하게 된다.common은 최하위 공용 레이어라 방향이 거꾸로다. ArchUnit 은 통과했지만(순환으로 잡히지 않았다) 통과한다고 옳은 건 아니라서 도메인별로 나눴다.대상이 아닌 것
oauth_states는 발급할 때마다 만료분을 지워 스스로 자정한다(OAuthStateService.issueStateWithPkce가 insert 전에deleteByExpiresAtBefore). 이미 해결돼 있어 건드리지 않았다.테스트
실제 Postgres 로 "기한이 지난 것만" 지우는지 양쪽 확인:
두 테스트 모두 "지운다"보다 "안 지운다" 쪽이 본체다.
검증 한계
로컬 Docker 데몬이 여전히 응답하지 않아 DB 테스트 2건은 CI 로 검증한다. 컴파일·ArchUnit 은 로컬 통과.
운영
MESSAGING_PROCESSED_MESSAGE_RETENTION_DAYS(30) ·MESSAGING_VOLATILE_SWEEP_INTERVAL_MS(24h) ·AUTH_REFRESH_TOKEN_SWEEP_INTERVAL_MS(24h).docs/environment.md갱신 — 보존 기간을 줄일 때의 위험도 함께 적었다.