05. 인덱스

디스크 읽기 방식

  • 랜덤 I/O(Random I/O)
    • 데이터를 비연속적인(물리적으로 떨어진) 위치에서 임의로 읽거나 쓰는 작업이다.
    • 디스크 헤드가 여러 위치로 이동해야 하므로, 작업당 seek time(탐색 시간)·latency가 크게 증가한다.
    • 인덱스 기반 탐색, WHERE 조건 조회, 특정 레코드 단건 접근, 임의 위치 갱신·삭제 등에서 주로 발생한다.
    • HDD에서는 랜덤 I/O가 매우 느리며, SSD는 그 차이가 적지만 throughput(처리량)은 여전히 낮은 편이다.
      • ex) 인덱스 레인지 스캔, 키 값 하나씩 랜덤 조회 등.
  • 순차 I/O(Sequential I/O)
    • 물리적으로 연속된(붙은) 위치에서 데이터를 순서대로 읽거나 쓴다.
    • 디스크 헤드를 거의 이동시키지 않고, 한 번 seek한 뒤 연달아 데이터를 읽거나 쓸 수 있다.
    • 전체 테이블 스캔, 대량 정렬·그룹화, 연속 블록 덤프 등에서 발생.
    • 대용량 데이터 일괄 처리에 유리하며, HDD·SSD 모두에서 랜덤 I/O보다 월등히 빠르다.
    • ex) 풀 테이블 스캔(모든 로우 직접 읽기), 파일 연속 저장 등.
  • 성능 개선 관점
    • DB 쿼리 성능을 높이려면 랜덤 I/O를 최소화하고, 순차 I/O가 많이 발생하도록 데이터 구조·쿼리를 최적화하는 것이 바람직하다.
    • 이는 꼭 필요한 데이터만 읽고 쓰도록 설계하거나, 액세스 패턴을 연속적으로 유지하는 방식으로 이뤄질 수 있다.

인덱스란?

  • 모든 데이터를 뒤져서 원하는 결과를 가져오는 데에 시간이 걸리기 때문에 컬럼과 레코드가 저장된 위치를 key-value 관리
    • DBMS의 인덱스도 컬럼의 값을 주어진 순서로 미리 정렬해서 가지고 있다.
    • SortedList: DBMS의 인덱스와 동일한 자료 구조로 정렬된 상태 유지
      • 저장하는 과정은 복잡하고 느리지만 원하는 값을 빠르게 찾을 수 있다.
    • ArrayList: 데이터 파일과 동일한 자료 구조 사용로 저장된 순서 그대로 유지
  • 인덱스는 데이터 저장 방식에 따라 분류할 수 있으며 대표적으로 B-Tree 인덱스, Hash 인덱스가 있다
  • 데이터 중복 여부에 따라 unique index, non-unique index로 구분할 수 있다

