최근 회사에서 DB 쿼리 수정 작업을 진행하면서 DB 최적화의 중요성을 느꼈다. 거창하게 쿼리문에서 특별한 함수를 사용해서 획기적으로 수행시간을 줄인다거나 메모리 사용량을 줄이는 작업은 아니었다. 하지만 사소한 부분을 캐치하여 수정하는 것만으로도 DB의 성능을 어느 정도 끌어올릴 수 있다는 것을 알 수 있었다.
그동안 원하는 데이터만 잘 받아오면 되지라는 생각으로 쿼리를 짰던 것에 대해서 다시 생각해 보는 계기가 되었고 따라서 이 글을 남기게 되었다.
사실 그동안 DB 쿼리를 짜면서 성능에 그렇게까지 신경을 쓰지 않았던 것 어차피 데이터 자체가 적어서 어떤 식으로 쿼리를 짜도 딱히 성능의 차이를 느끼지 못 했던 점도 있는 것 같다. DB에 데이터가 정말 많아봐야 몇 만개 정도였고 테이블도 10개 이내였기 때문에 정말 최악의 효율로 쿼리를 작성하지 않는한 사용에 문제가 없었다.
하지만 현업에서는 그렇지 않았다. 내가 다니고 있는 회사에서는 수십개의 테이블을 가지고 있고 몇몇 테이블에는 데이터 개수가 1,000만개 가까이된다. 이런 상황에서 내가 토이프로젝트를 할 때처럼 DB 쿼리문을 작성한다면 사용자가 응답을 받기까지는 엄청난 시간이 걸릴 것이다.
사용자가 개발자들의 상황을 이해하고 한없이 기다려준다면 정말 좋겠지만 현실은 그렇지 않다. 같은 팀의 사수분께 물어보니 응답 시간이 1초가 넘어가면 사용자들이 불편함을 느끼기 시작한다고 한다. 생각해 보니 나 역시도 웹서핑 중에 응답이 바로바로 나오지 않으면 불편함을 느꼈던 것 같다. 그러니 만약 사용자 페이지를 개발하게 된다면 응답 시간을 꼭 체크해야 할 것이다.
위에서 약 600자의 분량을 할애하여 최적화의 중요성에 대해 설명하였으니 이제 어떻게 최적화를 해야하는 지에 대해 이야기해 볼 것이다. 이 내용은 아직 2개월밖에 되지 않은 신입의 입장에서 작성된 내용이라는 것을 감안하고 봐주시길 바란다.
인덱스는 DB 쿼리 수행 시간을 줄이는 데 아주 핵심적인 역할을 한다. 인덱스가 무엇인지에 대해 간단히 설명하면 컬럼의 정보를 다른 곳에 저장하여 놓고 해당 컬럼을 사용할 때 저장된 정보를 통해 빠르게 scan을 하는 것이다.
매번 해당 컬럼을 모두 scan하는 것보다 훨씬 좋은 성능을 내기때문에 이를 잘 활용하면 DB 쿼리 수행 시간을 크게 줄일 수 있다.
위에서 설명했듯이 인덱스 컬럼이 DB 쿼리 수행 시간을 크게 줄여주기는 하지만 주의해야 할 점도 몇 가지 있다. 그중에 내가 아는 것을 몇 가지 설명해 보면
1. 인덱스는 해당 컬럼에 대한 내용을 따로 저장해두는 것이기 때문에 저장 공간을 소모한다. 따라서 아무렇게나 인덱스를 걸어두면 저장 공간 효율이 매우 떨어질 것이다.
2. 인덱스 컬럼에 함수를 사용하면 인덱스를 활용하지 못한다. WHERE 문이나 ORDER BY 문에 인덱스 컬럼을 활용하는 과정에서 함수를 사용하면 안 된다.
예를 들어 계정 생성일을 저장하는 날짜 형식의 regist_date라는 컬럼이 있고 2024년 10월에 가입한 계정들을 조회하려 한다. 이때 regist_date는 PK로 설정하였을 경우(실제로 이렇게 하면 안 될 것 같지만 테스트니 그냥 그러려니 해주기를 바란다.)
위와 같이 regist_time을 그대로 사용할 경우 KEY를 사용하여 탐색할 수 있다.
하지만 아래와 같이 regist_time에 함수를 적용할 경우 KEY를 사용하지 못한다.
3.like 절에서 첫 자리에 와일드카드를 사용할 시 INDEX 적용이 되지 않는다. 예를 들어 유저 ID를 검색하려고 할 때
아래와 같이 뒷자리에 'ser1'이 들어가는 유저를 찾을 경우 KEY를 사용하지 못한다.
하지만 아래와 같이 'us'로 시작하는 유저를 찾을 경우 KEY를 사용할 수 있다.
내가 아는 내용은 대략적으로 이정도이며 인덱스 컬럼을 사용할 때는 위의 내용을 꼭 기억하고 있기를 바란다.
쿼리의 실행 순서도 DB의 수행 시간에 영향을 준다. 코딩테스트를 준비하면서 알고리즘 문제를 풀어본 사람들은 알 것이다. 똑같은 작업도 어떤 로직을 활용하느냐에 따라 수행시간이 천차만별이다. DB 쿼리도 똑같다. 비효율적인 로직과 상대적으로 좀 더 효율적인 로직이 존재한다.
일단 쿼리문이 실행되는 순서는 (FROM or JOIN)-WHERE-GROUP BY-HAVING-SELECT-ORDER BY이다. 앞에서부터 최대한 거를 수 있는 데이터는 걸러가며 작업하는 것이 좋을 것이다.
이중 내가 중요하다고 느꼈던 부분은 WHERE 부분이다. WHERE 절에서 단지 검색 순서를 변경했을 뿐인데 쿼리 성능에 차이가 난다. WHERE에서도 탐색 데이터의 범위를 많이 좁힐 수 있거나 빠르게 좁힐 수 있는 탐색부터 수행하는 것이 성능에 도움이 된다.
내가 수정했던 쿼리문 중 하나의 예를 들어보겠다. 어떤 기간에 특정 페이지의 방문 횟수를 구하는 쿼리였는데 그 쿼리에서 사용되는 테이블에는 날짜 컬럼에는 인덱스가 없었고 페이지 정보에는 인덱스가 있었다. 나는 이 쿼리에서 날짜를 먼저 검색한 후 페이지를 검색하는 방식을 페이지를 먼저 검색한 후 날짜를 검색하는 방식으로 변경하였고 이를 통해 수행 시간을 줄일 수 있었다.
인덱스가 존재하는 페이지 정보 컬럼을 이용해 빠르게 원하는 페이지의 데이터만을 가져오고 그 데이터 안에서 날짜를 찾아냈기 때문에 모든 데이터를 Full-scan하여 해당 기간에 맞는 데이터를 찾고 그 안에서 페이지 정보를 찾는 방법보다 빠르게 처리할 수 있었다.
이렇게 내가 느낀 DB 쿼리 최적화의 중요성과 내가 아는 간단한 규칙들을 설명해 보았다. 아마 현업에 종사 중인 분들이라면 여기에 나온 내용들은 모두 알 것이고 나보다 훨씬 잘 알 것이다. 난 이번에 글을 쓰면서 신입과 개발자 지망생이 이런 내용을 알면 좋을 것 같다고 생각하고 쓴 글이므로 그분들에게 도움이 아주 약간이라도 되면 이 글을 쓴 이유를 만족시킬 수 있을 것 같다.