콘텐츠로 이동

Clustering Index

클러스터형 인덱스는 프라이머리 키(PK) 값의 순서에 따라 테이블의 데이터가 물리적으로 정렬되어 저장되는 방식을 의미한다.

  • 변경되는 경우엔 해당 레코드의 물리적인 위치도 변경
  • 테이블 구조 자체는 일반 B-Tree와 비슷하지만, 클러스터링 인덱스의 리프노드에 레코드의 모든 컬럼이 저장

InnoDB 테이블은 반드시 클러스터형 인덱스를 가지며, 아래의 우선순위에 따라 클러스터링 키를 결정한다.

  1. 명시적으로 지정된 프라이머리 키(Primary Key)
  2. 첫 번째 NOT NULL + 유니크 인덱스(Unique Index).
  3. 내부적으로 6바이트 크기의 숨겨진 ROW_ID 컬럼 생성

보조 인덱스가 PK를 참조하는 구조

섹션 제목: “보조 인덱스가 PK를 참조하는 구조”

InnoDB의 모든 보조 인덱스는 리프 노드에 데이터의 물리적 주소 대신 프라이머리 키 값을 저장하게 되는데, 이는 다음과 같은 이유 때문이다.

  1. 보조 인덱스가 실제 레코드 주소를 가짐
  2. 실제 레코드 주소의 PK 변경 시 레코드의 주소가 변경 필요(클러스터링 된 상태여야 하기 때문)
  3. 그때마다 해당 테이블의 모든 인덱스에 대해 변경 작업 수행 필요
  4. 오버헤드 발생

PK를 클러스터링 인덱스로 사용하는 이유

섹션 제목: “PK를 클러스터링 인덱스로 사용하는 이유”

PK를 클러스터링 인덱스로 사용하는 이유는 읽기 성능을 향상시키기 위함이고, 그 장단점은 다음과 같다.

  • 장점
    • 프라이머리 키 기반의 범위 검색(Range Scan) 시, 데이터가 물리적으로 인접해 있어 빠른 I/O 성능 제공
    • 보조 인덱스가 프라이머리 키 값을 포함하므로, 쿼리에 필요한 모든 컬럼이 보조 인덱스와 프라이머리 키에 있다면 커버링 인덱스 사용 가능
  • 단점
    • 모든 보조 인덱스가 PK 값을 포함하므로, PK의 크기가 크면 보조 인덱스의 전체 크기도 상승
    • 데이터 삽입 시 PK 값에 따라 물리적 저장 위치가 결정되므로, 순차적이지 않은 PK 값(예: UUID)을 사용하면 페이지 분할(Page Split)이 빈번하게 발생하여 쓰기 성능이 저하 발생
    • 보조 인덱스를 통한 조회는 PK를 이용한 2차 조회로 추가적인 오버헤드 발생

PK 타입은 단순히 식별자 표현 방식의 차이가 아니라, 클러스터링 인덱스의 쓰기 패턴과 보조 인덱스의 크기, 분산 환경에서의 충돌 가능성까지 결정하는 설계 의사결정이다.

DB가 발급하는 단조 증가 정수 키이다.

  • 장점
    • 8 bytes로 가장 작아 보조 인덱스의 누적 크기가 최소
    • 항상 클러스터링 인덱스의 우측 리프에 삽입되어 페이지 분할 거의 없음 → 쓰기 처리량과 버퍼 풀 캐시 적중률 모두 우수
    • 비교·해시·직렬화 비용이 저렴
  • 단점
    • 외부에 노출되면 ID 추정으로 가입자 수·주문 수 같은 비즈니스 규모가 새거나 IDOR 공격 표면이 됨
    • 단일 DB가 발급하므로 멀티 마스터·샤딩 환경에서 글로벌 유니크 보장이 별도 설계 필요
    • INSERT 이후에야 값이 확정되므로 트랜잭션 외부에서 미리 알 수 없음

128bit 난수 기반 식별자이다.

  • 장점
    • 글로벌 유니크 → 분산·멀티 마스터·오프라인 클라이언트 환경에서 충돌 위험 없음
    • 애플리케이션·클라이언트 측에서 사전 발급 가능 → INSERT 전 ID를 알 수 있어 워크플로 단순화
    • 노출돼도 추정 가능한 정보가 거의 없음
  • 단점
    • 16 bytes로 Long의 2배 → 모든 보조 인덱스 누적 크기 증가
    • 완전 랜덤 분포 → 클러스터링 인덱스에 무작위 위치로 삽입되어 페이지 분할 빈발, 단편화와 쓰기 amplification 발생
    • hot 페이지가 흩어져 버퍼 풀 캐시 적중률 하락, 벌크 INSERT 처리량 급감

상위 48bit에 Unix epoch milliseconds 타임스탬프를 배치하고 나머지를 난수로 채워 시간 순 단조 증가를 보장하는 RFC 9562(2024) 표준 포맷이다.

  • 장점
    • 16 bytes는 그대로지만 ms 단위로 단조 증가 → 클러스터링 인덱스에서 항상 우측 리프에 삽입되어 페이지 분할이 BIGINT 수준으로 회복
    • 글로벌 유니크와 사전 발급 가능성은 v4와 동일하게 유지 → 분산 환경 안전성 + 쓰기 효율 동시 달성
    • 시간 순 정렬이 자연스러워 시계열 조회·페이징에 유리
  • 단점
    • Long 대비 여전히 2배 크기 → 보조 인덱스 누적 비용은 그대로 존재
    • 타임스탬프 부분이 노출되어 생성 시각 추정이 가능 → 보안 민감 식별자에는 부적절
    • Java 표준 라이브러리의 UUID.randomUUID()는 v4 → v7 생성에는 별도 라이브러리(예: java-uuid-generator, JDK 25+ 신규 API) 필요
    • MySQL 8.x는 UUID_v7() 내장 함수가 없어 애플리케이션에서 발급해야 함 (PostgreSQL 18은 uuidv7() 내장)
항목BIGINT AUTO_INCREMENTUUIDv4UUIDv7
크기8 bytes16 bytes16 bytes
단조 증가OXO (ms 단위)
페이지 분할 빈도거의 없음빈번거의 없음
글로벌 유니크X (DB 인스턴스 단위)OO
사전 발급XOO
인덱스 캐시 효율매우 높음매우 낮음높음
외부 노출 위험추정 가능 (보안 위험)추정 어려움시각 추정 가능
표준화 시점SQL 표준 이전RFC 4122 (2005)RFC 9562 (2024)
  • 단일 DB · 외부 노출 없음 · 쓰기 처리량 최우선 → BIGINT AUTO_INCREMENT
  • 분산 환경 · 사전 발급 필요 · 외부 노출 가능 → UUIDv7
  • UUIDv4는 신규 채택 시 v7 대비 쓰기 성능 손해만 발생하므로 권장하지 않음 (기존 시스템 호환·라이브러리 미지원 등의 제약이 없는 한)
  • UUID 채택 시 MySQL은 BINARY(16) 컬럼에 저장해 문자열 36 bytes 비용을 회피

마지막 업데이트:

MySQL