DB index를 통해 조회 성능을 향상시키기

서버 Latency가 이상해요!

February 16, 2024 · 3 mins read

서론

진우님, Latency가 이상해요!

3년 전, 시즌패스라는 신규 feature 개발에 처음으로 백엔드 메인 개발을 담당했었다. 1-2달 정도의 개발 기간 이후 라이브 서버에 feature를 공개하였는데, 서버 latency가 점점 느려지는 현상이 확인되었다. 다행히 간단한 DB index 적용으로 해결되었다.

구조와 문제

우선, DB 구조는 다음과 같다. (보안 등의 이유로 일부 각색하였다)

user
id (PK)
...
pass_setting
id (PK)
title
start_date
end_date
rewards
...
pass
id (PK)
user_id (FK)
pass_setting_id (FK)
level
claim_rewards
...

게임을 실행하면, pass_setting에서 현재 진행중인 시즌패스 이벤트를 불러온다. 시즌패스 이벤트가 진행중이라면, pass DB에서 user_id, pass_setting_id를 통해 유저의 현재 이벤트 참여 현황을 불러온다. (만약 존재하지 않다면, 새로 만든다)

시즌패스 이벤트가 진행중일 때, 대략적으로 3초에 한 번 씩 pass DB를 접근해야 한다. 이 때도 마찬가지로 위와 같은 두 column을 where clause을 통해 가져오는데, 두 column에 아무런 처리가 되어있지 않아 매 번 Full scan을 실행하게 된다. 효율적으로 할 수는 없을까?

Index란?

Indexes are used to find rows with specific column values quickly. Without an index, MySQL must begin with the first row and then read through the entire table to find the relevant rows.

MySql 공식 문서를 인용하면, Index는 특정한 column을 통해 값을 매우 빠르게 찾기 위해 사용된다. 기본적으로 MySQL에서 where clause 등을 통해 레코드를 탐색할 때에는 첫번째 row부터 마지막까지 모든 레코드들을 탐색하면서 찾고자 하는 데이터를 찾는다. 즉, Full scan한다고 할 수 있다.

Index는, 검색의 대상이 되는 주요 column을 별도로 정렬해두어 이후 해당 column을 통해 검색 시 정렬된 순서로부터 필요한 값을 빠르게 찾을 수 있도록 한다. (참고로 B-Tree 알고리즘을 이용한다)

당연한 말이지만, 자주 수정되는 column들은 Index에 적절하지 않다. update가 발생할 때 마다 정렬을 다시 수행해줘야 하기 때문이다.

분석과 해결

기본적으로 PK는 index가 적용되어 있다. 그러나 필자의 경우, pk인 id가 아닌, user_idpass_setting_id로 검색을 하였기 때문에 매 번 Full Scan을 하게 되었다. 기억 상 CCU가 10,000명 정도 되었는데, 과장을 보태서 오픈 직후에 3초 마다 10,000*(10,000+1)/2번의 DB Scan을 할 가능성이 있었다는 것이다. (물론 접속중인 유저들이 모두 게임중이진 않았을 것이다)

최종적으로 user_idpass_setting_id column에 multiple-column index를 적용하여 해결할 수 있다. 두 column을 PK로 바꿔도 괜찮았겠지만, 이미 live 유저 데이터가 들어간 DB 스키마를 수정해서 리스크를 굳이 감수할 필요는 없고, 그 외 거의 모든 DB에도 id를 PK로 사용하기 때문에 일관성 유지의 이유도 없지는 않았던 것 같다.

여담

당시에는 서버 Latency를 모니터링하고 계셨던 매니저님께서 hotfix를 해주셨다. 다행히 선재적으로 조치가 되었지만, 그 이전에 치밀한 설계, 그리고 부하 테스트 등을 통한 검증이 필요하다는 것을 느낀다. 그리고 지금이 이러한 공부를 하기에 적기인 것 같다.

참고자료

https://dev.mysql.com/doc/refman/8.0/en/mysql-indexes.html