1. 테스트 배경

웹 애플리케이션에서 데이터베이스 접근은 성능에 매우 큰 영향을 미칩니다. 특히 대규모 트래픽 상황에서 동일한 데이터 요청이 반복된다면, 데이터베이스 부하가 급격히 증가하여 응답 속도가 느려지고 시스템 안정성이 저하될 수 있습니다.

 

이를 해결하기 위해 흔히 사용하는 방법 중 하나가 데이터베이스 인덱싱입니다. 하지만 인덱싱만으로 해결되지 않는 부하 문제를 더 효율적으로 해결하기 위해 캐싱(Caching)을 도입할 수 있습니다.

 

이번 테스트에서는 데이터베이스에 적절한 인덱싱을 설정한 상태에서 캐싱을 추가로 적용했을 때 성능에 어떤 차이가 발생하는지 궁금하여, Spring Boot와 JPA를 사용하여 @Cacheable을 활용한 성능 효과를 Artillery로 측정하고 분석해 보았습니다.


2. 캐싱 적용 전후 비교

테스트는 다음 두 가지 시나리오로 나누어 진행했습니다

  • 캐싱 미적용: 데이터베이스에서 모든 요청을 처리하며, 데이터 캐싱을 사용하지 않는 상태.
  • 캐싱 적용: Spring의 @Cacheable을 사용하여 동일한 요청에 대해 캐싱된 데이터를 반환.

@Cacheable 사용 코드

@Cacheable(value = "customers", key = "#condition.gender + '-' + #pageable.pageNumber")
public Page<CustomerResponse> search(Pageable pageable, CustomerSearchCondition condition) {
    pageable = pageable == null ? PageRequest.of(0, 5) : pageable;
    return customerSearchRepository.search(condition, pageable);
}

3. 캐싱 적용 전후 성능 비교

항목캐싱 적용 전캐싱 적용 후개선 효과

총 요청 수 1563건 2800건 +79%
완료된 시나리오 287개 700개 +143%
평균 RPS (초당 처리량) 21.17 46.42 +119%
중앙값 지연 시간 3963.5ms 1ms -99.97% (거의 즉시 응답)
95th 퍼센타일 지연 시간 8904ms 78ms -99.1%
최대 지연 시간 9994ms 797ms -92%
오류 수 413건 0건 완전 제거

 


4. 결론

캐싱의 효과

  • 요청 처리 속도 향상: 캐싱을 통해 데이터베이스 요청 횟수가 감소하면서 전체 지연 시간이 크게 단축되었습니다. 캐싱된 데이터를 반환하므로 중앙값 기준 지연 시간이 3963.5ms에서 1ms로 개선되었습니다.
  • 처리량 증가: 평균 RPS(초당 요청 처리 수)가 21.17에서 46.42로 약 2배 증가하여 더 많은 요청을 처리할 수 있게 되었습니다.
  • 안정성 향상: 캐싱 미적용 상태에서 데이터베이스 과부하로 인해 발생했던 ETIMEDOUT 오류 413건이 캐싱 적용 후 완전히 사라졌습니다.

시스템 안정성

  • 캐싱 적용은 데이터베이스 부하를 줄여 응답 속도와 시스템 안정성을 동시에 개선할 수 있음을 확인했습니다. 특히, 95th 퍼센타일 지연 시간이 8904ms에서 78ms로 크게 단축되어 사용자 경험도 향상되었습니다.

5. 캐싱 적용 시 유의사항

캐싱은 모든 경우에 적합하지 않으며, 적용 시 주의해야 할 점이 있습니다:

  • 정적 데이터에 적합: 캐싱은 변경되지 않거나 자주 변경되지 않는 데이터(예: 코드 테이블, 인기 상품 등)에 적합합니다.
  • 메모리 관리 필요: JVM 메모리를 사용하는 캐싱(Simple Cache)은 대규모 트래픽 환경에서는 메모리 부족 문제를 유발할 수 있습니다. Redis와 같은 분산 캐싱 솔루션을 고려해야 합니다.
  • TTL(Time To Live) 설정: 캐싱된 데이터의 유효 기간을 적절히 설정하여 오래된 데이터 반환 문제를 방지해야 합니다.

6. 최종 생각

캐싱은 데이터베이스 부하를 줄이고 애플리케이션의 응답 속도를 높이는 데 효과적인 도구입니다. 이번 테스트를 통해 캐싱이 성능 개선에 미치는 영향을 수치로 확인할 수 있었습니다.

  • 적용 사례: 읽기 요청이 많은 API, 동일한 조건으로 반복 호출되는 요청, 외부 API 호출 결과 캐싱 등.
  • 향후 확장 가능성: Simple Cache를 Redis 등 분산 캐시로 확장하여 대규모 트래픽도 안정적으로 처리할 수 있습니다.

