쿼리 최적화의 중요성을 몸소 체험하게 되었다.
그 동안 딱히 복잡한 쿼리가 없었기에 실감하지 못했는데 이번 프로젝트에서 딱 봐도 서비스 내에서 가장 무거워보이는 쿼리를 장난삼아 부하테스트를 해보다가 내가만든거지같은쿼리 심각성을 깨달았다.
영단어 학습 웹서비스를 개발중이던 우리는 사용자가 회원가입 하기 전, 어떻게 학습이 진행되는지 체험시켜주기 위해 10개의 랜덤한 단어를 순차적으로 보여주고, 각 단어에 사지선다를 제공해 재미삼아 문제를 풀어볼 수 있도록 해주기로 했다. 문제는 로그인한 사용자가 아니기에 모든 데이터를 한번에 보내주고 클라이언트에서 관리해야기에 쿼리가 상당히 뚱뚱해졌었다.
요청
-> Word테이블에서 랜덤한 단어 10개 조회
-> 각 단어마다 랜덤한 4지선다를 제공하기 위해 Word테이블에서 랜덤으로 3개의 단어를 조회
-> 셔플 후 반환
한 번도 쿼리성능에 대해 깊게 생각해본적 없던 나는 쿼리를 루프안에서 사용하기 시작했고 멈춰 그렇게 부하테스트에서 충격적인 결과를 보고 쿼리를 확인해봤을 때, 한 번의 요청에 무려 31번의 쿼리가 실행되고있었다.
30초동안 초당 10번의 요청도 버티지 못하는 처참한 결과..
errors.ETIMEDOUT: .............................................................. 188
http.codes.200: ................................................................ 18
http.codes.500: ................................................................ 94
http.downloaded_bytes: ......................................................... 64766
http.request_rate: ............................................................. 10/sec
http.requests: ................................................................. 300
http.response_time:
min: ......................................................................... 116
max: ......................................................................... 9566
median: ...................................................................... 788.5
p95: ......................................................................... 7117
p99: ......................................................................... 9416.8
http.responses: ................................................................ 112
vusers.completed: .............................................................. 112
vusers.created: ................................................................ 300
vusers.created_by_name.0: ...................................................... 300
vusers.failed: ................................................................. 188
vusers.session_length:
min: ......................................................................... 119.3
max: ......................................................................... 9575.1
median: ...................................................................... 788.5
p95: ......................................................................... 7117
p99: ......................................................................... 9416.8
최적화 진행
일단 현재 가장 많은 쿼리를 발생시키고 있는, 사지선다를 만들기 위한 쿼리부터 어떻게할까 고민했었다. 매번 요청이 들어올때마다 새로 만드는 것 보다 미리 3개의 뜻을 그룹화해서 테이블로 만들고 랜덤하게 조회된 단어의 뜻과 셔플을 해주면 되겠다 싶었다. 그러면 반복문을 사용하지 않아도 되기에 쿼리가 단 두 번으로 줄어들었다.
최적화 진행 후 동일한 조건으로 테스트해봤을 때 많이 안정적인 모습...
http.codes.200: ................................................................ 300
http.downloaded_bytes: ......................................................... 0
http.request_rate: ............................................................. 10/sec
http.requests: ................................................................. 300
http.response_time:
min: ......................................................................... 20
max: ......................................................................... 123
median: ...................................................................... 24.8
p95: ......................................................................... 30.9
p99: ......................................................................... 41.7
http.responses: ................................................................ 300
vusers.completed: .............................................................. 300
vusers.created: ................................................................ 300
vusers.created_by_name.0: ...................................................... 300
vusers.failed: ................................................................. 0
vusers.session_length:
min: ......................................................................... 22.7
max: ......................................................................... 142.5
median: ...................................................................... 29.1
p95: ......................................................................... 37
p99: ......................................................................... 64.7
조금 더 올려서 30초간 초당 30번, 총 900번의 요청을 보내보았다
http.codes.200: ................................................................ 600
http.codes.429: ................................................................ 300
http.downloaded_bytes: ......................................................... 12600
http.request_rate: ............................................................. 30/sec
http.requests: ................................................................. 900
http.response_time:
min: ......................................................................... 0
max: ......................................................................... 554
median: ...................................................................... 80.6
p95: ......................................................................... 98.5
p99: ......................................................................... 223.7
http.responses: ................................................................ 900
vusers.completed: .............................................................. 900
vusers.created: ................................................................ 900
vusers.created_by_name.0: ...................................................... 900
vusers.failed: ................................................................. 0
vusers.session_length:
min: ......................................................................... 1.1
max: ......................................................................... 564.2
median: ...................................................................... 83.9
p95: ......................................................................... 100.5
p99: ......................................................................... 228.2
서버를 보호하기 위해 사용자 당 분당 요청을 600번으로 제한했기에 300번의 요청은 429를 반환하고, 나머지 요청들은 모두 성공한걸 확인할 수 있었다.
그럼 반대로, 짧은 시간동안 더 많은 요청을 보내보기로 했다.
errors.ETIMEDOUT: .............................................................. 74
http.codes.200: ................................................................ 226
http.downloaded_bytes: ......................................................... 0
http.request_rate: ............................................................. 23/sec
http.requests: ................................................................. 300
http.response_time:
min: ......................................................................... 6801
max: ......................................................................... 9987
median: ...................................................................... 9230.4
p95: ......................................................................... 9801.2
p99: ......................................................................... 9999.2
http.responses: ................................................................ 226
vusers.completed: .............................................................. 226
vusers.created: ................................................................ 300
vusers.created_by_name.0: ...................................................... 300
vusers.failed: ................................................................. 74
vusers.session_length:
min: ......................................................................... 6804.8
max: ......................................................................... 9991.2
median: ...................................................................... 9230.4
p95: ......................................................................... 9801.2
p99: ......................................................................... 9999.2
10초간 초당 30번의 요청을 보냈을 때 세션 길이도 많이 길어지고 74번의 타임아웃이 발생했다.
최적화 하기 전 30초간 초당 10번의 요청에 188번의 타임아웃을 발생시키던 때 보단 확실히 좋아지긴했지만 이정도면 쓸만한 상태인건지 확신이 들지 않는다.
해당 api가 그렇게 많이 사용되지 않는다면 괜찮지 않을까?
그렇다고 여기서 더 쿼리를 간소화할 방법이 생각나지 않는다
그러면 해당 서비스의 메커니즘 자체를 바꿔야하나..?
재밌다.
사실 여러번의 프로젝트를 거치면서 반복적인 작업에 조금은 지루함을 느끼고 있던 차에 새로운 문제를 발견하니 오히려 설레는 마음이 컸던것같다 변태아니냐고
시간이 지날수록 코드를 작성하는 시간보다 노려보는 시간이 길어진다고 했었는데, 그 말이 조금씩 이해가 되어가고 있는 코린이다.