지표관리 화면의 초기 조회 성능 개선
API 병목 해소를 위해 캐싱 가능한 도메인 경계를 정의하고 무효화 규칙 설계
1. 문제발생
'지표관리' 화면은 최초 진입 시 지표 목록·상세·임계치·변경이력 등 여러 조회 API가 동시에 호출되는 구조였어요.
이 중 초기 조건 지표 목록 조회는 항상 동일 조건임에도 불구하고 매 요청마다 DB를 재조회해야했고, 이는 전체 지연의 주요 병목으로 작용했어요.
특히 캐시 대상이 아닌 지표 기본정보 상세 API 또한 초기 진입 시 333ms까지 지연되는 등 전체 초기 로딩 성능이 심각하게 저하되는 문제가 발생했어요.
2. 해결과정
캐시 적용이 단순하지 않은 도메인 구조
지표는 결재·버전·부서 관리가 얽혀 있어 전체를 무작정 캐싱하면
금융 도메인 특성상 데이터 일관성 사고로 이어질 수 있는 상황이었어요.
따라서
“무엇을 캐싱할 수 있고, 무엇을 캐싱하면 안 되는지”
도메인 관점에서 경계를 직접 정의해야 했어요.
2-1. 도메인 관점에서 ‘캐시 가능한 경계’를 직접 정의
지표 상세·임계치·이력·버전 상태 등은 결재 흐름과 밀접하게 연결되어 있어 캐싱 불가.
반면, 검색 초기 조건은 변경 이벤트가 극히 드물고 반복 호출되는 영역이었어요.
현업 분들께서도 해당 업무 시, '검색 초기화' 버튼을 가장 많이 사용한다고 알려주셨어요.
따라서 캐싱 가능한 범위를 다음처럼 명확히 설정했다.
“오직 초기 조건 검색만 캐싱한다”
2-2. 도메인 이벤트 기반 캐시 무효화(Evict) 규칙 설계
지표 목록은 다음 이벤트가 발생할 때만 변경되므로
해당 시점마다 캐시를 무효화해야 했어요.
- 지표 신규 등록
- 지표 수정
- 지표 결재 요청
- 결재 승인(신규 버전 생성) 혹은 반려
즉, 캐시 무효화는 단순 CRUD가 아니라
결재 흐름과 버전 생성이라는 도메인 이벤트를 기준으로 설계해야 했어요.
이벤트 기반 Evict를 통해 캐시와 실제 비즈니스 상태의 정합성을 확보했어요.
2-3. 운영환경에서 발생한 Redis Serialization 장애 해결
로컬에서는 ConcurrentMapCacheManager로 정상 작동했지만
운영(UAT/PRD)은 RedisCacheManager가 자동 활성화되어
NotSerializableException이 발생했어요.
해결
- 캐시 대상 DTO 전체에
implements Serializable적용 serialVersionUID명시- nested 객체 구조 정리
- Redis 직렬화 포맷 및 해시 슬롯 기반 분산 구조 검증
- Cluster 9노드 환경에서의 Evict 전파 확인
이로써 캐싱이 운영 환경에서도 안정적으로 동작하도록 문제를 해결했어요.
2-4. 캐싱이 비즈니스 일관성을 깨지 않도록 idempotent 구조 설계
지표는 결재 상태 및 버전 관리가 핵심이기 때문에
조회가 잘못되면 금융사에서는 “사고”가 될 수 있어요.
그래서 아래 원칙을 설계했어요.
- 읽기는 캐시 기반
- 쓰기(등록/수정/결재/승인 또는 반려) 는 반드시 캐시 무효화
- 즉, 캐시는 항상 최신 상태와 동기화되며
모든 작업이 idempotent(반복 실행해도 동일 결과) 하도록 구성
3. 결과
✔ 3-1. 성능 개선
- 초기 로딩 6개 API 묶음 총 응답시간
610ms → 321ms (약 47% 개선) - 캐시 대상이 아닌
지표 기본정보 상세도
333ms → 132ms (약 2.5배 개선)
✔ 3-2. 도메인 일관성 유지
- 결재/버전관리/부서정보가 뒤섞이는 사고 가능성 제거
- 캐싱이 비즈니스 로직과 충돌하지 않도록 완전한 경계 정의 완료
✔ 3-3. 운영환경까지 포함한 전 구간 안정화
- Redis Cluster 기반에서도 정상 작동
- 캐시 저장·조회·무효화 전 구간 검증 완료
- DTO 직렬화 문제 해결
+ 문제 원인 분석
캐시 적용 후 캐시 대상이 아닌 API까지 응답시간이 단축된 점을 보면,
당시 요청 타이밍에서 DB 커넥션 풀 경쟁이나 WAS 스레드 대기 같은 공통 병목이 완화된 것으로 해석할 수 있어요.
이는 대규모 시스템에서 특정 API의 부하 제거가 전체 리소스 경합 감소로 이어지는 전형적인 간접 성능 개선 패턴이에요.
4. 운영 리스크 분석
문제: 고객사 담당자가 DB에 직접 insert/update하는 경우 캐시 무효화가 불가능
해결 방향:
- 기술적 관점의 ‘완화(Mitigation)’ 전략
- TTL(Time To Live) 기반의 Soft-Inconsistency 허용
- 캐시 TTL을 너무 길게 설정하지 않고 적절한 수준에서 expire 되도록 설계
- 직접 조작이 매우 드물다는 전제를 이용해 실질적인 영향 최소화
- DB Trigger → 메시지 발행(Event) 방식
- 외부 시스템 작업을 허용한다면 DB Trigger → Redis Pub/Sub, Kafka, Message Queue로 이벤트를 날려 캐시를 비동기로 무효화하는 아키텍처도 가능
- 하지만 금융권/폐쇄망 환경처럼 외부 의존이 제한되면 도입이 어려워요.
- 운영 정책 관점의 ‘방지(Prevention)’ 전략
- 운영서버 DB 직접 수정 금지 정책 확립
- 대부분의 기업은 운영 DB 직접 조작을 원칙적으로 금지
- DB 직접 조작은 데이터 무결성과 이력 관리 문제를 유발하기 때문
- 따라서 “캐시 무효화 실패”보다 훨씬 더 큰 리스크를 만들게 됨
- 필요 시 전용 Admin API 제공
- 고객사 내부 운영자가 데이터를 수정해야 하는 사유가 존재한다면 이를 위한 전용 관리자 API를 제공하고 해당 API가 캐시 무효화를 수행하도록 설계
- 직접 수정보다는 훨씬 안정적이고 추적 가능