[캐시전략] K6로 캐시 적용 전후 성능 테스트해보기

y001·2025년 4월 14일
0
post-thumbnail

1. 단순한 설계로는 알 수 없다 — 왜 실험이 필요한가?

이 프로젝트에서 가장 먼저 캐시가 필요한 지점은 단연 홈 화면이었다. 홈 화면은 모든 사용자가 진입하자마자 자동으로 호출하게 되는 고정 API를 가지고 있고, 특히 /api/movies 엔드포인트는 상영 중인 영화 목록을 페이징 없이 전부 내려주는 API였다. 이 API는 한 번에 많은 데이터를 반환하기 때문에 DB I/O와 직결되는 부하가 컸다.

이론적으로는 “캐시를 쓰면 성능이 좋아진다”고 말할 수 있지만, 실제 운영 환경에서는 단순히 캐시를 도입했다고 해서 모든 요청이 빨라지지는 않는다. 특히 로컬 캐시인 Caffeine을 썼을 때와 중앙 캐시인 Redis를 붙였을 때 어떤 상황에서 속도가 차이나고, 멀티 인스턴스 환경에서 어떤 효과를 가지는지는 직접 실험해보지 않으면 알 수 없는 영역이다. 이번 글에서는 Caffeine과 Redis 각각의 캐시 전략을 적용한 후, 실제 동일한 요청을 반복하며 응답 시간, 서버 부하, 처리량, 그리고 캐시 계층의 동작 흐름을 K6를 통해 수치화하고 실시간 로그 분석을 통해 확인하는 과정을 정리했다.

2. 실험 설계 — 구조별 비교를 위한 명확한 시나리오 수립

테스트는 총 세 가지 구조에 대해 진행했다. 각각은 모두 동일한 API 엔드포인트(/api/vX/movies)를 사용하지만 내부적으로 다른 캐시 계층을 가지고 있으며, 이로 인해 처리 방식이 달라진다.

버전구조캐시 방식기대 흐름
v1기본 구조캐시 없음모든 요청이 DB로 향함
v2로컬 캐시Caffeine서버 내 메모리에서 응답, 빠름
v3분산 캐시Caffeine + Redis일정 횟수 이상 호출되면 Redis 승격, 멀티 인스턴스 간 공유 가능

이 세 가지 구조에 대해 동일한 요청을 반복하면서, 응답 시간이 어떻게 달라지는지, 서버가 캐시를 어떻게 활용하는지, 데이터가 어느 시점에서 Redis로 올라가는지를 수치와 로그로 모두 검증했다.

3. 실험 준비 — 캐시 초기화와 멀티 인스턴스 환경 구성

실험의 신뢰성을 확보하기 위해 모든 테스트는 cold start 상태에서 시작되었다. Redis는 redis-cli FLUSHALL 명령으로 데이터를 전부 비웠고, Caffeine은 서버 인스턴스를 완전히 내려서 메모리가 초기화되도록 했다. 이후, 같은 애플리케이션을 8081, 8082 포트로 각각 실행했다. 이렇게 하면 두 서버 인스턴스는 독립된 JVM을 가지므로 Caffeine 캐시는 완전히 분리되고, 공통의 Redis를 통해서만 데이터를 공유할 수 있게 된다.

터미널에서 루트 디렉터리에서 아래 명령 실행:

./gradlew clean bootJar

# 인스턴스 A (8081 포트)
java -jar bootstrap/build/libs/bootstrap.jar --server.port=8081

# 인스턴스 B (8082 포트)
java -jar bootstrap/build/libs/bootstrap.jar --server.port=8082

멀티 인스턴스를 구성한 이유는 “서버 A에서 채운 캐시가 서버 B에서도 유효한가?”, 즉 “Redis 기반 분산 캐시가 실전에서 의미 있게 작동하는가?”를 확인하기 위함이었다.

4. 테스트 방식 — K6 기반의 실제 요청 시뮬레이션

K6는 부하 테스트용 CLI 도구로, 단순 반복 호출을 넘어 동시 사용자 수, 요청 간격, 요청 패턴까지 세밀하게 구성할 수 있다는 장점이 있다. 이번 테스트에서는 다음과 같은 방식으로 시나리오를 구성했다. v1, v2는 localhost:8080에서 실행 중인 서버를 대상으로 조건 없는 전체 조회, 조건 기반 검색 요청을 반복하며 평균 응답 시간을 측정했고, v3는 멀티 인스턴스인 localhost:80818082를 번갈아 호출하며 캐시 계층이 어떻게 반응하는지를 로그로 관찰했다. 특히 v3의 경우는 처음엔 DB로 직접 가고, 일정 hitCount를 넘기면 Redis로 승격되며, 그 다음부터는 다른 인스턴스에서도 Redis HIT이 발생하게 되는 구조를 재현하고자 했다.

5. 테스트 결과 — 구조에 따른 성능 수치 비교

항목v1 (캐시 없음)v2 (Caffeine)v3 (Caffeine + Redis)
평균 응답 시간13.12ms13.12ms113.24ms
최대 응답 시간405ms405ms973ms
평균 대기 시간12.88ms12.88ms112.66ms
초당 처리량97.3 req/s97.3 req/s8.96 req/s
응답 성공률100%100%100%

v1/v2는 동일 스크립트에서 측정되었고, v3는 cold start 상태에서 반복 요청을 통해 Redis 승격 조건을 실험했다.

6. 캐시 계층의 동작 — 로그로 확인한 실제 흐름

v3 구조에서 서버를 띄우고 테스트를 시작하면, 다음과 같은 흐름이 로그로 나타난다.

📡 DB QUERY [key=movie:32:92319]
🚫 Redis SKIP (hitCount=0)
✅ Caffeine Cache HIT

이 메시지는 다음과 같은 흐름을 뜻한다: 첫 요청은 DB에서 데이터를 조회하고, Redis는 아직 hitCount 조건이 안 맞아서 무시(SKIP)되며, DB 결과는 Caffeine에 저장되어 이후에는 Caffeine에서 응답된다. 일정 횟수 이상 반복되면 Redis로 승격되고, 서버가 다르더라도 Redis에서 데이터를 읽게 된다. 이 구조는 로그 상에서 명확하게 확인되었고, 멀티 인스턴스 간 일관된 응답을 제공할 수 있음을 입증했다.

7. 요약 및 결론

로컬 캐시(Caffeine)는 빠르다. 하지만 이는 서버 인스턴스 하나에만 한정된 이야기이며, 서버가 여러 대로 수평 확장되면 각 인스턴스의 캐시는 분리되고, 동일한 요청이어도 일부는 DB로 내려갈 수 있다. 반면 Redis는 느릴 수 있지만 확장성과 일관성을 보장한다. 처음엔 느릴 수 있지만, 캐시가 채워지고 나면 모든 인스턴스에서 동일한 응답을 빠르게 처리할 수 있으며, 인프라가 커질수록 Redis 기반의 공유 캐시는 더욱 필수적인 요소가 된다.

환경추천 캐시 구조
단일 서버 / 내부 도구Caffeine 단독
다중 서버 / 실서비스Caffeine + Redis (3단 구조)

0개의 댓글