1. 성능 개선 방법
이전 포스팅에서 nGrinder를 통한 성능테스트 및 개선을 수행했다. 계속 진행한 테스트 시나리오를 보면 다음과 같은 플로우로 요청을 하는데 팀목록이나 경기 목록, 경기 정보와 같은 조회 API는 한번 DB에 등록이 되면 변하지 않는다. 따라서 변하지 않는 데이터에 한해서 캐시를 적용해 성능 최적화를 해보려고 한다.
1. 로그인
2. 팀 목록
3. 경기 목록
4. 경기 정보
5. 좌석 등급 목록
6. 좌석 등급 정보
7. 예약 좌석 목록
8. 좌석 선택
9. 예매
2. 캐시(Cache)
먼저 캐시와 캐싱전략을 알아보자.
2.1 캐시(Cache)
캐시는 한번 조회된 데이터를 미리 특정 공간에 저장해놓고, 똑같은 요청이 발생하면 서버에게 다시 요청하지 않고 저장해높은 데이터를 가져와 빠르게 데이터를 제공해주는것을 말한다.
캐시의 주요 장점은 응답 속도의 향상이다. 캐시를 사용하면 DB에 접근하지 않기 때문에 불필요한 DB 부하를 줄여주기때문에 시스템의 전반적인 성능을 크게 향상시킬 수 있다. 또한 캐시를 사용할때 가장 중요한 점은 일관성이다. 캐시된 데이터는 항상 최신 상태를 유지해야하며 캐시된 데이터가 업데이트 되면 원본 데이터와 동기화하는 일관성을 가져야한다.
2.2 캐시 방식
캐시는 다양한 방식으로 구현될 수 있다. 메모리 내 캐시, 분산 캐시, 디스크 캐시 등 여러 형태가 있다.
메모리 내 캐시(In-Memory Cache)는 어플리케이션의 로컬 메모리에 데이터를 저장한다. 메모리에 직접 접근하므로 매우 빠르지만 서버 재 시작 시 캐시 데이터가 사라지고, 분산 시스템에서 각 서버의 캐시가 독립적이기 때문에 데이터 일관성 유지가 어렵다.
분산 캐시(Distributed Cache)는 여러 서버에 걸쳐 데이터를 분산 저장한다. 따라서 확장성이 뛰어나다. 하지만 네트워크 통신을 통해 데이터에 접근하므로 메모리 내 캐시보다 느리다. Redis, Memcached가 대표적인 예이다.
디스크 캐시 (Disk Cache)는 데이터를 디스크에 저장한다. 디스크가 메모리보다 큰 용량을 제공하기 때문에 더 큰 용량을 저장할 수 있으며 서버 재시작 후에도 데이터가 유지된다. 하지만 디스크 I/O는 메모리 접근보다 느리다. 브라우저 캐시(쿠키, 로컬 스토리지)등이 대표적인 예이다.
1.3 캐싱 전략
캐싱 전략을 캐시를 더 효율적이고 저장하고 접근할 수 있도록 캐시를 사용하는 방법을 말한다.
Cache -Aside
어플리케이션에서 캐시를 직접 사용하는 방식이다. 어플리케이션 코드가 캐시를 탐색하고 캐시가 존재하면(Cache hit) 캐시에서 바로 데이터를 반환한다. 만약에 캐시가 존재하지 않는다면(Cache miss) DB에서 데이터를 조회한 후 캐시에 다시 적재한다.
이 패턴의 장점은 데이터를 캐시에 저장하고 조회하는 논리가 어플리케이션 내에 정확히 구현되어 있어 구현이 간단하다. 단점은 캐시가 데이터에 없는 경우 응답시간이 오래 걸리고, 데이터의 일관성이 깨질 수 있다. 예를 들어 DB에서 데이터가 업데이트되고, 캐시가 갱신되기 까지 시간이 소요되는데 이 동안 캐시된 데이터가 업데이트 되지 않는다면 일시적으로 데이터 일관성이 깨진다. 이 패턴은 주로 읽기 작업에 적합하다.
Read-Through
Cache-Aside와 비슷한 방법이나 조금 다르다. 어플리케이션에서 데이터를 요청하고 캐시가 존재하지 않는다면 (Cache miss), 캐시가 DB에서 데이터를 가져와서 캐시에 저장한 후 클라이언트에게 전달한다. 따라서 Cache miss가 발생했을 때 주체가 다르다. Cache-Aside는 어플리케이션, Read-Through는 캐시가 데이터를 처리한다.
3. 캐시(Cache) 적용 후 테스트
이제 내 프로젝트의 캐시를 적용해보자. 먼저 어떻게 캐시를 적용해볼까? 생각해보았을 때 현재 프로젝트가 Scale out을 통해서 이미 분산화가 되어있기 때문에 Redis와 같은 외부 저장소에 캐시를 저장하는 것이 데이터의 일관성을 유지하는 것이 좋다고 생각해 Redis에 저장하기로 했다.
그리고 테스트 시나리오에서 정보를 가져오는 모든 API를 캐싱처리를 했다.
2. 팀 목록
3. 경기 목록
4. 경기 정보
6. 좌석 등급 정보
그리고 좌석 등급 정보 응답시간이 94ms →12ms로 확실히 줄어들음 확인할 수 있었다.
기존과 똑같은 조건으로 nGrinder VUser 300명, agent 1개, 실행시간 1분을 고정시키고, warm up을 설정해 테스트했다.
테스트를 수행한 결과, TPS는 캐싱 적용 전후로 2,817 → 3,364 로 약 20% 증가하였다. 또한 Mean Test Time 는 55.40ms → 46.86ms 로 약 15% 감소했다.
응답시간은 평균 15ms, 최대 421ms가 나왔다.
CPU 사용량은 최대 약 61%이 나왔다.
4. 결론
정리하면 Scale-out을 도입함으로써 다음과 같은 성능 개선을 이루었다.
Scale-out 이전 | Scale-out 이후 | 캐싱 후 | 개선 | |
TPS | 1,546 | 2,817 | 3,364 | 117.56% |
CPU | 80% | 44% | 60% | 25% |
평균 응답시간 | 32ms | 11ms | 15ms | 113.33% |
최대 응답시간 | 795ms | 413ms | 421ms | 97.34% |
개선은 scale-out 이전과 캐싱 후을 비교했다.
'Spring' 카테고리의 다른 글
nGrinder와 Pinpoint로 성능테스트 및 개선하기 (4) - Scale out (0) | 2024.05.20 |
---|---|
nGrinder와 Pinpoint로 성능테스트 및 개선하기 (3) - Connection Pool 크기 조절 (0) | 2024.05.19 |
nGrinder와 Pinpoint로 성능테스트 및 개선하기 (2) - 병목지점 파악 (0) | 2024.05.14 |
nGrinder와 Pinpoint로 성능테스트 및 개선하기 (1) - 환경 구축 (0) | 2024.05.10 |
Redis Lua Script 실제로 Atomic할까? (0) | 2024.04.18 |