Caffeine(로컬) + Redis(리모트) 레이어드 캐시가 읽기 TPS에 얼마나 영향을 주는지, K6 부하테스트로 DB만 / Redis만 / Caffeine+Redis 3단 비교합니다.
┌──────────────────────────────────────────────────────────────┐
│ K6 부하 (100 VUs) │
└─────────────────────────┬────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Spring Boot API │
│ │
│ ┌──────────┐ ┌────────────┐ ┌──────────────────┐ │
│ │ db-only │ │ redis-only │ │ layered │ │
│ │ │ │ │ │ │ │
│ │ → DB │ │ → Redis │ │ → Caffeine │ │
│ │ │ │ → DB │ │ → Redis │ │
│ │ │ │ │ │ → DB │ │
│ └──────────┘ └────────────┘ └──────────────────┘ │
└──────────────────────────────────────────────────────────────┘
| 도구 | 버전 | 설치 확인 |
|---|---|---|
| Docker & Docker Compose | - | docker --version |
| Java | 21 | java --version |
| K6 | - | k6 version |
K6 설치:
brew install k6(Mac) / 공식 가이드
docker-compose up -d첫 실행 시 10만건 상품 데이터가 자동 생성됩니다 (약 1~2분).
MySQL이 Ready 상태인지 확인:
docker logs -f cache-proof-mysql 2>&1 | grep "ready for connections"
ready for connections메시지가 2번 나오면 준비 완료입니다.Ctrl+C로 로그 확인을 종료하세요.
데이터 확인 (선택):
docker exec cache-proof-mysql mysql -uroot -proot cache_proof -e "SELECT COUNT(*) FROM product;"결과가
100000이면 성공입니다.
터미널 2개를 사용합니다. 하나는 앱 실행, 하나는 K6 실행.
# 터미널 1: 앱 시작
CACHE_MODE=db-only ./gradlew bootRun# 터미널 2: K6 부하테스트
k6 run k6/load-test.js테스트 완료 후
Ctrl+C로 앱을 종료합니다.
# 터미널 1: 앱 재시작
CACHE_MODE=redis-only ./gradlew bootRun# 터미널 2: K6 부하테스트
k6 run k6/load-test.js# 터미널 1: 앱 재시작
CACHE_MODE=layered ./gradlew bootRun# 터미널 2: K6 부하테스트
k6 run k6/load-test.jsK6 출력에서 아래 항목을 비교합니다:
http_req_duration ← avg, p(50), p(95), p(99)
http_reqs ← total / duration = TPS
detail_latency ← 상품 상세 조회 latency
list_latency ← 상품 목록 조회 latency
| 모드 | avg | p(95) | p(99) | TPS |
|---|---|---|---|---|
| DB Only | 3.17 ms | 8.70 ms | 20.98 ms | 2,736 /s |
| Redis Only | 1.34 ms | 3.69 ms | 8.06 ms | 2,931 /s |
| Layered | 1.08 ms | 3.40 ms | 11.26 ms | 2,954 /s |
환경(CPU, 메모리, Docker 리소스)에 따라 수치가 달라집니다. 중요한 것은 모드 간 상대적 차이입니다.
참고: 이 테스트는 상품 ID를 1~100,000 균등 랜덤으로 요청하므로, 캐시에 가장 불리한 조건입니다. Layered의 p(99)가 Redis Only보다 높은 것은 Caffeine miss 시 cold path(Redis+DB 동기 로딩) 때문입니다. 실무에서 인기 상품 위주로 트래픽이 집중되면 Caffeine hit rate가 올라가며 p(99)도 개선됩니다.
| 엔드포인트 | 설명 |
|---|---|
GET /api/mode |
현재 캐시 모드 확인 |
GET /api/stats |
DB 쿼리 수, Redis/Caffeine hit/miss 통계 |
POST /api/stats/reset |
통계 초기화 |
POST /api/cache/reset |
모든 캐시(Redis + Caffeine) 초기화 |
# 예시: 통계 확인
curl -s http://localhost:8080/api/stats | python3 -m json.toolround5-blog-proof/
├── docker-compose.yml # MySQL + Redis
├── build.gradle.kts # Spring Boot + Kotlin
├── k6/
│ └── load-test.js # K6 부하테스트 스크립트
├── sql/
│ ├── 01-schema.sql # 테이블 + 인덱스 생성
│ └── 02-seed-data.sql # 10만건 시드 데이터
└── src/main/kotlin/.../
├── Application.kt # Spring Boot 메인
├── Product.kt # JPA Entity + DTO
├── ProductRepository.kt # Spring Data JPA
├── ProductFacade.kt # 3-mode 캐시 전략 (핵심)
└── ProductController.kt # REST API
CACHE_MODE 환경변수에 따라 읽기 경로가 달라집니다:
- db-only:
ProductRepository→ DB 직접 조회 - redis-only: Redis → miss 시 DB 조회 → Redis에 저장
- layered: Caffeine(2초 리프레시) → Redis → DB
동일한 API, 동일한 요청, 캐시 모드만 다르게 해서 순수하게 캐시 레이어의 효과만 비교합니다.
# 컨테이너 + 볼륨 삭제 (데이터 초기화)
docker-compose down -v다시
docker-compose up -d하면 10만건이 새로 생성됩니다.