콘텐츠로 이동

인덱스 스캔 vs 비트맵 스캔: xmin/xmax의 역할

🐘 PostgreSQL: 왜 인덱스만으로 부족한가?

PostgreSQL에서 데이터를 조회할 때, 인덱스에 모든 컬럼이 포함되어 있음에도 불구하고 Index Only Scan 대신 Bitmap Scan이나 Heap 접근이 발생하는 이유는 크게 두 가지 메커니즘 때문입니다.

1. MVCC와 가시성(Visibility) 문제

PostgreSQL은 MVCC(Multi-Version Concurrency Control)를 통해 동시성을 관리합니다.

  • 데이터의 상태: 모든 행(Tuple)은 생성 및 삭제 시점을 추적하기 위해 숨겨진 시스템 열을 가집니다.
  • xmin (Transaction Minimum): 행(튜플)이 삽입(INSERT)되거나 업데이트(UPDATE)될 때 해당 행을 생성한 트랜잭션 ID. 용도: 이 행이 언제 만들어졌는지, 어떤 트랜잭션이 유효한지 확인.
  • xmax (Transaction Maximum): 행이 삭제(DELETE)되거나 업데이트(UPDATE)될 때 해당 행을 만료시킨 트랜잭션 ID. (0이면 유효한 데이터) 행이 언제 삭제/변경되었는지 추적.
  • 인덱스의 한계: B-Tree 등의 인덱스 구조에는 이 xmin, xmax 정보가 포함되어 있지 않습니다. 오직 데이터 값과 실제 데이터 위치(TID)만 저장합니다.
  • 힙(Heap) 접근의 이유: 인덱스에서 조건에 맞는 데이터를 찾아도, 해당 데이터가 현재 내 트랜잭션에서 "보이는 버전"인지 확인하려면 결국 테이블 원본(Heap)의 시스템 열을 확인해야 합니다.

2. 가시성 지도 (Visibility Map)의 역할

매번 힙에 접근하는 성능 저하를 막기 위해 PostgreSQL은 가시성 지도(Visibility Map)를 사용합니다.

  • All-visible 상태: 특정 페이지의 모든 행이 모든 트랜잭션에게 보인다고 판단되면 가시성 지도에 표시됩니다.
  • 최적화: 인덱스 스캔 시 가시성 지도를 확인하여 해당 페이지가 "All-visible"이라면, 힙에 가지 않고 Index Only Scan을 수행합니다.

3. 데이터 삽입 직후의 상황 (Issue Case)

수천 개의 데이터를 방금 삽입(Bulk Insert)한 직후에는 다음과 같은 문제가 발생합니다.

현상 원인 결과
신뢰도 저하 새로 삽입된 데이터는 아직 가시성 지도에 업데이트되지 않음. 플래너가 데이터의 유효성을 확신할 수 없음.
스캔 방식 변경 인덱스만으로는 가시성 확인이 불가능함. Bitmap Scan 등을 통해 힙 영역을 강제 확인.

4. 해결책: VACUUM

이 문제를 해결하고 성능을 최적화하려면 VACUUM 명령어가 필요합니다.

  • 동작: PostgreSQL은 xmin과 xmax를 비교하여 현재 트랜잭션이 행을 볼 수 있는지 결정합니다. VACUUM 작업은 xmax를 기반으로 더 이상 필요 없는(dead) 튜플을 청소하고 가시성 지도를 최신 상태로 갱신합니다.
  • 효과: 가시성 지도가 업데이트되면, 이후 동일 쿼리 실행 시 플래너가 힙 접근 없이 Index Only Scan을 선택하게 됩니다.

📝 핵심 요약 테이블

항목 설명
xmin 행을 생성한 트랜잭션 ID. 데이터의 생성 시점 결정.
xmax 행을 삭제/변경한 트랜잭션 ID. 데이터의 만료 시점 결정.
Index Only Scan 가시성 지도가 최신일 때만 힙 접근 없이 인덱스로만 처리하는 방식.
성능 최적화 대량 작업 후 VACUUM 또는 ANALYZE를 통해 통계 및 가시성 정보 갱신 필요.