Skip to content

성능 테스트 ‐ 랭킹 JWT 인덱스 적용 (최종)

min9805 edited this page Aug 27, 2024 · 19 revisions

개요

  • 테스트 API 종류
    • 상위 20명 랭킹 조회
    • 특정 인원 게임 점수 갱신

API 로직

DB 사용 로직

  • 랭킹 조회
    • EventUsers 테이블에 GameScore 필드로 Index 가 생성되어있다.
    • SELECT (...) FROM EventUsers ORDER BY game_score DESC LIMIT 20
    • 해당 쿼리는 20 개의 row 만 검사하기에 매우 빠른 실행 속도를 가진다.
  • 게임 점수 갱신
    • EventUser 테이블의 특정 row 의 GameScore 를 갱신한다.
    • GameScore 로 index 가 생성되어있기 때문에 update 시 index 재정렬이 필요해 EventUser 테이블이 클 수록 많은 시간이 소요된다.

Redis 사용 로직

  • 랭킹 조회
    • 서버 실행 시 GameScore 상위 20명의 정보를 가져와 Redis SortedSet 에 삽입한다.
    • SortedSet 의 score 는 GameScore 를 사용한다.
    • 랭킹 조회 시 상위 20명의 데이터를 가져온다.
  • 게임 점수 갱신
    • 점수 갱신 시 데이터베이스에 접근하지 않고 해당 유저의 정보와 점수를 SortedSet 에 삽입한다.
    • N 명 점수 갱신 시 Redis 에는 (20 + N) 개 이하의 데이터가 삽입된다. (중복 시 갱신 처리로 데이터의 양이 늘어나지 않음)
    • 이후 Batch 작업으로 Redis 와 DB 를 동기화시킨다.

예상 (1)

  • index 를 사용한 데이터베이스 직접 조회와 Redis 조회의 성능 차이는 무의미하다.
  • 로직 별 응답속도 차이를 확인하기 위해 점수 갱신의 API 요청 비율을 높인다.
  • 점수에 대해 index 가 생성되어있기 때문에 데이터베이스 갱신 시 Redis 의 SortedSet 에 add 하는 것보다 더 많은 시간이 걸릴 것이다.

DB

Type Name # Requests # Fails Median (ms) 95%ile (ms) 99%ile (ms) Average (ms) Min (ms) Max (ms) Average size (bytes) Current RPS Current Failures/s
GET /api/v1/lottery/drawing/rank?subEventId=4 53914 0 1700 3900 6100 1856.07 3 58826 1067.55 336.7 0
GET /api/v1/lottery/drawing/score?subEventId=4 32561 0 1900 4000 6400 1974.69 5 58825 142.19 197 0
  Aggregated 86475 0 1800 4000 6200 1900.73 3 58826 719.12 533.7 0

image image image

Redis

Type Name # Requests # Fails Median (ms) 95%ile (ms) 99%ile (ms) Average (ms) Min (ms) Max (ms) Average size (bytes) Current RPS Current Failures/s
GET /api/v1/lottery/drawing/rank?subEventId=4 44550 0 1500 3900 5800 1642.36 4 40140 1084.09 301.6 0
GET /api/v1/lottery/drawing/score?subEventId=4 26955 1 1700 4200 6000 1782.8 7 40138 142.13 186 0
  Aggregated 71505 1 1600 4000 5900 1695.3 4 40140 729 487.6 0

image image image

실제 (1)

  • 데이터베이스에 10만명의 유저가 있는 상황에서 (Locust 기준) 2000 명의 User, 600 이상의 Rps 이후 응답 시간이 급격하게 증가하는 현상
  • 해당 시점 이전까지는 DB, Redis 40ms 이하의 응답 시간을 보인다.
  • 해당 시점 이후의 응답 시간의 증가는 랭킹 관련 로직 (DB, Redis 사용 유무) 과 관련없이 t3.smaill 인스턴스의 Spring 서버가 요청을 수용할 수 있는 한계라 판단.
    • 즉, 응답 시간의 증가는 로직의 병목 구간이 아닌 인스턴스와 서버의 한계이기에 해당 테스트로 로직에 따른 성능을 평가할 수 없다.
    • 실제 로그를 통해 메서드 실행 시간을 판별했을 때에도 요청의 수와 무관하게 항상 낮은 실행 시간을 직접 확인할 수 있었다. 즉, 메서드의 병목이 아니다.
  • 따라서 DB, Redis 의 사용 유무에 따른 로직 성능을 평가하기 위해서는 각 요청의 응답 시간을 증가시키기 위해 EventUser 를 100만명으로 증가시켰다.
