
들어가며: 쿼리가 느려지는 이유
서비스가 커지면 데이터도 늘어난다. 1초 만에 끝나던 쿼리가 100만, 1,000만 건 앞에서는 느려진다. 인덱스 없는 데이터베이스는 수만 권 책이 꽂힌 도서관에서 1페이지부터 끝까지 읽는 것과 같다. 이를 전체 스캔(Full Scan, O(N))이라 한다.
인덱스는 단순한 성능 도구가 아니다. "이 서비스는 특정 데이터를 이 순서로 빠르게 조회하겠다"라는 개발자의 의도를 표현한다. 복합 인덱스(Composite Index)를 이해하면 시스템 병목을 해결할 통찰력을 얻을 수 있다.
1. 정렬 순서가 성능을 결정한다
복합 인덱스 idx_metrics_stream_time(['streamId', 'recordedAt'])는 단순 컬럼 묶음이 아니다. 계층적 정렬 구조를 가진다.
- 첫 번째 컬럼 streamId로 먼저 정렬
- 같은 streamId 내에서 recordedAt 순으로 정렬
비유: streamId가 챕터, recordedAt이 페이지인 책장 구조와 같다.
B-tree 계층 구조 예시

이 구조 덕분에 특정 스트림의 특정 시간대 데이터를 조회할 때, 데이터베이스는 챕터(streamId)를 먼저 선택하고 페이지만 연속적으로 읽으면 된다.
예제 쿼리
SELECT *
FROM metrics
WHERE streamId = 3
AND recordedAt BETWEEN '2026-01-01' AND '2026-01-02';
2. 왼쪽의 법칙(Left-most Prefix Rule)
복합 인덱스는 왼쪽 컬럼부터 조건에 사용해야 한다. 첫 번째 컬럼 없이 뒤 컬럼만 쓰면 인덱스는 무력해진다.
인덱스 활용 체크표
| 쿼리 조건 | 인덱스 활용 여부 |
| WHERE streamId = ? | ✅ 효과적 |
| WHERE streamId = ? AND recordedAt BETWEEN ? AND ? | ✅ 최적 상태 |
| WHERE streamId = ? ORDER BY recordedAt DESC | ✅ 정렬 활용 가능 |
| WHERE recordedAt = ? | ❌ 인덱스 효과 거의 없음 |
주의: 지표 데이터(FPS, Bitrate 등)는 동일한 streamId와 recordedAt을 가진 로우가 여러 개 존재할 수 있다. 따라서 @Unique 제약을 걸면 안 된다.
3. 인덱스는 '지도'일 뿐 '보물'이 아니다
인덱스에는 실제 데이터가 아닌 위치 정보가 들어 있다.
| DB 종류 | 인덱스 구조 |
| PostgreSQL | ctid로 테이블(Heap) 위치 참조 |
| MySQL (InnoDB) | 보조 인덱스는 PK 값으로 참조 → 더블 룩업 발생 |
인덱스 과다 사용 문제
- 저장 공간 증가
- 삽입/수정 시 인덱스 재작성 → 쓰기 성능 저하
핵심: 모든 컬럼을 인덱스에 넣으면 안 된다. 인덱스 최적화는 항상 트레이드오프다.
4. 커버링 인덱스(Covering Index)
커버링 인덱스는 쿼리에 필요한 컬럼이 모두 인덱스에 포함된 상태다. 테이블로 이동하지 않고 인덱스만 읽어 결과를 반환할 수 있다.
예제: Index Only Scan
SELECT streamId, recordedAt
FROM metrics
WHERE streamId = 3;
조회 과정 비교
| 종류 | 탐색 과정 | 장점 |
| 일반 인덱스 | 인덱스 → 테이블 이동 → 데이터 조회 | 인덱스 크기 작음, 일부 컬럼만 활용 가능 |
| 커버링 인덱스 | 인덱스만 읽고 결과 반환 | 테이블 이동 없음 → 속도 극대화 |
결과: 수천만 건도 빠르게 조회 가능. 필요한 컬럼만 선택하면 성능을 극대화할 수 있다.
마무리: 인덱스 설계는 서비스 설계와 같다
- 인덱스 이름 하나에도 전략이 담겨야 한다 (idx_metrics_stream_time)
- 선두 컬럼 결정과 인덱스 구성은 선택이 아니라 필수
- 인덱스는 속도를 위한 마법이 아니라, 계산된 트레이드오프의 결과다
질문: 지금 설계한 복합 인덱스의 첫 번째 컬럼은 제 역할을 하고 있는가?
'잡(job)기술' 카테고리의 다른 글
| SVN Merge 실수 줄이는 법: 체리픽부터 mergeinfo까지 실전 전략 5가지 (0) | 2026.02.11 |
|---|---|
| NestJS 모듈의 비밀: 왜 클래스 안은 텅 비어 있을까? (0) | 2026.02.06 |
| 데이터 무결성을 지키는 마지막 방어선 - TypeORM Entity 설계의 5가지 핵심 포인트 (3) | 2026.02.03 |
| Nginx 설정, 이 5가지만 알면 ‘고수’ 소리 듣는다 (0) | 2026.02.03 |
| React 프로젝트를 200% 효율적으로 만드는 4가지 핵심 원칙 (0) | 2026.02.01 |