개요
프로젝트를 진행하는 동안 “이 값을 어디에 저장할까”라는 질문을 왕왕 맞닥뜨리게 된다. 회원탈퇴 기능을 만들면서도 세 번 마주쳤다.
- 재가입 차단 마커(provider+providerId, 30일간 재가입 금지)
- 크로스모듈 정리 완료추적(어떤 모듈이 데이터 정리를 끝냈는지)
- 애플 소셜 로그인 revoke를 위한 refresh_token 저장 위치
세 값 다 “Redis에 두면 편할 것 같은데”로 시작했는데, 결론은 둘만 Redis에 남고 하나는 RDB로 갔다. 판단 기준은 접근 속도나 구현 편의성이 아니라 **“이 값이 유실되면 정확히 무슨 일이 생기는가”**였다.
1. 재가입 차단 마커 — Redis
valueOps.set(marker.withdrawKey, marker.payload, Duration.ofDays(30))
이 값이 Redis에서 유실될 경우
재가입이 며칠 일찍 허용된다. 어뷰징 방지 강도가 살짝 약해지는 정도지, 데이터가 잘못되거나 법적 문제가 생기는 게 아니다. 그리고 TTL을 쓰면 “30일 뒤 삭제 배치”가 아예 필요 없어진다.
2. 각 모듈별 정리 완료 추적 — Redis
withdrawal:{userId}:completed_modules → Set, 멤버는 help/review/communication
UserWithdrawn 이벤트를 여러 모듈이 각자 구독해서 자기 데이터를 정리하는데, @TransactionalEventListener(AFTER_COMMIT)에서 발생한 예외는 Spring이 로그만 남기고 호출자에게 전파하지 않는다.
- Redis를 사용해서 완료 여부를 추적하였다.
- 이 Set이 유실되면 배치가 “아직 완료 안 됨”으로 판단하고 이벤트를 다시 발행하는데, 각 모듈의 삭제 연산이 멱등(
DELETE WHERE writer_id = X를 두 번 실행해도 결과는 같음)하기 때문에 재작업 낭비 이상의 문제는 없다.
3. 애플 refresh_token — 둘 다 애매해서 RDB + 암호화로 갔다
카카오/애플 unlink API를 붙이면서, 애플의 revoke(연결 해제)에는 로그인 당시 발급받은 refresh_token을 그대로 다시 보내야 한다는 걸 알게 됐다(자세한 내용).
이 값을 저장해야 했는데, 앞의 두 사례와 다르게 이번엔 Redis도 RDB도 그냥 걸리지 않았다.
- Redis: 이 값은 계정이 살아있는 몇 년 동안 유실 없이 보존돼야 한다.
- 앞의 두 사례(재가입 마커, 완료추적)는 “유실돼도 감내할 수 있는” TTL 데이터였는데, 여기엔 그 전제가 안 맞는다
- 장기 보존이 필요한 값을 “휘발돼도 괜찮다”는 전제로 쓰던 저장소에 넣는 셈이다.
- RDB: OAuth refresh_token은 그냥 컬럼에 평문으로 넣기엔 너무 민감한 데이터이다.
- 컬럼 레벨 암호화가 정석인데, 이 프로젝트엔 암호화 유틸리티가 전혀 없는 상태였다.
결론은 “RDB + AWS KMS 암호화”였다. 이 사례는 앞의 두 개와 성격이 다르다
- 재가입 마커/완료추적은 “유실 리스크 방향”만 보면 바로 답이 나왔는데, 애플 refresh_token은 유실 리스크 판단(장기 보존 필요 → RDB 쪽)과 별개로 “저장 형태 자체가 민감하다”는 보안 문제가 하나 더 얹혀서, 저장소를 정한 뒤에도 암호화라는 추가 작업이 따라붙었다.
요약
| 값 | 유실 시 결과 | 저장소 | 이유 |
|---|---|---|---|
| 재가입 차단 마커 | 재가입이 며칠 일찍 허용 | Redis (TTL) | 경미한 리스크, TTL로 정리 배치도 불필요해짐 |
| 각 모듈별 완료추적 | 정리 작업 재시도(멱등이라 안전) | Redis (Set) | 재작업 낭비 이상의 문제 없음 |
| 애플 refresh_token | 장기 보존 필요 + 값 자체가 민감 | RDB + KMS 암호화 | 유실 리스크와 별개로 보안 문제가 추가로 얹힘 |
세 경우 모두 “Redis가 빠르고 구현이 간단하다”는 점은 동일했다. 다른 건 그 값을 잃어버렸을 때 시스템이 어느 방향으로 실패하느냐였다
- 일시적으로 덜 엄격해지는 방향의 실패는 Redis로 감수할 만하고, 영구적으로 의무를 못 지키는 방향의 실패는 RDB로 막아야 한다.
- “얼마나 자주 읽는가”보다 이러한 질문이 저장소 선택에 훨씬 결정적이었고, 애플 refresh_token 사례처럼 그 판단에 보안이라는 별개의 축이 추가로 얹히는 경우도 있었다.