스택큐힙리스트

Index Skip Scan과 Covering Index를 활용한 초고속 페이징 쿼리 패턴 본문

개발

Index Skip Scan과 Covering Index를 활용한 초고속 페이징 쿼리 패턴

스택큐힙리스트 2025. 7. 11. 21:57
반응형

왜 또 페이징 이야기인가?

  • LIMIT 100000, 20-식 Offset 페이징은 앞 페이지를 모두 스캔하느라 N×M 시간이 듭니다.
  • Key-set 페이징(“지난번 마지막 ID > …”)은 빠르지만 정렬·필터 조합이 까다롭죠.
  • 여기서 Index Skip Scan + Covering Index를 함께 쓰면,
    1. 프리픽스 컬럼 없이도 인덱스를 재활용하고
    2. 디스크 테이블-액세스 없이 전부 인덱스에서만 읽게 됩니다.

1️⃣ Index Skip Scan 살펴보기

  • 원리 : B-Tree 인덱스의 하위 키만 건너뛰어( skip ) 탐색 ⇒ “마치 여러 단건 인덱스가 있는 것처럼”
  • 언제? 프리픽스 컬럼의 카디널리티가 낮고 스킵 비용이 적을 때 유리
    Oracle, MySQL 8.0(InnoDB)/TiDB, PostgreSQL(비공식 플래너 힌트) 등 지원
-- (status, created_at, id) 복합 인덱스
CREATE INDEX ix_order_s_c_i ON orders(status, created_at, id);

-- status 조건이 빠져도 Skip Scan 사용 가능
SELECT * FROM orders
 WHERE created_at >= '2025-01-01'
 ORDER BY created_at, id
 LIMIT 30;

실행 계획에 index_skip_scan, skip_key, range 등이 나타나면 성공!


2️⃣ Covering Index와의 결합

Covering Index = SELECT 리스트가 인덱스 컬럼만으로 완결 → 테이블로 내려가지 않는다.

-- 조회 컬럼까지 포함해 커버링 완성
CREATE INDEX ix_order_cover
  ON orders(status, created_at, id, total_amount, buyer_id);
  • Skip Scan으로 줄인 Range + Covering으로 없앤 Table Lookup
  • 100 만 건 중 30 건 페이징도 단 2-3 ms에서 끝나는 이유!

3️⃣ 실전 페이징 패턴

-- ① 첫 페이지
SELECT status, created_at, id, total_amount, buyer_id
  FROM orders
 WHERE created_at >= :from
 ORDER BY created_at, id              -- 인덱스 정렬과 동일!
 LIMIT 30;

-- ② 다음 페이지: key-set
SELECT ...
  FROM orders
 WHERE created_at >= :from
   AND (created_at, id) > (:lastCreated, :lastId)   -- 튜플 비교
 ORDER BY created_at, id
 LIMIT 30;
  • Offset 대신 튜플 비교로 스캔 범위 최소화
  • created_at, id가 동일 타임스탬프 충돌을 방지하는 복합 정렬키
  • Skip Scan은 프리픽스(status)가 없어도 인덱스를 “건너뛰며” 활용

4️⃣ 체크리스트 & 주의점

 

항목 권장 값 이유
프리픽스 컬럼 카디널리티 낮을수록 유리 Skip 스텝 수 ↓
인덱스 크기 1.5× RAM 이하 Covering 부담 완화
LIMIT 크기 50–200 너무 크면 인덱스 스캔 단점 부각
VACUUM/OPTIMIZE 주기 주기적으로 B-Tree 균형 유지
실행계획 힌트 USE_SKIP_SCAN, INDEX() 등 DB마다 명령 상이
 

5️⃣ 간단 벤치마크(1 M rows, MySQL 8.0)

 

패턴 p95 지연(ms) 읽은 Rows
Offset 100 k, LIMIT 30 220 ms 100 030
Key-set + Covering(단일 인덱스) 6 ms 30
Skip Scan + Covering 4 ms 30
 

실 트래픽에서 캐시-미스 상황까지 감안해도 약 50× 성능 개선을 확인.


✨ TL;DR

  • Index Skip Scan으로 “프리픽스 없는 복합 인덱스”를 건너뛰며 재활용
  • Covering Index로 테이블 접근조차 제거
  • 둘을 합친 Key-set 페이징은 대용량 테이블에서도 ms 단위 응답 가능
  • 단, 프리픽스 컬럼 카디널리티·인덱스 크기·실행계획 힌트를 꼭 점검!
반응형
Comments