PostgreSQL 인덱스 소개

2 weeks ago 9

  • PostgreSQL 인덱스는 데이터 접근 속도를 높이기 위한 핵심 구조로, 디스크에서 읽어야 하는 데이터 양을 줄여 쿼리 성능을 향상시킴
  • 인덱스는 Btree, Hash, BRIN, GIN, GiST, SP-GiST 등 다양한 형태로 제공되며, 각기 다른 데이터 특성과 쿼리 패턴에 최적화됨
  • 인덱스는 디스크 공간, 쓰기 성능, 쿼리 플래너 복잡도, 메모리 사용량 등 여러 비용을 수반함
  • 부분 인덱스, 다중 컬럼 인덱스, 커버링 인덱스, 표현식 인덱스 등 고급 기능을 통해 특정 상황에서 효율성을 극대화할 수 있음
  • 적절한 인덱스 선택과 관리가 PostgreSQL 성능 최적화의 핵심 요소로 강조됨

인덱스의 기본 개념

  • 인덱스는 데이터베이스가 디스크에서 읽는 데이터 양을 줄여 쿼리 속도를 높이는 구조
    • 기본 키, 유니크 키, 배타 제약 조건 등도 인덱스를 통해 구현
    • 쿼리 결과가 전체 테이블의 15~20% 미만일 때 인덱스가 효과적이며, 그 이상이면 순차 스캔이 더 효율적일 수 있음
  • PostgreSQL은 기본적으로 6가지 인덱스 유형을 제공하며, 확장을 통해 더 많은 유형을 사용할 수 있음
    • 각 인덱스는 키 값과 해당 데이터 위치(TID)를 연결

디스크에 저장되는 데이터 구조

  • PostgreSQL의 테이블은 힙(heap) 파일로 저장되며, 8KB 페이지 단위로 구성
  • 각 행(tuple)은 특정 순서 없이 저장되고, 내부 주소는 ctid (current tuple id) 로 식별됨
    • 예: (0,1)은 페이지 0의 첫 번째 튜플을 의미
  • 인덱스는 이러한 힙의 위치(ctid)를 트리 구조로 연결하여 빠른 검색을 지원

인덱스가 데이터 접근을 가속화하는 방식

  • 인덱스가 없는 경우, PostgreSQL은 모든 페이지를 읽는 순차 스캔을 수행
    • 예시 쿼리에서 name='Ronaldo'를 찾을 때 6272개의 페이지를 읽고 265ms 소요
  • 인덱스를 추가하면 Index Scan으로 전환되어 4개의 페이지만 읽고 0.077ms에 완료
    • 인덱스는 값과 ctid를 매핑하여 필요한 행만 빠르게 찾음
  • 인덱스 파일 크기는 테이블 크기와 유사할 수 있음 (예: 30MB 테이블 → 30MB 인덱스)

인덱스의 비용 요소

  • 인덱스는 성능 향상 외에도 여러 부담 요소를 동반

디스크 공간

  • 인덱스는 별도의 저장 공간을 차지하며, 테이블보다 더 클 수 있음
    • 백업, 복제, 장애 복구 시 추가 비용 발생
    • 부분 인덱스, 다중 컬럼 인덱스, BRIN 등을 통해 공간 효율 개선 가능

쓰기 작업

  • UPDATE, INSERT, DELETE 시 인덱스가 포함된 컬럼이 변경되면 인덱스 갱신 오버헤드 발생

쿼리 플래너

  • 인덱스가 많을수록 플래너가 고려해야 할 옵션이 증가하여 쿼리 계획 수립 시간이 늘어남

메모리 사용량

  • 인덱스 페이지는 shared buffer에 적재되어 캐시되며, 인덱스가 많을수록 메모리 부담 증가
  • btree 노드 크기 제한으로 인해 컬럼이 클수록 트리 깊이가 증가
  • 정렬, 다중 컬럼 스캔, vacuum, reindex 등에서도 work memory가 추가로 사용됨

인덱스의 주요 유형