MongoDB 인덱스의 개요

  • 클러스터링 인덱스
    • 클러스터링 인덱스는 기본 키 값을 기준으로 데이터가 물리적으로 정렬 저장되는 인덱스 방식이다.
    • MongoDB 인덱스 구조와 차이점
      • MongoDB는 모든 인덱스를 B-Tree 기반으로 구현하며, 인덱스는 키와 도큐먼트 위치(논리적 주소)를 별도로 저장한다.
      • 도큐먼트 저장 위치와 인덱스는 분리되어 있다.
        • 도큐먼트가 물리적으로 정렬되는 구조가 아니고, 인덱스가 클러스터된 데이터를 포함하지 않는다.
      • 따라서 기본 키(_id) 인덱스도 단순 유니크 인덱스일 뿐, 클러스터링과 같은 기능은 하지 않는다.
    • 지원하지 않는 이유
      • MongoDB는 도큐먼트 지향 DB로, 각 도큐먼트는 BSON 형태로 저장되고, 크기와 구조가 가변적이어서 물리적 정렬 저장이 어렵다.
      • 도큐먼트 크기 변동과 메모리 조각화 현상 때문에 클러스터링 인덱스처럼 물리적 위치 재정렬을 실시간으로 유지하는 게 비효율적이다.
      • 대신 인덱스는 빠른 탐색을 위한 별도의 구조로 유지하고, 도큐먼트는 자유롭게 분산·저장한다.
  • 인덱스 내부(WiredTiger 스토리지 엔진 기준)
    • B+Tree 기반: WiredTiger 인덱스는 B+Tree 자료구조를 사용
      • 이는 계층적 구조(루트 노드, 내부 노드, 리프 노드)로 구성된다. B+Tree는 빠른 탐색, 삽입, 삭제를 지원한다.
    • 레코드 ID: 인덱스 키 엔트리는 각 도큐먼트에 고유 식별자인 Record-Id를 할당한다.
      • Record-Id는 64비트 정수로 컬렉션 단위로 별도 자동 증가 값을 사용하며, 도큐먼트 위치와 논리적으로 연결된다.
    • MVCC 지원: WiredTiger는 다중 버전 동시성 제어(MVCC)를 지원
      • 인덱스는 여러 버전을 관리하며, 읽기 작업은 특정 시점의 일관된 버전을 참조하고, 쓰기 작업은 새 버전을 생성한다.
    • 저널링 및 체크포인트: 인덱스 구조 변경(노드 분할, 병합 등)은 원자적으로 이루어져야 하며, 저널 로그를 통해 내구성이 보장된다.
      • 변경된 인덱스는 메모리에서 빠르게 처리 후 체크포인트 시 디스크에 반영되어 랜덤 I/O를 순차 I/O로 변환해 성능을 향상시킨다.
    • 압축: 인덱스는 기본적으로 프리픽스 압축(prefix compression) 지원
      • 인덱스 키들의 공통 접두사 부분을 압축해 저장 공간을 절감한다.
  • 로컬 인덱스
    • 샤딩 클러스터 환경에서 각 샤드가 자신이 저장하고 있는 도큐먼트에 대해서만 관리하는 인덱스를 의미한다.
    • 특징
      • 각 샤드는 자신의 데이터에 대해 별도로 인덱스를 생성·관리한다.
      • 인덱스가 저장하고 있는 도큐먼트들은 해당 샤드 내에 물리적으로 존재하는 데이터에 한정된다.
      • 애플리케이션 레벨에서 전역 유니크성을 보장해야 한다.
        • 샤딩 환경에서 유니크 인덱스나 프라이머리 키 인덱스가 있다면 반드시 샤드 키를 포함하는 등의 방법 필요
    • 의미
      • 샤딩된 컬렉션은 데이터가 여러 샤드에 분산 저장되므로, 인덱스 역시 각 샤드별로 로컬하게 존재하고 관리된다.
      • 샤드간의 인덱스 통합 구조가 아니라 각자 독립적인 인덱스를 갖는다.
    • 제한 사항
      • 전역 유니크 인덱스는 기본적으로 지원하지 않으며, 이를 위해 샤드 키와 연계하거나 외부에서 중복 체크를 해야 한다.
      • 인덱스가 많아질수록 밸런싱 속도와 시스템 부하에 영향을 줄 수 있다
  • 인덱스 키 엔트리 자료구조
    • 키 값(Key): 인덱스를 생성할 때 지정한 필드의 값이 인덱스 키로 사용되며 BSON 타입으로 복합 인덱스인 경우 여러 필드가 조합되어 하나의 키를 형성
    • Record-Id (도큐먼트 위치 정보): 각 인덱스 키와 연결된 도큐먼트의 저장 위치를 의미한다.
      • MongoDB 내부적으로는 Record-Id로 관리되며, 도큐먼트의 논리적 위치 혹은 물리적 위치를 가리킨다.
    • 노드 종류
      • 브랜치 노드: 하위 노드들을 가리키는 인덱스 엔트리들을 포함한다.
      • 리프 노드: 실제 키와 Record-Id 쌍으로 구성되어, 도큐먼트 바로 접근이 가능하다.
    • 정렬: 키 값들은 B-Tree 특성상 정렬되어 저장되어 있어 빠른 탐색과 범위 쿼리가 가능하다.
    • 멀티 키 인덱스: 배열 필드를 인덱스할 때 하나의 도큐먼트에 여러 키 엔트리가 생성될 수 있다.
    • 동작 및 특징: 인덱스는 빠른 검색을 위해 키 값에 따라 정렬과 트리 탐색 구조를 유지한다.
      • Record-Id를 통해 인덱스에서 도큐먼트로 빠르게 접근 가능하며, 추가/삭제 시 B-Tree 노드가 분할/병합된다.
      • BSON 기반의 가변 길이 키 값을 처리하기 때문에 인덱스 키 엔트리 크기가 달라질 수 있다.

B-Tree 인덱스

Hash 인덱스

멀티 키 인덱스

인덱스 속성