Nick Dev

[Outsagram] nGrinder & pinpoint로 병목 지점 파악 후, sharding을 통해 성능 개선한 경험 본문

Outstagram

[Outsagram] nGrinder & pinpoint로 병목 지점 파악 후, sharding을 통해 성능 개선한 경험

Nick99 2024. 12. 12. 06:06
반응형

5줄 요약

  1. nGrinder & pinpoint로 DB에서 getConnection()이 오래 걸린다는걸 파악함
  2. 커넥션 풀 개수 조절해봄(10, 15, 20개)
  3. 별 차이 없었음 😢
  4. sharding을 통해 DB 부하를 절반으로 줄여봄
  5. 성능 개선됨 😊


현재 서버 아키텍처 및 상황

  • 클라우드는 Naver Cloud Platform을 사용 중이다.

  • nGrinder로 가상의 유저의 행동을 스크립트로 작성하고 이를 실행해보았다.

    • 한 유저가 회원가입 -> 로그인 -> 게시물 조회 100회, 게시물 생성 5회, 좋아요 누르기 15회, 북마크 누르기 15회하고 로그아웃 한다고 가정했다.
- 즉, **한 유저 당 138개의 api를 호출**하게 된다.


- connection pool(cp) size는 디폴트인 10이다.


- 이러한 가상의 유저(vuser)가 700명이 있다고 가정하고 10분간 부하를 부어보았다.

- (참고로 이미 캐싱 로직은 적용되어 있다)

✅ cp = 10 인 경우

nGrinder 결과

pinpoint 결과

DB 인스턴스 CPU 사용량

📌 결과 해석

  • 평균 응답 시간은 매우 양호하지만, Response Max가 5.34 sec가 나오는건 좀 심각하다.

  • 그래서 pinpoint로 해당 api 호출의 call stack을 살펴본 결과, getConnection()에서 대부분의 시간을 차지하는 것을 보았다.

  • 처음에는 커넥션 얻는게 오래 걸리니깐 cp가 부족한가..? 라는 생각을 했다.

  • 하지만, Hikari CP의 공식문서에 따르면 connections = ((core_count * 2) + effective_spindle_count)라고 명시되어 있기에 vcpu가 4개인 상황에서 디폴트인 10개면 충분한 개수로 볼 수 있다.

  • 그래도 혹시 모르니...? cp size 조절해서 성능 차이가 있을지 확인해보자



✅ cp = 15, 20 인 경우

cp가 10, 15, 20일 때, 부하 테스트 결과 사진 모음

📌 결과 정리

  • 결과 표를 보면 알 수 있듯이, cp 개수를 변경해도 성능의 개선이 없다.

  • 그럼 connection pool size가 문제가 아님

  • getConnection()에서 오래 걸렸다면 cp size 문제 말고 DB 인스턴스의 CPU 사용량이 최대 77 % 찍은게 문제일 수 있겠다고 생각했다.

    • 매우 심각한 정도의 cpu 사용량은 아니지만 10분간 테스트를 진행했을 때 이정도라면 시간을 늘리게 되면 충분히 문제가 될 수 있는 포인트라고 생각함
  • 그럼 DB에 가해지는 부하를 나누면 기본적인 성능도 개선되고 Response Max도 줄일 수 있을 것 같다.

  • 그러면 이제 샤딩을 진행해보자!!



✅ sharding 적용 후

sharding 적용 후 서버 아키텍처

샤딩 로직 구현 과정은 아래에서~
(AOP로 routing할 db 세팅)
애플리케이션 레벨에서 DB routing 로직 구현하는 과정

nGrinder 결과

pinpoint 결과



⭐ 결과 비교

  • vcpu 4, cp = 10, vuser = 700 은 공통이고 DB sharding 유무의 차이만 있음

  • DB를 2개로 샤딩 하니깐 평균적으로 31.63% 정도 성능이 개선되었다.

  • 특히, 가장 큰 문제였던 Response Max 값이 5.34 sec ➡️ 2.57 sec으로 *50 % *정도 감소하면서 샤딩을 통해서 원하는 바를 어느정도 이뤘다고 볼 수 있다.

  • 그리고 샤딩을 통해서 같은 시간동안 263,028개(30%)의 api를 더 처리할 수 있었다.



✅ 성능 개선하는 과정 요약

1. nGrinder & pinpoint로 부하 테스트 후 call stack 살펴본 결과, getConnection()에서 오래 걸린다는걸 파악함(최대 5.34 sec 소요)

2. 현재 vcpu가 4개이기에 이론적으로 cp 10이면 적당하지만 그래도 커넥션 풀 개수 조절해보고 테스트 후 결과 비교해봄(10, 15, 20개)

3. 역시나 별 차이 없었음 😢

4. 서버 모니터링 결과, DB 인스턴스의 cpu 사용량이 높은걸 확인함

5. DB 인스턴스의 부하를 줄이려면 물리적으로 DB를 나눠보자 = sharding

6. sharding 전후 비교해보니, 평균적으로 31.63 % 성능이 개선되었고, 가장 문제였던 Response Max 값이 5.34 ➡️ 2.57초로 약 50 % 감소했음 😊

느낀점

  • 실제 유저의 트래픽이 아니라 내가 만든 스크립트에 따른 트래픽인게 좀 아쉽긴 하다

  • 그래도, 내가 서비스를 론칭했을 때 받을 유저의 트래픽보단 일부로 좀 더 오버해서 트래픽을 부어보았다.

  • pinpoint로 각 api의 call stack을 확인하고 어디서 많은 시간을 잡아먹는지 보는게 신기했고, 이론적으로 알고있던 connection pool에서의 병목 현상을 직접 경험해봐서 재밌었다.

    • 애플리케이션 로직이 매우 복잡한게 없기도 했지만, 쿼리 자체 수행하는데는 110ms 밖에 안걸리고 connection pool 얻으려고 기다리는 시간이 훨씬 길다는 것을 직접 확인해볼 수 있었다.


Cloud DB 안쓰고 Server를 DB로 쓰는 이유


  • 네트워킹, Pulbic IP, 로드 밸런서 등 비교할게 많지만 간단하게 비교해보면 같은 스펙으로 1달 사용했을 때, 비용이 2배 차이가 난다.

  • 그래서 Server에 docker 설치해서 mysql db를 실행시켜 사용했다.

반응형