[PERF] 잔디 캐시 직렬화 — EVERYTHING 타입 메타데이터로 큰 페이로드 캐시가 역효과
📊 개선 시급성 및 우선순위
⚡ 성능 개선 개요 및 목표
연관 이슈 번호 : #
연관 도메인 : grass (전역 설정이라 다른 캐시 도메인에도 영향 가능 — 랭킹 등)
현재 상태 (As-Is) : RedisConfig가 모든 캐시에 GenericJackson2JsonRedisSerializer + ObjectMapper.DefaultTyping.EVERYTHING을 전역 적용 중. 캐시에 들어가는 모든 객체·필드에 타입 메타데이터(@class)가 붙어서, 페이로드가 큰 캐시(grassYearly:v2, grassLessons:v2 — 365일치, 21KB+)는 역직렬화 비용이 캐시 이득을 상회함. 중간 강도 부하(10,000명, 30초 분산) 측정 결과 캐시를 켰는데 캐시 없을 때보다 더 느려짐:
/api/grass/yearly: 280ms(캐시 없음) → 1,809ms(캐시 있음, 현재 직렬화)
/api/grass/lessons: 766ms(캐시 없음) → 1,681ms(캐시 있음, 현재 직렬화)
목표 상태 (To-Be) : 캐시 이름별로 타입을 고정한 가벼운 직렬화(Jackson2JsonRedisSerializer<구체타입>)로 교체. 로컬 실험 결과 yearly 1,809ms → 26ms(91%↓, 10.8배), lessons 1,681ms → 22ms(97%↓, 34.8배)로 정상화됨.
💻 상세 작업 내용 및 영향 계층
🚨 검증 및 사이드 이펙트 확인 (TestCode)
📸 실행 계획(Explain) 및 지표 로그
측정 환경: 회원 10,000명, daily_study_stats 665만 행, k6 ramping-arrival-rate(10,000명이 30초에 걸쳐 분산 도착)
| 엔드포인트 |
캐시 없음 |
캐시 + 기존 직렬화 |
캐시 + 가벼운 직렬화(제안) |
/api/grass (view) |
19ms |
27ms |
18ms |
/api/grass/yearly |
280ms |
1,809ms ⚠️ |
26ms |
/api/grass/lessons |
766ms |
1,681ms ⚠️ |
22ms |
/api/grass/monthly |
18ms |
27ms |
17ms |
⚠️ = 캐시를 켰는데 캐시 없을 때보다 더 느려진 구간
참고: 극단적으로 짧은 시간(10초)에 동시 요청이 몰리는 상황에서는 직렬화를 가볍게 바꿔도 개선이 제한적임(시스템 자체의 동시 처리 한계가 다른 병목으로 작용) — 이건 이번 PR 범위 밖, 별도 인프라/용량 검토 과제로 분리.
원본 비교/코드 예시: Notion 문서 참고
[PERF] 잔디 캐시 직렬화 — EVERYTHING 타입 메타데이터로 큰 페이로드 캐시가 역효과
📊 개선 시급성 및 우선순위
P1: 주요 API 응답 지연 및 사용자 경험(UX) 저하 현상 해결P0: 치명적인 성능 병목 (타임아웃 발생, 서버 리소스 고갈 등 긴급 조치 필요)P2: 사전 예방 차원의 쿼리 튜닝 및 리소스 사용 효율화⚡ 성능 개선 개요 및 목표
연관 이슈 번호: #연관 도메인: grass (전역 설정이라 다른 캐시 도메인에도 영향 가능 — 랭킹 등)현재 상태 (As-Is):RedisConfig가 모든 캐시에GenericJackson2JsonRedisSerializer+ObjectMapper.DefaultTyping.EVERYTHING을 전역 적용 중. 캐시에 들어가는 모든 객체·필드에 타입 메타데이터(@class)가 붙어서, 페이로드가 큰 캐시(grassYearly:v2,grassLessons:v2— 365일치, 21KB+)는 역직렬화 비용이 캐시 이득을 상회함. 중간 강도 부하(10,000명, 30초 분산) 측정 결과 캐시를 켰는데 캐시 없을 때보다 더 느려짐:/api/grass/yearly: 280ms(캐시 없음) → 1,809ms(캐시 있음, 현재 직렬화)/api/grass/lessons: 766ms(캐시 없음) → 1,681ms(캐시 있음, 현재 직렬화)목표 상태 (To-Be): 캐시 이름별로 타입을 고정한 가벼운 직렬화(Jackson2JsonRedisSerializer<구체타입>)로 교체. 로컬 실험 결과 yearly 1,809ms → 26ms(91%↓, 10.8배), lessons 1,681ms → 22ms(97%↓, 34.8배)로 정상화됨.💻 상세 작업 내용 및 영향 계층
Model/Policy:UseCase/Command:Service:Port/Adapter:RedisConfig.cacheManager()—grassMonthly:v2/grassYearly:v2/grassView:v2/grassLessons:v24개 캐시에 한해withInitialCacheConfigurations()로 타입 고정 직렬화 적용. 나머지 캐시(랭킹 등)는 기존EVERYTHING설정 유지(이번 PR 범위 밖, 별도 검토 필요).Repository: (예: Fetch Join 적용, 인덱스 추가 등)Controller:response/request:🚨 검증 및 사이드 이펙트 확인 (TestCode)
CacheSerializationRoundTripTest에 그대로 적용 — 타입 고정 직렬화도 record 역직렬화가 정상 동작하는지 확인)📸 실행 계획(Explain) 및 지표 로그
측정 환경: 회원 10,000명,
daily_study_stats665만 행, k6ramping-arrival-rate(10,000명이 30초에 걸쳐 분산 도착)/api/grass(view)/api/grass/yearly/api/grass/lessons/api/grass/monthly참고: 극단적으로 짧은 시간(10초)에 동시 요청이 몰리는 상황에서는 직렬화를 가볍게 바꿔도 개선이 제한적임(시스템 자체의 동시 처리 한계가 다른 병목으로 작용) — 이건 이번 PR 범위 밖, 별도 인프라/용량 검토 과제로 분리.
원본 비교/코드 예시: Notion 문서 참고