데이터베이스 성능 최적화에서 인덱싱은 빠른 조회 속도를 보장하는 중요한 도구입니다. 하지만 인덱스를 많이 생성하면 쓰기 작업(INSERT, UPDATE, DELETE)의 성능이 저하된다고 알려져 있습니다. 이번 글에서는 실제 실험 데이터를 기반으로 이 가정을 테스트한 결과를 공유합니다.


1. 테스트 목적

멀티 컬럼 인덱스와 단일 컬럼 인덱스를 다수 적용했을 때, 쓰기 작업의 성능이 얼마나 저하되는지 확인하는 것이 목적이었습니다. 특히, 데이터베이스에 110만 건 이상의 데이터를 넣고 쓰기 작업을 수행하며, 인덱스가 없는 상태와 있는 상태를 비교해 보았습니다.


2. 테스트 환경

  • 데이터베이스: MySQL 8.0
  • 테이블: Customer 테이블
  • 데이터 크기: 약 110만 건
  • 인덱스 설정:
    • 테스트 A: 기본 키 외에 추가 인덱스 없음
    • 테스트 B: 복합 인덱스 2개 적용
CREATE INDEX idx_customer_name_email ON customer(name, email);
CREATE INDEX idx_customer_phone_grade ON customer(phone, grade);

 

  • 테스트 도구: Artillery를 사용한 부하 테스트
  • 테스트 작업:
    • INSERT: 고객 데이터 삽입
    • UPDATE: 고객 데이터 수정
    • DELETE: 고객 데이터 삭제

3. 테스트 시나리오 및 데이터

요청 예시

config:
  target: 'http://localhost:8080'
  phases:
    - duration: 10
      arrivalRate: 10
    - duration: 20
      arrivalRate: 20
    - duration: 30
      arrivalRate: 30
    - duration: 20
      arrivalRate: 10

scenarios:
  - flow:
      # 1. 로그인 후 토큰 획득
      - post:
          url: "/api/v1/auth/signintest"
          json:
            email: "test2@gmail.com"
            password: "123456789"
          capture:
            - json: "$.data.atk"
              as: "authToken"

      # 2. 고객 정보 수정 (쓰기 작업)
      - patch:
          url: "/api/v1/customers/1000"
          headers:
            Authorization: "Bearer {{ authToken }}"
          json:
            name: "Updated Name {{ $randomInt }}"
            phone: "010-{{ $randomInt }}-{{ $randomInt }}"

      # 3. 고객 삭제 (쓰기 작업)
      - delete:
          url: "/api/v1/customers/{{ $randomInt }}"
          headers:
            Authorization: "Bearer {{ authToken }}"

      # 4. 고객 메모 작성/수정 (쓰기 작업)
      - put:
          url: "/api/v1/customers/100/memo"
          headers:
            Authorization: "Bearer {{ authToken }}"
          json:
            content: "This is a test memo with random value {{ $randomInt }}."
            title: "Test Memo Title {{ $randomInt }}"

 


4. 테스트 결과

1. 지연 시간(Latency) 비교

지표 첫 번째 결과 (인덱스 없음) 두 번째 결과 (인덱스 있음)

최소 지연 시간 (min) 0 ms 0 ms
최대 지연 시간 (max) 109 ms 391 ms
중앙값 (median) 1 ms 2 ms
95퍼센타일 (p95) 77 ms 77 ms
99퍼센타일 (p99) 79 ms 80 ms

분석

  • 최소 지연 시간은 두 결과 모두 동일합니다 (0 ms).
  • 최대 지연 시간에서 큰 차이가 나타납니다:
    • 인덱스가 있는 경우 최대 지연 시간은 391 ms.
    • 인덱스가 없는 경우 최대 지연 시간은 109 ms.
  • 중앙값에서 인덱스가 있는 경우 2 ms, 없는 경우 1 ms로 차이가 있습니다.
  • 95% 및 99% 지연 시간은 거의 동일하게 나타났습니다.

5. 결론

멀티 컬럼 인덱스를 적용하면 최대 지연 시간이 늘어났다는 것을 알았지만 지금 테스트가 뭔가 이상한 거 같아서 다시 한번 검토가 필요한 거 같습니다.

'성능테스트 > 성능 개선' 카테고리의 다른 글

JPA에서 캐싱 활용하여 성능 테스트  (0) 2024.12.20

+ Recent posts