[실전 DB 02] 측정부터, 어느 쿼리가 무거운가

데이터베이스를 최적화하려는데 어디부터 손대야 할지 막막했다. 쿼리가 수십 개인데 어느 게 문제인지 감으로는 알 수 없었다. 그때 배운 게 측정의 중요성이다. 어느 쿼리가 얼마나 많이 훑는지 재보지 않으면 최적화는 찍기에 불과하다. 이번 편은 무거운 쿼리를 찾아내는 측정 방법을 다룬다.


앞선 시리즈들에서 반복한 감이 아니라 데이터라는 원칙이 데이터베이스에서도 그대로다. 최적화는 계기판을 읽는 데서 시작한다.

1. 왜 측정이 먼저인가

측정 없이 최적화하면 엉뚱한 곳을 고친다. 느릴 것 같은 쿼리를 짐작해 손보는데, 정작 진짜 병목은 다른 데 있는 경우가 많다. 내가 문제라고 생각한 쿼리는 사실 별로 안 무거웠고, 무심코 넘긴 목록 쿼리가 읽기의 대부분을 차지하고 있었다. 짐작과 실제는 자주 다르다.


그래서 최적화의 첫걸음은 어느 쿼리가 얼마나 자원을 쓰는지 재는 것이다. 각 쿼리가 몇 번 실행되고 매번 몇 행을 훑는지 알면, 그 둘을 곱한 값이 그 쿼리의 총 부담이다. 이 총 부담이 큰 쿼리부터 손보는 게 순서다. 자주 실행되면서 많이 훑는 쿼리가 최우선 타깃이다.


이 우선순위가 중요하다. 아무리 무거운 쿼리라도 하루에 한 번 실행되면 총 부담은 작고, 가벼운 쿼리라도 초당 수십 번 실행되면 총 부담이 크다. 그래서 한 번의 무게가 아니라 총 부담으로 봐야 한다. 실행 빈도와 회당 스캔 행수를 함께 봐야 진짜 주범이 드러난다.


측정이 먼저라는 게 최적화의 제1원칙이다. 짐작으로 손대면 엉뚱한 데를 고치느라 시간을 날린다. 내가 문제라 확신했던 쿼리가 실은 가벼웠고, 무심코 넘긴 쿼리가 진짜 주범이었다. 이 경험 이후로는 무엇을 고치기 전에 반드시 먼저 잰다. 재보면 짐작과 다른 경우가 놀랄 만큼 많다.


조금 더 배경을 주면, 측정을 건너뛰고 싶은 유혹이 크다. 코드를 보면 이 쿼리가 느릴 것 같다는 직감이 들기 때문이다. 하지만 그 직감은 자주 틀린다. 데이터베이스는 데이터 분포와 인덱스 상태에 따라 예상과 다르게 동작한다. 그래서 직감을 믿지 말고 재봐야 한다. 재는 데 드는 몇 분이 헛수고 며칠을 아낀다.


한 가지 더, 측정은 최적화만이 아니라 사고 감지에도 쓰인다. 평소 읽기량을 알아두면, 어느 날 갑자기 튀었을 때 바로 이상을 알아챈다. 새로 배포한 코드에 비효율이 생겼거나 봇 트래픽이 몰렸을 수 있다. 정상 범위를 알고 감시하면, 비용 폭탄이 커지기 전에 초기에 잡는다. 측정은 진단이자 경보 장치다.

2. 실행 계획으로 한 쿼리를 뜯어보기

개별 쿼리가 얼마나 훑는지는 실행 계획으로 본다. 대부분의 데이터베이스는 쿼리가 실제로 어떻게 실행되는지를 보여주는 기능이 있다. 이 쿼리가 인덱스를 타는지 전체를 훑는지, 정렬을 위해 임시 작업을 하는지가 여기 나온다. 쿼리 앞에 실행 계획 조회 명령을 붙이면 된다.


실행 계획에서 봐야 할 핵심은 스캔이냐 검색이냐다. 전체를 훑는 스캔이 나오면 그 쿼리는 큰 테이블에서 비싸다. 반면 인덱스로 바로 찾아가는 검색이 나오면 효율적이다. 실행 계획 한 줄만 봐도 이 쿼리가 무거운지 가벼운지 판단할 수 있다. 이건 다음 편에서 더 자세히 다룬다.


실행 계획을 보는 습관을 들이면, 쿼리를 짤 때부터 이게 인덱스를 탈지를 의식하게 된다. 새 쿼리를 만들면 실행 계획을 확인해, 의도치 않게 전체를 훑고 있지 않은지 점검한다. 배포 전에 이 확인을 거치면 비효율적인 쿼리가 서비스에 들어가는 걸 막는다.


실행 계획으로 개별 쿼리를 뜯어보는 습관이 큰 자산이 된다. 쿼리 하나를 실행 계획으로 보면, 그게 인덱스를 타는지 전체를 훑는지 한눈에 보인다. 새 쿼리를 짤 때마다 이걸 확인하면, 비효율적인 쿼리가 서비스에 들어가기 전에 걸러진다. 배포 전 이 짧은 확인이 나중의 큰 사고를 막는다.


실행 계획을 읽는 습관이 생기면, 쿼리를 짜는 방식 자체가 달라진다. 이 쿼리가 인덱스를 탈까를 미리 상상하며 짜게 되는 것이다. 처음엔 짜고 나서 확인하지만, 익숙해지면 짜면서 이미 실행 계획을 예상한다. 이 감각이 비효율적인 쿼리를 애초에 안 만들게 해준다.

3. 전체 관점의 측정 도구

