![[실전 DB 07] 인덱스를 만들어도 안 쓰일 때, 통계의 문제](https://img.thenullpage.com/posts/5644/5644_1_d7a169.webp)
인덱스를 정성껏 만들었는데 실행 계획을 보니 여전히 전체 스캔이었다. 분명 맞는 인덱스인데 왜 안 쓰일까. 이 답답한 상황의 흔한 원인이 통계 문제다. 데이터베이스가 데이터 분포를 모르면 잘못된 판단을 한다. 이번 편은 인덱스가 안 쓰이는 원인과 통계를 갱신하는 법을 다룬다.
이건 인덱스를 만들었다고 끝이 아니라는 걸 보여주는 대표적인 사례다. 만들고 확인하라는 원칙이 왜 중요한지가 여기서 드러난다.
1. 데이터베이스는 어떻게 인덱스 사용을 결정하나
데이터베이스는 쿼리를 받으면 여러 실행 방법 중 가장 빠를 것 같은 걸 고른다. 인덱스를 쓸지 전체를 스캔할지도 이때 판단한다. 그런데 이 판단은 데이터의 분포를 근거로 한다. 조건에 맞는 행이 전체의 몇 퍼센트인지 같은 통계를 보고, 인덱스가 유리한지 스캔이 유리한지 정한다.
여기서 문제가 생긴다. 데이터베이스가 데이터 분포를 정확히 모르면 잘못 판단한다. 통계가 없거나 낡으면, 인덱스를 쓰는 게 나은데도 스캔을 고르거나 그 반대를 할 수 있다. 인덱스는 잘 만들었는데 데이터베이스가 그걸 쓸 판단을 못 하는 것이다.
이게 초보를 당황하게 한다. 인덱스가 분명 맞는데 안 쓰이니, 인덱스 설계가 틀린 줄 알고 헤맨다. 그런데 원인은 인덱스가 아니라 통계다. 데이터베이스가 데이터를 몰라서 잘못 판단하는 것이다. 이 원인을 알면 해결이 간단하다.
인덱스를 만들어도 안 쓰이는 상황은 초보를 크게 당황하게 한다. 분명 맞는 인덱스인데 실행 계획은 여전히 스캔이니, 인덱스 설계가 틀린 줄 알고 헤맨다. 그런데 원인이 인덱스가 아니라 통계인 경우가 많다. 이 원인을 알면 해결이 간단하다. 인덱스는 그대로 두고 통계만 갱신하면 된다.
한 가지 더, 이 통계 문제는 특정 데이터베이스에서 특히 자주 겪는다. 통계를 자동으로 잘 관리하는 데이터베이스도 있지만, 그렇지 않으면 사용자가 직접 갱신해야 한다. 그래서 내가 쓰는 데이터베이스가 통계를 어떻게 관리하는지 알아두는 게 좋다. 자동이 아니면 통계 갱신을 정기 작업에 반드시 넣어야 한다.
2. 통계를 갱신하기
해결책은 통계를 갱신하는 것이다. 대부분의 데이터베이스는 데이터 분포를 분석해 통계를 만드는 명령을 제공한다. 흔히 ANALYZE 같은 명령이다. 이걸 실행하면 데이터베이스가 각 테이블과 인덱스의 데이터 분포를 다시 분석해 통계를 갱신한다.
통계를 갱신하면 데이터베이스가 정확한 판단을 하게 된다. 조건에 맞는 행이 얼마나 되는지 제대로 알게 되니, 인덱스가 유리한 상황에서 인덱스를 쓴다. 내가 인덱스를 만들고도 안 쓰여 헤맬 때, 통계 갱신 명령 한 번으로 실행 계획이 인덱스를 타는 검색으로 바뀌었다. 통계가 문제였던 것이다.
그래서 인덱스를 새로 만든 뒤엔 통계를 갱신하는 게 좋다. 새 인덱스에 대한 통계가 없으면 데이터베이스가 그 인덱스의 유용성을 판단하지 못한다. 인덱스를 만들고 통계를 갱신하고 실행 계획을 확인하는, 이 세 단계를 한 묶음으로 하는 것이다.
데이터베이스가 통계를 근거로 판단한다는 걸 이해해야 한다. 조건에 맞는 행이 전체의 몇 퍼센트인지 같은 통계를 보고, 인덱스가 유리한지 스캔이 유리한지 정한다. 통계가 없거나 낡으면 이 판단이 틀린다. 인덱스는 잘 만들었는데 데이터베이스가 그걸 쓸 판단을 못 하는 것이다. 그래서 통계 갱신이 필요하다.
3. 통계는 주기적으로 갱신한다
통계는 한 번 갱신하고 끝이 아니다. 데이터가 계속 쌓이고 바뀌면서 분포도 변한다. 처음엔 정확했던 통계가 시간이 지나면서 실제 분포와 어긋난다. 그래서 통계는 주기적으로 갱신해야 한다. 데이터가 많이 바뀐 뒤에는 통계도 다시 맞춰줘야 한다.
이걸 정기 작업으로 자동화하는 게 좋다. 앞선 서버리스 시리즈에서 다룬 정기 작업, 즉 크론으로 주기적으로 통계 갱신 명령을 돌리는 것이다. 한 달에 한 번 정도 통계를 갱신하면, 데이터가 늘어도 데이터베이스가 계속 정확한 판단을 한다. 사람이 매번 기억할 필요 없이 자동으로 유지된다.
다만 통계 갱신 자체도 데이터를 분석하는 작업이라 부담이 있다. 그래서 너무 자주 하기보다 적절한 주기로 한다. 그리고 데이터가 적을 때 트래픽이 한산한 시간에 돌리는 게 좋다. 앞서 다룬 정기 작업의 시간 관리 원칙이 여기에도 적용된다. 무거운 작업은 한산한 시간에 배치한다.
통계를 주기적으로 갱신하는 게 중요하다. 데이터가 쌓이며 분포가 변하니, 처음엔 정확했던 통계도 시간이 지나면 어긋난다. 그래서 정기 작업으로 통계 갱신을 자동화한다. 한 달에 한 번 정도 갱신하면 데이터가 늘어도 데이터베이스가 계속 정확한 판단을 한다. 사람이 매번 기억할 필요 없이 유지된다.
또 하나, 통계 갱신 자체도 부담이 있으니 주기를 적절히 잡는다. 데이터를 분석하는 작업이라 너무 자주 하면 그 자체가 부담이다. 데이터가 많이 바뀐 뒤에 하거나, 한산한 시간에 정기적으로 하는 게 좋다. 앞서 다룬 정기 작업의 시간 관리 원칙, 즉 무거운 작업은 한산한 시간에가 여기에도 적용된다.
4. 통계 외의 다른 원인들
인덱스가 안 쓰이는 원인이 통계만은 아니다. 쿼리 구조가 인덱스와 안 맞는 경우도 있다. 예를 들어 조건에서 컬럼을 가공하면, 예를 들어 컬럼에 연산을 하거나 함수를 씌우면, 그 컬럼의 인덱스를 못 쓴다. 인덱스는 원래 값 기준으로 정렬돼 있는데, 가공된 값은 그 정렬과 안 맞기 때문이다.
그래서 조건에서 컬럼을 최대한 가공하지 않고 원래 값 그대로 쓰는 게 좋다. 가공이 필요하면 그 가공을 반대편으로 옮기거나, 가공된 값에 대한 인덱스를 따로 만드는 방법도 있다. 이건 쿼리를 인덱스가 쓰이게 짜는 기술이다. 실행 계획으로 인덱스가 안 쓰이는 걸 발견하면, 이 쿼리 구조도 의심해봐야 한다.
또 데이터가 적을 때는 데이터베이스가 일부러 스캔을 고르기도 한다. 행이 몇 개 안 되면 인덱스를 거치는 것보다 그냥 전체를 훑는 게 빠를 수 있기 때문이다. 이건 문제가 아니라 올바른 판단이다. 데이터가 늘면 자연히 인덱스를 쓰게 되니, 데이터가 적을 때 인덱스가 안 쓰인다고 걱정할 필요는 없다.
통계 외의 원인도 알아두면 진단이 빨라진다. 쿼리에서 컬럼을 가공하면 그 컬럼의 인덱스를 못 쓴다. 인덱스는 원래 값 기준으로 정렬돼 있는데 가공된 값은 그 정렬과 안 맞기 때문이다. 그래서 조건에서 컬럼을 최대한 원래 값 그대로 쓰는 게 좋다. 실행 계획에서 인덱스가 안 쓰이면 이 쿼리 구조도 의심한다.
또 하나, 통계 문제는 데이터가 급격히 바뀐 직후에 특히 잘 생긴다. 대량으로 데이터를 넣거나 지운 뒤에는 통계가 실제와 크게 어긋나서, 데이터베이스가 엉뚱한 판단을 한다. 그래서 대량 작업을 한 뒤에는 통계를 갱신하는 걸 잊지 말아야 한다. 데이터가 크게 변했으면 통계도 다시 맞춰주는 것이다.
5. 만들고 확인하는 습관
이 편의 핵심 교훈은 인덱스를 만들고 반드시 확인하라는 것이다. 만들었다고 쓰이는 게 아니고, 통계나 쿼리 구조 때문에 안 쓰일 수 있다. 실행 계획으로 확인해서 안 쓰이면 통계를 갱신하거나 쿼리를 손보고, 그래도 안 되면 원인을 더 파고든다.
이 확인 습관이 없으면 안 쓰이는 인덱스를 만들어놓고 최적화했다고 착각한다. 인덱스는 있는데 실행 계획은 여전히 스캔이고, 읽기량은 안 줄어드는데 왜 그런지도 모른다. 만들고 확인하는 한 단계가 이 착각을 막는다. 인덱스 작업은 만들기 반, 확인하기 반이다.
정리하면 인덱스를 만들어도 통계가 없거나 낡으면 안 쓰일 수 있으니 통계를 갱신하고, 쿼리 구조가 인덱스와 맞는지 보고, 실행 계획으로 확인한다. 다음 편에서는 인덱스의 어두운 면, 즉 인덱스도 공짜가 아니라는 점과 쓰기 비용, 중복 인덱스 정리를 다룬다.
만들고 확인하는 습관이 이 편의 핵심 교훈이다. 인덱스를 만들었다고 쓰이는 게 아니라, 통계나 쿼리 구조 때문에 안 쓰일 수 있다. 실행 계획으로 확인해서 안 쓰이면 통계를 갱신하거나 쿼리를 손본다. 이 확인 없이 넘어가면 안 쓰이는 인덱스를 만들고 최적화했다 착각한다. 인덱스 작업은 만들기 반 확인하기 반이다.
만들고 확인하는 습관이 결국 이 시리즈 전체를 관통한다. 인덱스를 만들고 실행 계획으로 확인하고, 안 쓰이면 통계나 쿼리 구조를 손본다. 이 확인의 반복이 최적화를 착각이 아닌 실제로 만든다. 인덱스가 있는데 안 쓰여 읽기가 안 주는 답답한 상황은, 대부분 이 확인 단계를 건너뛰어서 생긴다.