6.5 BRIN 인덱스
BRIN(Block Range Index)은 대용량 + 물리 저장 순서와 값 순서가 강하게 상관된 데이터에 특화된 인덱스입니다. 다른 인덱스가 row 단위 entry를 갖는 반면, BRIN은 페이지 범위(block range) 단위 요약값만 저장해 인덱스가 매우 작습니다. log·시계열·append-only 테이블의 기본 후보입니다.
동작 원리
테이블을 128페이지(기본) 묶음으로 나누고, 각 묶음마다 min/max 같은 요약값을 저장합니다.
range 0 (pages 0~127): min=1, max=12500
range 1 (pages 128~255): min=12501, max=25000
range 2 (pages 256~383): min=25001, max=37500
...쿼리 WHERE id BETWEEN 20000 AND 21000:
- range 0: max=12500 < 20000 → 건너뜀
- range 1: 겹침 → 페이지 128~255만 스캔
- range 2: min=25001 > 21000 → 건너뜀
페이지 대다수를 건너뛰므로 B-tree만큼 빠르진 않지만 매우 작은 인덱스로 큰 효과.
잘 맞는 데이터
| 조건 | 예시 |
|---|---|
| 물리 순서 = 값 순서 (correlation ≈ 1.0) | append-only insert가 만든 자연 순서 |
| 매우 큰 테이블 | 수억~수십억 row |
| 범위 쿼리 위주 | 날짜·ID 범위 검색 |
대표 사례:
created_at(시간 순서 INSERT)event_id(시퀀스)- IoT 센서 데이터의 timestamp
- 로그 테이블
잘 안 맞는 데이터
- random insert (UUID v4 등) — correlation 낮음
- 자주 UPDATE되는 컬럼
- 작은 테이블 — 비용 차이 무의미
생성
CREATE INDEX idx_events_created_at_brin
ON events USING brin (created_at);
-- 페이지 범위 크기 조절
CREATE INDEX idx_events_created_at_brin
ON events USING brin (created_at) WITH (pages_per_range = 32);| 옵션 | 기본 | 의미 |
|---|---|---|
pages_per_range | 128 | 한 range가 몇 페이지 |
autosummarize | off | 새 데이터 들어올 때 자동 요약 |
작은 pages_per_range는 인덱스 크기 ↑, 정밀도 ↑. 보통 32~128 사이.
크기 비교
| 인덱스 | 1억 row 기준 크기 (대략) |
|---|---|
B-tree (created_at) | 약 2GB |
BRIN (created_at, default) | 약 1MB |
차이가 2000배. 작은 인덱스는 캐시에 다 들어가고, ANALYZE·VACUUM 부담도 작습니다.
관리
신선도 유지
새 row가 들어오면 마지막 range의 max만 자동 갱신되지만, 새 range가 시작되면 명시적 summarize가 필요합니다.
-- 강제 summarize
SELECT brin_summarize_new_values('idx_events_created_at_brin'::regclass);autosummarize = on이면 PostgreSQL이 알아서 — 대부분 켜는 게 좋습니다.
대량 INSERT 후
ETL이 끝나면 한 번씩:
VACUUM events; -- BRIN range 갱신·요약 보강opclass
같은 데이터 타입이라도 opclass에 따라 BRIN이 저장하는 요약이 달라집니다.
| opclass | 저장 | 잘 처리하는 쿼리 |
|---|---|---|
*_minmax_ops (기본) | min, max | =, <, >, BETWEEN |
*_minmax_multi_ops (PG 14+) | 여러 min/max 묶음 | 비연속 분포에 유리 |
*_bloom_ops (PG 14+) | bloom filter | = 등호 — correlation 낮은 데이터에도 유효 |
*_inclusion_ops | bounding box | 범위 타입·기하 |
bloom_ops가 새로운 가능성: correlation이 낮은 컬럼에 BRIN을 적용해 확률적으로 페이지를 건너뛸 수 있습니다. tenant_id 같은 카테고리 컬럼에 효과적.
CREATE EXTENSION bloom; -- bloom_ops는 일부 타입은 별도 extension
CREATE INDEX idx_events_tenant_brin
ON events USING brin (tenant_id int4_bloom_ops);EXPLAIN의 BRIN
Bitmap Heap Scan on events
Recheck Cond: (created_at BETWEEN '2026-01-01' AND '2026-01-15')
Rows Removed by Index Recheck: 12000
-> Bitmap Index Scan on idx_events_created_at_brin
Index Cond: (created_at BETWEEN '2026-01-01' AND '2026-01-15')| 특징 | 의미 |
|---|---|
| 항상 Bitmap Heap Scan | BRIN은 정확한 row 위치를 모름. 후보 페이지를 모은 뒤 recheck |
Rows Removed by Index Recheck | range 안에 잘못 들어온 row를 SQL 조건으로 재거름 |
recheck가 너무 많으면 pages_per_range를 줄여 정밀도 ↑.
운영 의사결정
flowchart TD
Q["대용량 테이블<br/>범위 쿼리"] --> CORR{"INSERT 순서 =<br/>값 순서?"}
CORR -- "yes (시계열·시퀀스)" --> BRIN["BRIN<br/>(minmax_ops)"]
CORR -- "낮음" --> BLOOM{"등호 쿼리만?"}
BLOOM -- "yes" --> BBRIN["BRIN bloom_ops"]
BLOOM -- "no" --> BTREE["B-tree<br/>(또는 파티션)"]
classDef brin fill:#d1fae5,stroke:#047857,color:#064e3b
classDef btree fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef q fill:#fed7aa,stroke:#c2410c,color:#7c2d12
class Q,CORR,BLOOM q
class BRIN,BBRIN brin
class BTREE btree
파티셔닝과의 조합
BRIN과 파티셔닝은 보완 관계. 큰 시계열 테이블을 월 단위 파티션으로 나누고, 각 파티션 안에서 created_at에 BRIN을 둡니다. 파티션이 큰 범위를 처내고, BRIN이 세부 범위를 처냄.
정리
- BRIN = 페이지 범위 단위 요약값만 저장 → 매우 작은 인덱스
- 잘 맞는 데이터: append-only, 시계열, correlation ≈ 1.0
- random insert에는 부적합 (단,
bloom_ops로 등호 쿼리는 가능) - 옵션:
pages_per_range(정밀도),autosummarize(자동 갱신) - EXPLAIN에는 항상 Bitmap Heap Scan + Recheck
- 파티셔닝과 조합해 대용량 시계열에 최적
다음 절(6.6)에서는 일반 인덱스 전부에 적용 가능한 partial · expression 인덱스를 봅니다.