개별 쿼리를 뜯어보는 것과 별개로, 사이트 전체에서 어느 쿼리가 얼마나 자원을 쓰는지 보는 도구도 있다. 대부분의 데이터베이스 플랫폼은 쿼리별 실행 횟수와 읽기량을 집계해 보여주는 분석 기능을 제공한다. 여기서 가장 많이 읽는 쿼리 순위를 볼 수 있다.


내가 읽기 폭증을 잡을 때도 이 도구가 결정적이었다. 어느 쿼리가 하루에 몇 번 실행되고 총 몇 행을 읽는지 순위로 보여주니, 상위 몇 개만 손봐도 큰 효과가 나는 게 분명했다. 상위 몇 개가 전체 읽기의 대부분을 차지하는 경우가 많다. 그 소수에 집중하면 효율적으로 최적화할 수 있다.


이 전체 관점과 개별 쿼리 관점을 함께 쓴다. 전체 관점으로 어느 쿼리가 주범인지 찾고, 개별 관점으로 그 쿼리가 왜 무거운지 실행 계획을 뜯어본다. 그러면 무엇을 왜 고쳐야 하는지가 명확해진다. 큰 그림과 세부를 오가며 진단하는 것이다.


전체 관점의 분석 도구가 진단을 효율적으로 만든다. 수십 개 쿼리를 하나씩 볼 수는 없으니, 어느 쿼리가 총 얼마나 읽는지 순위로 보여주는 도구로 주범을 좁힌다. 대개 상위 몇 개가 전체 읽기의 대부분을 차지해서, 그 소수만 손봐도 큰 효과가 난다. 전체를 다 고칠 필요 없이 핵심만 치는 것이다.


조금 더 실무적으로, 분석 도구로 주범을 찾을 때 상위 몇 개에 집중하는 게 효율적이다. 읽기량의 대부분이 소수의 쿼리에 몰려 있는 경우가 많아서, 그 소수만 손봐도 전체가 크게 준다. 모든 쿼리를 완벽하게 만들려 하기보다, 가장 무거운 것부터 순서대로 치는 게 시간 대비 효과가 크다.

4. 측정 시점과 조건

측정할 때 주의할 점이 있다. 측정 시점과 조건을 일정하게 유지해야 한다. 트래픽이 시간대마다 다르니, 어제 이 시간과 오늘 이 시간을 비교하는 식으로 조건을 맞춘다. 그래야 최적화 전후의 차이가 트래픽 변화가 아니라 최적화 효과인지 분간할 수 있다.


또 측정에는 시차가 있을 수 있다. 방금 배포한 최적화의 효과가 지표에 바로 안 나타나고 몇 시간 뒤에 반영되기도 한다. 그래서 최적화하고 바로 지표를 보고 실망하기보다, 하루 정도 지켜보며 추세를 봐야 한다. 이건 앞선 시리즈의 검색 지표를 볼 때와 같은 인내다.


그리고 측정값에 내가 개발하며 날린 쿼리도 섞일 수 있다는 걸 감안한다. 로컬 테스트나 관리 작업으로 실행한 쿼리가 지표에 잡히면, 실제 서비스 부담을 부풀려 보이게 한다. 그래서 순수한 서비스 트래픽 기준으로 보려면 이런 잡음을 걸러내거나 감안해야 한다.


측정 조건을 일정하게 유지하는 게 정확한 비교의 조건이다. 트래픽이 시간대마다 다르니, 최적화 전후를 같은 시간대끼리 비교해야 효과가 트래픽 변화인지 최적화 덕인지 구분된다. 그리고 지표에는 시차가 있어서, 방금 배포한 효과가 몇 시간 뒤에 나타나기도 한다. 조급하게 판단하지 말고 추세를 봐야 한다.

5. 측정이 최적화의 나침반이다

측정을 습관으로 만들면 최적화가 막막하지 않다. 어디가 문제인지 데이터가 알려주니, 그 순위 상위부터 하나씩 손보면 된다. 고친 뒤엔 다시 측정해 효과를 확인한다. 이 측정과 개선의 반복이 최적화의 기본 사이클이다.


이 사이클이 없으면 최적화는 밑 빠진 독이 된다. 어디가 새는지 모른 채 여기저기 막다 지친다. 반면 측정으로 새는 곳을 정확히 찾으면, 적은 노력으로 큰 효과를 낸다. 상위 몇 개 쿼리만 고쳐도 읽기가 극적으로 줄었던 게 그 증거다. 측정이 노력을 집중시키는 나침반이 되는 것이다.


정리하면 데이터베이스 최적화는 측정에서 시작한다. 실행 계획으로 개별 쿼리를 뜯어보고, 분석 도구로 전체에서 주범을 찾고, 조건을 맞춰 재고, 고친 뒤 다시 잰다. 다음 편에서는 무거운 쿼리를 고치는 가장 강력한 무기, 인덱스의 원리를 다룬다. 색인이 왜 곧 속도이자 비용인지를 파고든다.


측정이 나침반이라는 게 이 편의 결론이다. 어디가 새는지 데이터가 알려주니, 그 순위 상위부터 고치고 다시 재서 효과를 확인한다. 이 측정과 개선의 반복이 최적화의 기본 사이클이다. 측정 없이 여기저기 막으면 지치지만, 측정으로 새는 곳을 정확히 찾으면 적은 노력으로 큰 효과를 낸다.


측정을 습관으로 만드는 게 이 편의 진짜 메시지다. 한 번 재고 끝이 아니라, 최적화의 매 단계마다 재는 것이다. 고치기 전에 재서 기준을 잡고, 고친 뒤 재서 효과를 확인한다. 이 반복이 쌓이면 데이터베이스가 어떻게 동작하는지에 대한 감각이 는다. 측정은 도구이자, 데이터베이스를 이해하는 학습법이기도 하다.