🗓️ 2025.07.18 ~ 2025.08.05 1차 기술 세미나
계좌 상세 페이지 진입 시 호출되는 '매일 이자 받기' 기능에 Redis 캐싱을 적용했다.
이 기능은 짧은 시간 내에 반복 요청이 많은 특성을 가지므로, 조회 결과를 Redis에 캐싱해 DB 부하를 줄이고 응답 속도를 개선할 수 있다.
선택 이유 :
비즈니스 로직 제어 용이: "오늘 이자를 받았는지" 등 조건부 처리 로직을 애플리케이션에서 직접 제어 가능
장애 격리: Redis 장애 시에도 DB 통해 서비스 지속 가능
유연한 캐싱 정책: TTL, 키 전략 등 유동적으로 설정 가능
➡️ 복잡한 금융 비즈니스 로직에 적합하고, 장애 대응력과 유연성 확보
Read-Through 대안 검토 결과
-> 복잡함 및 안정성 문제로 부적합 판단
-> 단순 조회에는 적합하지만, 비즈니스 조건이 복잡한 금융 서비스에는 부적합
선택 이유:
DB와 캐시를 동시에 갱신해 데이터 정합성 확보 → 금융 시스템에 적합
📎 캐시 일관성 유지 전략 비교
1️⃣ Update 방식: DB 저장 후 Redis에 바로 갱신 (0으로)
장점: 높은 캐시 히트율, 빠른 응답
단점: 동시성 제어(분산락) 필요, 복잡한 구현
2️⃣ Eviction 방식: DB 저장 후 Redis 캐시 삭제
장점: 구현 간단, 정합성 보장, 동시성 문제 없음
단점: 삭제 후 첫 조회 시 DB 부하 및 응답 지연
➡️ 두 전략 모두 구현해보고 성능 및 안정성 측면에서 비교 분석함
구현에 앞서, Cache Update 전략의 단점 중 하나는 동시에 여러 요청이 들어올 경우 중복 처리가 발생할 수 있다는 점이다.
[Race Condition 예상 시나리오]
이를 실제 운영 환경에서 발생할 수 있는 시나리오로 구성해봤다.
동일 사용자가 모바일과 웹 브라우저에서 동시에 이자 수령 버튼을 클릭했다고 가정하자. 분산락이 없으면 두 요청 모두 getExistsTodayInterest()에서 "미수령" 상태로 판단해 각각 500원씩, 총 1,000원이 중복 지급된다.
🌟 해결 방법 = 계좌 단위 분산락을 적용
Redis의 setIfAbsent()(SETNX)로 계좌별 락을 획득하고, 첫 번째 요청만 정상 처리한다. 두 번째 요청은 락을 못 잡으면 바로 차단된다. 락 TTL은 5초로 설정해 데드락도 방지했다.
[구현 방법 – RedisTemplate 기반 Cache Update]
Spring Cache(@CachePut)는 간단하게 캐싱을 적용할 수 있지만, TTL을 동적으로 설정하기 어렵고 분산락과 통합할 수 없으며 조건부 캐싱 로직에도 제약이 많다.
그래서 우리는 RedisTemplate을 선택했다. 이 방식은 세밀한 제어 가능,
분산락과의 자연스러운 통합, 동적 TTL 설정 가능이라는 장점이 있다.
핵심 플로우는 다음과 같다.
분산락 획득 – setIfAbsent()로 계좌별 락 설정 (5초 TTL)
비즈니스 로직 실행 – 이자 지급 처리
TTL 계산 – 당일 자정까지 남은 시간 계산 후 캐시 TTL 설정
캐시 갱신 – DB 접근 없이 다음 요청 처리
락 해제 – TTL 만료 전이라도 비즈니스 완료 시 즉시 해제
이 흐름을 통해 중복 지급을 막으면서도 DB 부하를 최소화하고, TTL을 상황에 맞게 조정할 수 있다.
[구현 방법 – Cache Eviction]
핵심 개념: 삭제 후 재생성
DB에 이자 지급 내역을 저장한 직후, evictCachedInterest()로 해당 캐시 키를 삭제한다. 이후 최초 조회 시 DB에서 최신 데이터를 불러오고, 이를 기반으로 캐시가 자동 재생성된다.
이 방식은 RedisTemplate.delete()를 사용해 간결하게 DB-캐시 정합성을 맞출 수 있다.
캐싱 전략이 실제 서비스 부하를 얼마나 완화하는지 검증하기 위해 다음과 같이 성능 측정 환경을 구성했다.
아키텍처 구성
K6 – JavaScript 기반 부하 테스트 도구, 실제 사용자 트래픽 시뮬레이션
Spring Boot Actuator – JVM 상태, 커넥션 풀, 응답시간 등 핵심 지표 노출
Prometheus – 실시간 지표 수집 및 시계열 데이터 저장
Grafana – 수집된 데이터를 실시간 대시보드로 시각화
측정 지표
처리량 – 초당 요청 수(RPS), 총 처리 요청 수
응답 시간 – 평균 및 분포
에러율 – 요청 실패 비율
이 환경을 통해 Cache Update와 Eviction 전략이 실제 부하 환경에서 성능을 어떻게 개선하고, DB 부담을 얼마나 줄이는지 검증했다.
자세한 내용은 깃허브로!!
https://github.com/FISA-TechSeminar/Better-Bank-BE/pull/18
캐싱 전과 cache update, eviction의 전략을 비교해보았다.
🧪 테스트 조건 = 동시 사용자: 100명 / 테스트 지속 시간: 10분
🏷️ 구분 | 📊 총 처리 건수 | ⚡ 평균 처리량 (req/s) | 📈 성능 향상률 | 특징 |
---|---|---|---|---|
1. 베이스라인 – Redis 적용 전 | 23,715건 | 39.0 | - | 요청 지연이 두드러짐, 캐싱 최적화 필요성 확인 |
2-1. Redis 적용 후 – Cache Update 전략 | 59,001건 | 99.2 | 🔼 154% | 높은 캐시 히트율 유지, 분산락·TTL 계산 로직 복잡, 동시성 제어 부담 |
2-2. Redis 적용 후 – Cache Evict 전략 | 59,254건 | 96.7 | 🔼 148% | 구현 단순, 데이터 정합성 보장, 유지보수 용이, Update 전략 대비 2.5 req/s(2.5%) 낮음 |
Update 와 Evict 전략 특성 비교는 다음과 같다.
🔒 여기서 우리는 의문을 가지게 되었다.
왜 Cache Update가 속도는 더 빠른데 총 처리 건수는 적을까?
🔑 그 이유는 다음과 같다
: Cache Evict 전략은 캐시 적중 시에는 처리 속도가 빠르고 초당 요청 처리량(RPS)도 높아지지만, 캐시 미스가 발생하면 DB까지 조회해야 하므로 응답이 느려지고 RPS가 낮아진다. miss된 요청은 더 많은 처리를 포함하므로 한 번의 요청이 더 많은 sub-request를 유발해 Total Requests는 많아진다.
금융권에서 최우선은 데이터 일관성과 신뢰성이다.
성능 차이 2.5%보다 중복 지급·정합성 오류를 원천 차단하는 Cache Evict 전략이 더 적합하다.
이번 세미나를 준비하고 발표하면서 Redis 캐싱 전략을 더 깊이 이해할 수 있었고, 고트래픽 환경의 병목 문제를 직접 다뤄본 좋은 경험이 되었다. 앞으로 이 경험을 바탕으로 최종 프로젝트에도 적용해보고, 실용적인 IT 역량을 키우며 서비스 안정성과 효율성에 기여하는 개발자가 되도록 꾸준히 노력하겠다!
팀 얘기를 하자면, 팀워크가 너무너무 좋아서 정말 재밌게 준비했다. 내 강박과 같은 계획에도 팀원들이 웃으며 함께 계획을 세우고 동참해줘서 고마웠다. 모두가 어느정도 완벽주의라 그런지 서로 잘 맞았던 것 같고 덕분에 의지가 꺾이지 않고 끝까지 최선을 다할 수 있었다. ㅎㅎ 정말 행복한 세미나였고, 덕분에 우승까지 했다~~!
안 풀릴 때마다 과자를 까먹으며 늦게까지 준비하고,
불꽃 여자라는 별명도 얻고,
열심히 발표 준비도 하고,
상도 받고,
끝나고 회식도 했다!
발표 영상을 유튜브에 올렸으니 궁금하면 👉🏻👉🏻 발표 영상 😳