6.2 Hash 인덱스
Hash 인덱스는 등호(=) 비교만을 위한 인덱스입니다. 키를 해시해서 버킷에 매핑하므로 룩업이 O(1)에 가깝습니다. 그러나 범위·정렬·prefix 모두 안 되고 운영 메리트가 작아, 일상에서는 거의 안 쓰입니다. PG 10에서야 WAL이 붙어 crash-safe하고 복제도 되는 운영 가능한 상태가 되었다는 점도 채택을 더디게 만든 이유.
언제 검토 가치가 있는가
| 시나리오 | Hash가 좋을 수 있나 |
|---|---|
| 매우 큰 텍스트(키 길이 100B+)의 정확 일치 룩업 | 인덱스 크기가 B-tree보다 작아질 수 있음 |
| 캐시 키 같은 단순 등호 쿼리 | OK, 단 B-tree로도 충분히 빠름 |
| 범위·정렬·prefix·ORDER BY | B-tree |
| join, unique 제약 | B-tree (Hash는 unique 미지원) |
운영 권장은 B-tree가 디폴트, Hash는 명확한 이유가 있을 때만.
생성
CREATE INDEX idx_users_email_hash ON users USING hash(email);조회는 평소처럼:
SELECT * FROM users WHERE email = 'a@b.com';EXPLAIN에서는 Index Scan using idx_users_email_hash 형태로 나타납니다.
제약과 한계
| 제약 | 메모 |
|---|---|
=만 지원 | <, >, BETWEEN, ORDER BY 모두 불가 |
| Unique 미지원 | PK·UNIQUE constraint를 hash로 못 만듦 |
| Multi-column 미지원 | 한 컬럼만. 다중은 표현식 인덱스로 묶거나 B-tree |
| Index-only scan 미지원 | VM 활용 불가 |
| WAL 지원 | PG 10+ |
성능 비교 — B-tree와의 차이는?
실제 운영에서는 B-tree와의 차이가 크지 않습니다. PG의 B-tree가 매우 잘 최적화돼 있어 등호 룩업도 O(log n) ≈ 23 페이지 비용입니다. Hash가 이론적으로 O(1)이지만 페이지 12를 따라가는 비용은 비슷.
| 측면 | B-tree | Hash |
|---|---|---|
| 룩업 비용 | O(log n), 보통 2~4 페이지 | O(1), 보통 1~2 페이지 |
| 인덱스 크기 | 키 + ctid + 메타 | 해시값 + ctid + 메타 — 긴 키에서 작아짐 |
| 범위·정렬 | 가능 | 불가 |
| 운영 도구 지원 | 완전 | 일부 도구(BLOAT 추정 등) 약함 |
짧은 키(int, uuid)는 차이 무시할 만 함. 매우 긴 키일 때만 hash가 크기 이점.
운영 시 고려 사항
-- Hash 인덱스는 collision이 늘면 BLOAT
\d+ users
-- Indexes:
-- "idx_users_email_hash" hash (email)pg_class.relpages로 크기 추적합니다. dead entry는 VACUUM이 정리하지만, B-tree만큼 다듬어진 도구가 적다 — 운영 표준에서 빠지는 이유.
표현식 인덱스로 hash 효과 내기
여러 컬럼 등호 룩업이 빈번하면 표현식 hash 인덱스가 가능:
CREATE INDEX idx_users_composite_hash
ON users USING hash((tenant_id::text || ':' || email));
-- 쿼리도 같은 식
SELECT * FROM users WHERE tenant_id::text || ':' || email = '42:a@b.com';다소 어색합니다. 대부분의 경우 multi-column B-tree가 더 자연스럽고 빠릅니다.
Hash 인덱스를 쓰기 전에 정말 필요한지 한 번 더 고민. 매우 긴 키의 등호 룩업이라는 구체적 이유가 있을 때만. 일반 OLTP는 B-tree가 답입니다.
진단 — Hash 인덱스 사용 추적
SELECT t.relname AS table, i.indexrelname AS index, i.idx_scan
FROM pg_stat_user_indexes i
JOIN pg_index x ON i.indexrelid = x.indexrelid
JOIN pg_class c ON x.indexrelid = c.oid
JOIN pg_am a ON c.relam = a.oid
JOIN pg_class t ON x.indrelid = t.oid
WHERE a.amname = 'hash';idx_scan = 0이면 사용 안 됨 — 정리 후보입니다.
정리
- Hash 인덱스는 등호 룩업 전용
- PG 10+에서야 운영 가능 (WAL·replication 지원)
- 짧은 키에서는 B-tree와 성능 차이 거의 없음
- 매우 긴 키의 정확 일치 룩업 외에는 B-tree가 표준
- Unique, multi-column, index-only scan, 정렬 모두 불가
- 운영에서는 확실한 이유가 있을 때만 채택
다음 절(6.3)에서는 공간 데이터·범위 타입에 강한 GiST와 SP-GiST를 봅니다.