휘발성 레코드 보존 정리 (멱등 레코드·만료 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 는 발급 때마다 만료분을 지워 스스로 자정하므로 대상이 아니다.
i3months
added a commit
to i3months/stackup
that referenced
this pull request
Aug 28, 2026
Team-StackUp#224 에서 쓴 렌즈("인덱스는 있는데 그 컬럼을 쓰는 코드가 없다")로 전체 인덱스를 훑은 결과다. ai_request_logs 는 LLM 호출마다 한 행이라 이 코드베이스에서 가장 빨리 자라는 테이블이다 — 피드백 한 번에 10여 건(패널·첫인상·직무적합·인성·답변별 코칭), TTS 는 문장마다. created_at 계열 인덱스가 3개나 있는데 조회하는 코드도, 정리하는 코드도 없었다. 보존 90일로 넉넉히 잡았다. 이 테이블의 가치는 비용 추이라 멱등 레코드와 성격이 다르다 — 지운다고 동작이 깨지지는 않지만 짧게 잡으면 학기 단위 비교가 불가능해진다. 함께: idx_user_consents_user_id 는 idx_user_consents_user_type (user_id, consent_type) 의 leftmost prefix 로 완전히 커버된다. 실제 쿼리 두 개 모두 복합 인덱스로 처리되므로 조회에 보탬 없이 쓰기 비용만 더한다. 훑는 김에 확인한 미구현: activity_logs(US-31)는 테이블·엔티티·리포지토리만 있고 읽기도 쓰기도 없다. 인덱스 3개도 함께 놀고 있다. 삭제 대상이 아니라 계획된 기능이므로 docs/observability.md 에 미구현으로 명시만 했다.
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갱신 — 보존 기간을 줄일 때의 위험도 함께 적었다.