Method DB Average Time (ms) Redis Average Time (ms)
랭킹 갱신 22.26 ms 11.75 ms
랭킹 조회 8.12 ms 15.18 ms

예상 (2)

  • 100만명의 유저를 생성할 경우 Redis 에 업데이트 및 조회 및 DB 조회에서는 성능 상 차이가 없을 것으로 예상한다.
  • 다만, 데이터베이스 직접 업데이트 시 인덱스 재정렬을 위한 시간이 유저가 많아질 수록 더 많이 소요될 것이라 예상하며 이는 Redis 와의 속도 차이로 연결 될 것으로 예상한다.
  • 여전히 조회에 있어서 DB 조회와 Redis 조회에서는 무의미한 성능 차이를 가질 것이다.

DB

Type Name # Requests # Fails Median (ms) 95%ile (ms) 99%ile (ms) Average (ms) Min (ms) Max (ms) Average size (bytes) Current RPS Current Failures/s
GET /api/v1/lottery/drawing/rank?subEventId=4 178453 0 440 2400 3200 739.63 4 4924 1076.79 311.6 0
GET /api/v1/lottery/drawing/score?subEventId=4 107253 3 470 2400 3200 766.97 5 4616 142.08 191.2 0
  Aggregated 285706 3 450 2400 3200 749.89 4 4924 725.91 502.8 0

image image image

Redis

Type Name # Requests # Fails Median (ms) 95%ile (ms) 99%ile (ms) Average (ms) Min (ms) Max (ms) Average size (bytes) Current RPS Current Failures/s
GET /api/v1/lottery/drawing/rank?subEventId=4 174595 0 450 2400 3300 760.16 3 5019 1107.72 320.5 0
GET /api/v1/lottery/drawing/score?subEventId=4 104954 3 480 2400 3300 786.78 5 4996 142.08 197.9 0
  Aggregated 279549 3 460 2400 3300 770.15 3 5019 745.18 518.4 0

image image image

실제 (2)

  • 100만명의 유저를 넣었지만, 실제 데이터베이스에서 업데이트 쿼리의 속도를 profiling 해봤을 때에도 성능적으로 우수함을 볼 수 있다.
  • 실제 Locust 로 테스트 해봤을 때에도 2000명 이하 User, 600 이하의 Rps 의 경우 DB와 Redis 의 성능에서는 유의미한 차이를 찾아볼 수 없었다.
SET profiling = 1;
UPDATE event_users SET game_score = 95 WHERE id = 387280;
SHOW PROFILE FOR QUERY 2;
단계 시간 (초)
starting 0.000080
Executing hook on transaction 0.000006
starting 0.000009
checking permissions 0.000142
Opening tables 0.000053
init 0.000007
System lock 0.000048
updating 0.000060
end 0.000004
query end 0.000005
waiting for handler commit 0.000012
closing tables 0.000009
freeing items 0.000045
cleaning up 0.000014

MySQL Cache

MySQL 내에서도 캐시가 존재하기에 조회의 성능을 높이는 요소 중 하나이다.

Query Average Execution Time (seconds)
Mysql 캐시 미사용 0.003841
Mysql 캐시 사용 0.001912

결론

  • 결론적으로 100만명의 유저가 존재하는 경우에서도, t3.small 인스턴스 위 Spring 서버 자체의 한계만 존재할 뿐 로직 자체의 병목 현상은 발생하지 않는다.
  • 또한 기획적 의도에서 참여자 수가 175,500 명이기 때문에 해당 환경에서는 외부 리소스(비용)인 Redis 의 도입보다 데이터베이스의 index 를 사용하는 것이 옳다.
  • 다만 상황에 따라 랭킹 로직의 부하를 데이터베이스에서 Redis 로 옮기는 것도 충분히 고려할 수 있다.