Databaseintermediate검토 2026.08

Spring Cache와 Redis 직렬화

Spring Cache의 Key·TTL·직렬화 계약과 ObjectMapper 불일치 문제를 운영 관점에서 정리한다.

#Redis#Spring Cache#serialization#ObjectMapper

Overview

Spring Cache는 저장소 접근을 Annotation으로 감싸지만, 실제 Redis 값에는 Key 규칙, TTL, Serializer, 타입 정보라는 장기 계약이 생긴다. 배포 버전 사이 계약이 달라지면 Hit가 오히려 역직렬화 예외를 일으킨다.

한 문장 설명: 캐시 직렬화 설정은 구현 세부사항이 아니라 여러 Application 버전이 공유하는 데이터 Schema다.

왜 필요한가

기본 ObjectMapper와 Redis 전용 Mapper가 서로 다른 Module이나 Typing 설정을 쓰면 날짜, Kotlin 타입, 다형 타입을 다르게 해석한다. Rolling 배포 중 구버전이 쓴 값을 신버전이 읽으므로 한 Process에서만 성공하는 테스트로는 부족하다.

핵심 원리

Key에는 Cache 이름과 Version, Domain ID를 포함하고 Value 형식의 소유자를 정한다. 전역 Web JSON 설정을 무심코 재사용하거나 별도 Mapper를 방치하지 말고 필요한 Module, 날짜 형식, 알 수 없는 필드 처리, Type 정보 정책을 명시한다. Java Native 직렬화는 언어·클래스 구조 결합과 보안 위험 때문에 피한다.

내부 동작

@Cacheable은 계산 전 Key를 만들고 CacheManager에 조회한다. Miss면 Method를 실행한 뒤 Serializer가 결과를 Byte로 바꾸어 저장한다. Hit면 역직렬화 결과를 반환하므로 Method 본문은 실행되지 않는다. Proxy를 거치지 않는 Self-invocation은 Cache Annotation이 적용되지 않을 수 있다.

sequenceDiagram; participant P as Spring Proxy; participant C as CacheManager; participant R as Redis; participant M as Method; P->>C: key 조회; C->>R: bytes 조회; alt hit; R-->>C: bytes; C-->>P: deserialize; else miss; P->>M: execute; M-->>P: object; P->>C: serialize+TTL; end

쉬운 비유

직렬화는 물건을 상자에 포장하는 규격이다. 쓰는 쪽은 새 포장법인데 읽는 쪽은 옛 설명서라면 상자는 있어도 열지 못한다.

Example

@Cacheable(cacheNames = "product-v2", key = "#id")
public ProductView findProduct(Long id) { ... }

v2처럼 형식 변경을 Key namespace로 분리하면 구형 값과 충돌을 피하고 점진적으로 전환할 수 있다. Cache별 TTL은 데이터 변경 빈도와 Stale 허용 시간을 기준으로 설정한다.

실무에서 발생하는 문제

DTO 필드 변경, 날짜 Module 누락, Class 이름을 포함한 타입 정보, Null Cache 여부, Key 충돌이 흔하다. 역직렬화 실패가 모든 요청 실패로 번지지 않도록 문제 Key 삭제나 Miss 처리 정책을 검토한다. 같은 Redis를 여러 서비스가 공유하면 Cache 이름 충돌도 발생한다.

Trade-off

JSON은 관찰과 호환성이 좋지만 크기와 CPU 비용이 있고, Binary 형식은 작지만 디버깅과 Schema 진화가 어렵다. 타입 정보를 넣으면 복원이 쉬워질 수 있지만 클래스 결합과 안전한 허용 목록 관리가 필요하다.

흔한 오해

  • Annotation을 붙였다고 내부 호출까지 자동 Cache되는 것은 아니다.
  • 전역 ObjectMapper와 Redis Serializer가 항상 같은 설정인 것은 아니다.
  • 캐시 데이터라서 Schema Migration이 불필요한 것이 아니다. Rolling 배포 구간이 있다.

Production Considerations

배포 전 구버전 Write → 신버전 Read와 반대 방향의 호환성 테스트를 한다. Cache 이름을 Versioning하고 TTL 동안 두 형식이 공존할 수 있게 한다. 역직렬화 오류 수, Hit/Miss, Value 크기를 측정하며 민감정보를 Cache에 넣을 때 암호화와 접근 범위를 검토한다.

Interview Questions / Follow-up Questions

  1. Spring Cache의 Self-invocation이 동작하지 않을 수 있는 이유는?
  2. Rolling 배포에서 캐시 직렬화 호환성이 필요한 이유는?
  3. 후속: DTO가 바뀔 때 Cache를 안전하게 전환하는 방법은?
  4. 후속: JSON에 타입 정보를 넣는 방식의 위험은?

Redis 운영 지도, 캐시 전략, Spring Core의 Proxy 개념과 함께 본다.

SOURCE REFERENCES

이 문서의 근거

본문은 Dev Atlas 안에서 완결되며, 검증이 필요할 때만 원문을 확인할 수 있습니다.

원문 출처 보기 1