모든 이야기

조회는 빨라지고 쓰기는 늘어난다: 인덱스의 비용

데이터베이스 인덱스는 테이블의 행을 조건에 맞게 빠르게 찾도록 돕는 별도의 접근 구조다. 책의 색인이 주제에서 페이지 위치로 안내하듯 인덱스는 키에서 해당 데이터로 가는 경로를 제공한다. 그러나 인덱스를 만들었다고 모든 질의가 빨라지거나 테이블 전체가 그 순서로 저장되는 것은 아니다.

이메일 인덱스를 통해 특정 행으로 접근하고 새 행을 넣을 때 테이블과 인덱스를 함께 갱신하는 그림

인덱스는 탐색 비용을 줄이는 대신 저장 공간과 갱신 비용을 추가한다.

훑기와 찾아가기의 차이

백만 명의 사용자 가운데 특정 이메일 하나를 찾는다고 하자. 인덱스가 없다면 많은 행을 검사할 수 있다. 이메일 인덱스가 있으면 비교적 적은 탐색으로 후보 위치를 찾고 해당 행을 읽을 수 있다. B 트리 계열은 정렬된 키를 이용해 등가 조건과 범위 조회를 지원하고, 해시 계열 등 다른 구조는 지원하는 조건과 장점이 다르다.

조건이 전체 행의 대부분을 반환한다면 인덱스 경유가 불리할 수도 있다. 인덱스에서 위치를 찾고 테이블의 여러 페이지를 다시 방문하는 비용이 크기 때문이다. 순차적으로 테이블을 읽는 방식이 더 저렴할 수 있다. 조건의 선택도, 데이터 분포, 메모리와 저장장치의 상태가 실행 계획에 영향을 준다.

복합 인덱스의 키 순서는 중요하다. (학과, 학번)으로 정렬된 구조는 먼저 학과별로 묶이고 그 안에서 학번 순서가 정해진다. 학과를 지정하고 학번 범위를 찾는 질의와 학번만으로 찾는 질의는 같은 방식으로 탐색하기 어렵다. 실제 시스템은 추가적인 접근 기법을 제공할 수 있으므로 앞 열이 없으면 무조건 전혀 못 쓴다는 식의 절대 규칙도 조심해야 한다.

데이터를 바꾸면 색인도 바뀐다

행을 삽입하거나 인덱스 키를 수정하면 관련 인덱스도 갱신해야 한다. 인덱스는 저장 공간, 캐시, 로그와 동시성 관리 비용을 사용한다. 읽기에 유용해 보이는 인덱스를 모든 열에 추가하면 쓰기 성능과 운영 비용이 커질 수 있다. 필요 없는 중복 인덱스를 남기는 것도 같은 문제를 만든다.

커버링 인덱스는 질의에 필요한 속성을 인덱스에 함께 넣어 테이블 접근을 줄이는 데 도움을 준다. 다만 인덱스만 읽는 실행이 항상 가능한 것은 아니다. MVCC를 사용하는 시스템에서는 행 가시성 확인 때문에 테이블 접근이 필요할 수 있고, 포함 열이 많으면 인덱스 크기와 쓰기 비용도 증가한다.

부분 인덱스는 특정 조건을 만족하는 행만 포함하고, 표현식 인덱스는 계산된 값에 대한 탐색을 지원한다. 이런 선택은 실제 쿼리의 조건과 맞아야 한다. 이메일을 소문자로 비교하는데 원래 값만의 인덱스가 모든 경우에 같은 도움을 주리라고 기대해서는 안 된다. 데이터의 비교 규칙과 정렬 규칙도 인덱스 의미에 영향을 준다.

인덱스를 평가할 때는 실행 계획과 실제 반환 행 수, 읽은 페이지 수, 쓰기 지연을 함께 확인하는 것이 좋다. 한 건만 있는 작은 테스트 테이블에서는 차이가 드러나지 않고, 실제로 값이 한쪽에 몰린 테이블에서는 균일 분포를 가정한 예측이 틀릴 수 있다. 통계가 오래되면 최적화기가 잘못된 경로를 고를 가능성도 있다.

유일 인덱스나 키 제약은 성능과 무결성의 역할이 함께 보일 수 있으나 둘을 같은 개념으로만 보지는 않아야 한다. 좋은 인덱스 설계는 어떤 조건을 얼마나 자주 읽는지, 얼마나 자주 바꾸는지, 어느 비용을 줄이고 어느 비용을 늘리는지를 설명할 수 있어야 한다. 실제 작업을 기준으로 만든 적당한 색인이 무작정 많은 색인보다 유용하다.

참고 자료