끄적끄적 서비스 조회 부하 테스트 2편: 데이터베이스 인덱스로 성능 향상. 가능할까?

최기웅·2024년 12월 23일
2
post-thumbnail

[이전 상황]

이전 1편에서는 쿼리 최적화를 통해 조회 성능을 크게 개선한 사례를 다뤘습니다. 쿼리 구조를 단순화하고 효율적인 접근 방식을 도입한 결과, 성능 지표에서 상당한 개선을 확인할 수 있었습니다.

하지만 실무에서는 조금 더 안전하고 빠른 조회를 요구하는 상황이 자주 발생합니다. 이러한 요구를 충족하기 위해, 이번에는 데이터베이스 인덱스 적용을 통해 성능 최적화를 한 단계 더 끌어올리고자 합니다.


[문제의 핵심]

쿼리 최적화를 통해 초기 성능 문제를 해결했지만, 데이터가 계속해서 증가하면서 대규모 데이터를 처리하는 데 한계가 드러났습니다.

특히, 테이블 크기가 커질수록 쿼리 처리 시간이 선형적으로 증가하여 성능이 저하되었습니다.

이에 따라, 기존 최적화 방법만으로는 부족하다고 판단해 DB 인덱스라는 새로운 해법을 검토하게 되었습니다.


[DB 인덱스란 무엇인가?]

인덱스에 대한 자세한 설명은 이미 여러 블로그에서 다루고 있으므로, 여기서는 간단히 핵심만 짚고 넘어가겠습니다.

데이터베이스 인덱스는 데이터를 빠르게 검색할 수 있도록 돕는 구조로, 주로 B-Tree 또는 Hash Table을 기반으로 동작합니다.

  • B-Tree 인덱스
    • 데이터가 정렬된 상태로 저장되어, 범위 검색과 순차 검색에 뛰어난 성능을 발휘합니다.
    • MySQL의 InnoDB 스토리지 엔진은 B-Tree 기반으로 인덱스를 생성합니다.
  • Hash 인덱스
    • 키-값 매핑을 기반으로 동작하며, 정확한 값 검색(Equality Search)에 최적화되어 있습니다.
    • 그러나 범위 검색(e.g., BETWEEN, >, <)에는 사용할 수 없습니다.
    • MySQL에서는 메모리 테이블에서 주로 사용됩니다.

[DB 인덱스의 장점과 단점]

  • 장점
    • 데이터 검색 속도 향상
      • 테이블의 데이터가 많아도 빠르게 검색할 수 있습니다.
      • 데이터베이스는 인덱스를 통해 특정 데이터를 바로 찾아가므로, 풀 테이블 스캔을 피할 수 있습니다.
    • 범위 검색 최적화
    • 정렬 속도 개선
  • 단점
    • 데이터 수정 시 성능 저하
    • 추가 저장 공간 필요
    • 과도한 인덱스 사용 부작용

현재 프로젝트에서는 MySQL을 사용하고 있으며, 블록을 조회하는데 정렬 및 범위 검색이 빈번하기 때문에 DB 인덱스를 적용합니다.


[DB 인덱스 적용]

이제부터 끄적끄적 서비스에 DB 인덱스를 적용하려합니다.

DB 인덱스를 적용하는 방법에는 2가지가 있습니다.

  1. JPA를 활용한 테이블 설정
    • @Table@Index 어노테이션을 사용하여 엔티티 클래스에 인덱스를 정의합니다.
    • JPA가 실행되는 시점에 DDL을 생성하여 데이터베이스에 반영합니다.
  2. MySQL 명령어로 직접 설정
    • MySQL의 CREATE INDEX 명령어를 사용하여 수동으로 추가합니다.

[선택]

  1. 코드 기반의 관리
    • 현재 프로젝트에서 JPA를 사용하고 있기 때문에, 코드 기반 관리가 더 직관적이고 일관성을 유지할 수 있습니다.
  2. 자동화된 DB 작업
    • JPA가 실행되는 시점에 자동으로 데이터베이스를 생성 및 수정하므로, 직접적인 DB 작업 부담을 줄일 수 있습니다.
  3. 유지보수의 용이성
    • 가장 중요한 이유는 유지보수의 편리함입니다.
    • 저희는 3명의 백엔드 개발자가 협업하고 있기 때문에, 엔티티에 정의된 인덱스 정보를 통해 인덱스 구조를 직관적으로 이해할 수 있습니다.
    • 이는 협업 과정에서 오해를 줄이고, 생산성을 높이는 데 큰 도움이 됩니다.

이러한 이유들로 인해, 저는 JPA 기반의 테이블 설정 방법을 선택했습니다.


[테스트 결과 (k6)]

1편에서 했던 테스트와 같은 환경으로 진행합니다.

쿼리 최적화 후 결과 (1편 최종 결과)

data_received..................: 4.3 GB 70 MB/s
data_sent......................: 25 kB  398 B/s
http_req_blocked...............: avg=58.12µs  min=3µs     med=7µs      max=1.1ms    p(90)=13µs     p(95)=424.29µs
http_req_connecting............: avg=15.67µs  min=0s      med=0s       max=379µs    p(90)=0s       p(95)=186.29µs
http_req_duration..............: avg=2.34s    min=1.36s   med=2.16s    max=4.92s    p(90)=2.92s    p(95)=4.74s   
  { expected_response:true }...: avg=2.34s    min=1.36s   med=2.16s    max=4.92s    p(90)=2.92s    p(95)=4.74s   
