우리는 수업 과정을 통해 Database 라는 저장소를 배우게 되고, Table 이라는 데이터를 저장하기 위한 프레임을 배우게 됩니다.
보통 우리는 다양한 정보를 공통의 데이터 프레임을 가지는 하나의 테이블에 저장하게 되는데, 여기서 테이블에 대한 조회를 빠르게 하기 위해 사용되는 키워드가 바로 인덱스라고 합니다.
인덱스는 다른 말로 색인이라고 불리는데, 색인 과정을 통해 우리는 특정 데이터에 대한 조회를 빠르게 할 수 있는 특징을 가지고 있습니다.
인덱스의 개념
인덱스 = 색인 과 같은 의미로, DB의 Table에서 원하는 정보를 빠르게 조회하기 위해 사용합니다.
인덱스는 주로 SELECT 쿼리에서 사용하는데, DB 사용의 70%는 읽기인 만큼 인덱스는 조회 성능을 높이기 위해서 사용한다는 특징을 가지고 있습니다.
인덱스의 구조: B-Tree
인덱스는 기본적으로 이진 탐색 기반의 트리 자료구조를 활용한 알고리즘인 B-Tree 인덱스를 사용하는데,
B-Tree란, 데이터가 정렬된 상태를 유지하면서 효율적으로 삽입, 삭제, 검색이 가능한 자체 균형 트리(self-balancing tree)를 의미하고,
데이터가 저장되는 공간은 노드라고 부릅니다. 이 노드는 리프 노드, 중간 노드, 리프 노드 세 개로 구성되어 있습니다.
- 루트노드: 트리의 최상위 노드로, 데이터 탐색이 시작되는 지점을 의미
- 중간노드: 루트와 리프 노드 사이에 위치하며, 데이터를 범위로 구분하여 검색 경로를 최적화하는 특징
- 리프노드: 데이터를 실제로 저장하는 노드이고, 검색이 완료되는 지점
인덱스의 종류
인덱스에는 크게 테이블 당 하나만 존재하는 클러스터형 인덱스와 여러 개 설정이 가능한 보조 인덱스가 존재합니다.
클러스터형 인덱스
- 테이블의 데이터 자체를 정렬된 형태로 저장하는 인덱스
- 대부분의 DB에서 기본 키(Primary Key)를 클러스터형 인덱스로 자동 설정
- 데이터가 물리적으로 정렬된 상태로 저장되므로 검색 속도가 빠르다.
- 클러스터형 인덱스를 생성하면 테이블 데이터의 저장 순서가 바뀔 수 있다.
보조 인덱스
- 클러스터형 인덱스 외에 추가적으로 생성되는 인덱스
- 데이터는 정렬되지 않고 별도의 인덱스 페이지에 저장된다.
- 테이블에 여러 개의 보조 인덱스를 생성할 수 있다.
- 보조 인덱스는 데이터의 물리적 저장 순서에 영향을 주지 않는다.
- 우리가 흔히 사용하는 Unique Key와 복합 인덱스가 여기에 속한다.
인덱스와 카디널리티
카디널리티란, 데이터베이스에서 특정 열의 고유 값의 개수를 의미하는데 카디널리티는 인덱스 성능을 평가하는 데 중요한 지표로 사용됩니다.
예를 들어, 주민등록번호의 경우 고유하기 때문에 카디널리티가 높다고 판단되지만, 사람의 경우 70억 명의 인구 모두 지칭하기 때문에 카디널리티가 낮다고 판단됩니다.
인덱스의 카디널리티를 판단하기 위해서는 SHOW INDEX FROM your_table_name; 을 입력하게 되면 다음과 같은 카디널리티 상태를 확인할 수 있습니다.
복합 인덱스
복합 인덱스(Composite Index)는 두 개 이상의 열을 결합하여 생성된 인덱스로, 왼쪽에서부터 순차적으로 사용됩니다.
복합 인덱스의 순서는 꼭 지켜야할까?
만약 (order_id, order_status, create_at) 이렇게 인덱스를 지정한다고 할 때, 동작은 다음과 같습니다.
- (order_id, order_status, created_at) => 동작
- (order_status, created_at) = > 동작 안 함
- (order_id, order_status) => 동작
- (order_id, created_at) => 동작
순서가 꼭 중요한건 아니지만, 좌측의 첫 번째 요소를 꼭 가지고 있어야 동작함을 확인할 수 있습니다.
그렇다면, 여러 복합 인덱스가 있을 때는 어떻게 동작할까?
(order_id, price, create_at) 이렇게 인덱스를 지정하고, order_id와 created_at을 가진 쿼리를 다음과 같이 요청하게 되면
explain select * from payment
where order_id = 1 and created_at = '2024-12-05';
처음 인덱스가 호출되는데, 어떠한 과정을 거쳐서 첫 번째 인덱스가 호출이 되는걸까요?
- MySQL이 쿼리를 실행할 때 어떤 인덱스를 사용할지 결정하는 과정은 쿼리 옵티마이저(Query Optimizer)에 의해 이루어집니다.
- 옵티마이저는 카디널리티, 인덱스 설계 순서, 쿼리 조건, 인덱스 커버링 등을 기반으로 가장 효율적인 인덱스를 선택하게 됩니다.
인덱스와 성능
앞서 인덱스는 조회 성능을 높이기 위해 사용한다는 언급이 있었습니다.
그렇다면, 인덱스를 CREATE, UPDATE, DELETE 작업에 적용하게 되면 어떻게 될까요?
인덱스가 어떻게 구성이 되는지 앞에서 짧게 설명한 대로, 인덱스는 중간에 데이터가 삽입/삭제 될 경우 구조 재배치를 진행하게 되기 때문에 성능에 영향을 미치게 됩니다.
그렇다면, 갱신은 괜찮지 않을까?? 라는 생각을 해 봤는데, 보통 인덱스가 행 기반이 아닌 열 기반으로 조회하기 때문에 인덱스가 걸려있지 않은 컬럼을 수정하는 경우라면 성능에 문제를 일으키지는 않는다고 합니다.
인덱스에 대한 성능 분석
인덱스가 걸리기 전의 Query Plan
인덱스가 걸린 후의 Query Plan
대용량 데이터에서 성능 측정
대략 500만 개 정도의 데이터를 가지고 측정 진행
- 현업에서는 이보다 더 많은 데이터를 가지고 있어서 현재 측정한 시간보다 더 측정시간이 차이가 있겠지만, 스스로 유의미한 성능의 차이가 보이는 구간이라고 생각하여 이를 기준으로 측정 진행
- 인덱싱 컬럼은 user_id, 인덱싱이 걸리지 않은 컬럼은 product_id
인덱스가 걸리지 않은 product_id의 경우 풀스캔이 발생하여 매 번 2초 정도의 조회 시간이 발생
조회 건수가 몇 만 건일 경우, 대략 0.5초 이내
조회 건수가 단 건일 경우, 대략 0.004 초 이내
정리
기존에 인덱스에 대해 인지하고 있었지만, 직접 성능 테스트를 해 보지는 못 했습니다. 이번에 인덱스에 대해 제대로 공부할 기회가 생기게 되어 인덱스와 관련 개념들, 그리고 정말 인덱스가 성능에 영향을 미치는지 테스트를 진행해보게 되는 경험을 했네요.. ㅎㅎ