Btree

  • PostgreSQL의 기본 인덱스 구조로, 대부분의 DBMS에서 사용되는 범용 인덱스
    • O(log n) 시간 복잡도로 빠른 검색 지원
    • 모든 리프 노드가 동일한 깊이를 가지는 균형 트리 구조
    • ORDER BY, JOIN 연산에 유리하며, 기본키·유니크키 제약에 사용
  • 내부 노드는 하위 노드 포인터를, 리프 노드는 키와 힙 포인터를 저장
  • 왼쪽·오른쪽 노드 포인터를 통해 양방향 탐색 가능
다중 인덱스 사용
  • PostgreSQL은 여러 인덱스를 비트맵 AND/OR 연산으로 결합해 복합 조건을 처리
    • 예: age=30 AND login_count=100 조건 시 두 인덱스의 비트맵을 결합
다중 컬럼 인덱스
  • 여러 컬럼을 하나의 인덱스로 묶어 공간 절약과 속도 향상 가능
    • 단, 컬럼 순서가 중요하며 왼쪽부터 일치하는 조건만 인덱스 사용 가능
부분 인덱스
  • 조건식을 사용해 특정 행만 인덱싱
    • 인덱스 크기 축소, RAM 적합성 향상, 조회 속도 개선
    • 예: create index on rules(status) where status='enabled';
    • 값 분포가 불균형할 때 유용 (status <> 'TODO' 등)
커버링 인덱스
  • 쿼리에서 필요한 모든 컬럼이 인덱스에 포함되면 heap 접근 없이 결과 반환(index-only scan) 가능
    • create index abc_cov_idx on bar(a, b) including c;
    • 다중 컬럼 인덱스보다 공간 효율적
표현식 인덱스
  • 컬럼 값이 아닌 함수나 표현식 결과를 인덱싱
    • 예: CREATE INDEX idx_lower_name ON customers (lower(name));
    • LOWER(name) 같은 변환된 값으로 검색 시 유용
    • 불변(immutable) 함수만 사용 가능

Hash

  • 해시맵 구조 기반 인덱스로, 긴 문자열이나 UUID 등에서 공간 효율적
    • 32비트 해시 코드를 저장하여 크기 절감
    • 동등 비교(=) 연산만 지원하며, 정렬이나 다중 컬럼 인덱스는 불가
    • 균등한 해시 분포일 때 Btree보다 빠른 읽기 성능 가능
  • 공식 문서에 따르면, 해시 인덱스는 버킷 페이지 직접 접근으로 대규모 테이블에서 I/O 감소

BRIN (Block Range Index)

  • 각 블록 범위의 최소·최대값만 저장하는 인덱스
    • 매우 압축적이고 캐시 친화적
    • 대규모, append-only, 시계열 데이터에 적합
  • 행이 자주 갱신되면 MVCC로 인한 중복 저장으로 효율 저하
  • pages_per_range 설정으로 정확도와 크기 간 트레이드오프 조정 가능

GIN (Generalized Inverted Index)

  • 복합 데이터 검색에 적합한 인덱스
    • 텍스트, 배열, JSONB 등에서 특정 요소 검색 지원
    • 데이터 타입별 전용 전략(opclass) 사용
    • JSON은 JSONB 컬럼, 텍스트는 tsvector 또는 pg_trgm 확장과 함께 사용 권장

GiST & SP-GiST

  • 일반화 검색 트리(GiST)공간 분할 트리(SP-GiST) 는 특정 데이터 타입용 인덱스 구현 프레임워크
    • GiST는 균형 트리, SP-GiST는 비균형 구조 지원
    • 지리정보, inet, 범위, 텍스트 벡터 등에 활용
    • GIN은 빠른 조회, GiST는 구축·유지 비용이 낮음
    • 전체 텍스트 검색 시 두 방식 중 요구사항에 맞게 선택

결론

  • 인덱스는 PostgreSQL 성능 최적화의 핵심이며, 읽기 속도 향상과 쓰기·저장 비용 간 균형이 중요
  • 데이터 특성과 쿼리 패턴에 맞는 인덱스 유형을 선택하면 빠르고 효율적인 데이터베이스 운영 가능
  • 적절한 인덱스 설계는 대규모 시스템의 확장성과 안정성 확보에 필수 요소임

Read Entire Article