http_req_failed................: 0.00%  0 out of 183
http_req_receiving.............: avg=189.73ms min=96.54ms med=173.96ms max=495.27ms p(90)=225.14ms p(95)=472.17ms
http_req_sending...............: avg=42.14µs  min=10µs    med=22µs     max=511µs    p(90)=55.2µs   p(95)=193.99µs
http_req_tls_handshaking.......: avg=0s       min=0s      med=0s       max=0s       p(90)=0s       p(95)=0s      
http_req_waiting...............: avg=2.15s    min=1.2s    med=2s       max=4.43s    p(90)=2.76s    p(95)=4.3s    
http_reqs......................: 183    2.945099/s
iteration_duration.............: avg=3.34s    min=2.36s   med=3.16s    max=5.92s    p(90)=3.92s    p(95)=5.74s   
iterations.....................: 183    2.945099/s
vus............................: 3      min=3        max=10
vus_max........................: 10     min=10       max=10

DB 인덱스 적용 후 결과

data_received..................: 5.1 GB 82 MB/s
data_sent......................: 29 kB  467 B/s
http_req_blocked...............: avg=46.41µs min=1µs      med=6µs     max=1ms      p(90)=11µs     p(95)=20.39µs
http_req_connecting............: avg=12.61µs min=0s       med=0s      max=331µs    p(90)=0s       p(95)=0s
http_req_duration..............: avg=1.85s   min=1.11s    med=1.8s    max=3.04s    p(90)=2.01s    p(95)=2.44s
  { expected_response:true }...: avg=1.85s   min=1.11s    med=1.8s    max=3.04s    p(90)=2.01s    p(95)=2.44s
http_req_failed................: 0.00%  0 out of 214
http_req_receiving.............: avg=180.1ms min=98.18ms  med=180.9ms max=274.13ms p(90)=208.97ms p(95)=212.93ms
http_req_sending...............: avg=24.31µs min=3µs      med=19µs    max=789µs    p(90)=34.7µs   p(95)=46.69µs
http_req_tls_handshaking.......: avg=0s      min=0s       med=0s      max=0s       p(90)=0s       p(95)=0s
http_req_waiting...............: avg=1.67s   min=964.91ms med=1.61s   max=2.9s     p(90)=1.83s    p(95)=2.31s
http_reqs......................: 214    3.458618/s
iteration_duration.............: avg=2.86s   min=2.12s    med=2.8s    max=4.04s    p(90)=3.01s    p(95)=3.44s
iterations.....................: 214    3.458618/s
vus............................: 4      min=4        max=10
vus_max........................: 10     min=10       max=10

쿼리 최적화 전과 후, 그리고 DB 인덱스 적용 후의 3가지 결과를 보기 좋게 표로 정리합니다.

[결과 데이터]

항목쿼리 최적화 전쿼리 최적화 후DB 인덱스 적용 후
데이터 수신량2.3 GB (35 MB/s)4.3 GB (70 MB/s)5.1 GB (82 MB/s)
http_req_duration
(HTTP 평균 요청 처리 시간)
5.49s2.34s1.85s
P90 요청 시간7.13s2.92s2.01s
P95 요청 시간7.9s4.74s2.44s
HTTP 요청 수96 요청 (1.49 요청/초)183 요청 (2.95 요청/초)214 요청 (3.46 요청/초)
HTTP 요청 수신 대기 시간237.22ms189.73ms180.1ms
http_req_waiting
(HTTP 평균 요청 대기 시간)
5.26s2.15s1.67s
총 요청 실패율0.00%0.00%0.00%
총 요청 수96 요청183 요청214 요청
iteration 평균 시간
(하나의 테스트가 실행되는데 걸리는 시간)
6.5s3.34s2.86s

[쿼리 최적화 후 → DB 인덱스 적용 후] 성능 개선을 요약하면 이와 같습니다.

  • 평균 요청 처리 시간: 20.9% 감소 (2.34s → 1.85s)
  • P90/P95 성능 개선:
    • P90(2.92s → 2.01s) 및 P95(4.74s → 2.44s) 지표가 각각 31.2%, 48.5% 단축되었습니다.
    • 대부분의 요청이 빠르게 처리되며 안정성이 높아졌습니다.
  • 평균 요청 대기 시간: 22.3% 감소 (2.15s → 1.67s)
  • HTTP 요청 수: 16.6% 증가 (183 → 214)
  • iteration 평균 시간: 14.3% 단축 (3.34s → 2.86s)

[정리]

  • 총 2번의 최적화로 검색 성능을 획기적으로 향상시켰습니다.
  • 테스트 결과, 평균 요청 시간과 최악의 응답 시간이 모두 눈에 띄게 감소했습니다.

[향후 고민]

  1. 인덱스 유지 비용
    • 업데이트나 삭제 작업 시 인덱스 재구성 비용이 발생합니다.
    • progress, status 같은 변동이 잦은 필드에 인덱스가 적합한지 고민 중입니다.
  2. 캐싱 도입 가능성
    • 업데이트/삽입 빈도가 높은 데이터는 DB 쿼리를 줄이고, 캐싱 레이어를 추가하는 것도 하나의 대안으로 보고있습니다.

✏️ 끄적끄적 서비스 링크

🐈‍⬛ 끄적끄적 프로젝트 깃허브 링크

profile
https://giwoong01.tistory.com/

0개의 댓글

관련 채용 정보