PostgreSQL에서 인덱싱은 데이터베이스에서 데이터를 빠르게 찾는 방법이야. 만약 네 테이블에 있는 데이터가 책이라면, 인덱싱은 도서관의 카탈로그 같은 거지. 제목이나 저자 이름으로 원하는 책을 금방 찾을 수 있게 해주는 거야. 근데 JSONB는 좀 더 까다로워. 왜냐면 데이터가 구조화된 포맷으로 저장돼 있어서, 그냥 행(row)이나 컬럼(column)처럼 단순하지 않거든.
JSONB 데이터가 "해리포터 책만큼 두꺼워지면, 근데 그림은 없는 버전"처럼 커지면, 그 안에서 뭔가 찾는 게 진짜 느려질 수 있어. 예를 들어, 특정 키 "status"가 "delivered"인 모든 주문을 찾고 싶으면, PostgreSQL이 모든 레코드를 다 뒤져야 해. 이거 직접 하라고 하면 진짜 귀찮겠지?
그래서 GIN이랑 BTREE 인덱스가 우리의 구세주처럼 등장하는 거야! 이 친구들이 있으면 오래 기다릴 필요가 없어.
JSONB용 인덱스 타입
GIN (Generalized Inverted Index)
GIN 인덱스는 배열이나 오브젝트 같은 구조화된 데이터에 딱 맞게 만들어진 거야. 그래서 JSONB에 완전 잘 맞아. 전체 오브젝트를 인덱싱하는 게 아니라, 그 안에 있는 각각의 키와 값들을 따로따로 인덱싱해줘. 즉, GIN 덕분에 특정 키, 값, 혹은 그 조합을 빠르게 찾을 수 있어.
예를 들어, JSONB 컬럼에 이런 데이터가 있다고 해보자:
{"name": "Alice", "age": 25, "city": "Berlin"}
GIN 인덱스는 내부적으로 "name", "age", "city" 키랑 그 값들을 연결해서 저장해. 그래서 "name": "Alice"를 찾으려고 하면, PostgreSQL이 이미 어디에 있는지 알고 있어서 테이블 전체를 뒤지지 않아도 돼.
BTREE
BTREE 인덱스는 좀 더 전통적인 방식이야. 정렬된 구조를 만들어서, 특정 값으로 데이터를 빠르게 찾을 수 있게 해줘. JSONB에서 BTREE 인덱스를 쓰려면, 전체 오브젝트가 아니라 특정 키나 값을 인덱싱하는 게 좋아 (예를 들어, JSONB 전체 값이 똑같은지 비교할 때).
만약 네 컬럼에 이런 JSONB 오브젝트가 있다면:
{"name": "Bob", "age": 30}
BTREE 인덱스는 전체 오브젝트가 완전히 똑같은 레코드를 찾을 때 유용해.
{"name": "Bob", "age": 30}
JSONB 인덱스 만들기
먼저 GIN 인덱스 만드는 방법부터 보자. 필요한 건 CREATE INDEX라는 마법의 명령어뿐이야. 이렇게 쓰면 돼:
-- JSONB 컬럼에 GIN 인덱스 만들기
CREATE INDEX idx_jsonb_data ON orders USING GIN (data);
여기서:
idx_jsonb_data— 인덱스 이름이야.orders— 테이블 이름.data—JSONB데이터가 들어있는 컬럼.
이 인덱스를 만들고 나면, JSONB 안에서 키나 값을 찾는 쿼리가 훨씬 빨라질 거야.
예를 들어, orders라는 테이블에 data라는 JSONB 컬럼이 있다고 해보자:
| id | data |
|---|---|
| 1 | {"status": "pending", "total": 100} |
| 2 | {"status": "delivered", "total": 200} |
인덱스 없이 쿼리하면:
-- "delivered" 상태인 모든 주문 찾기
SELECT * FROM orders WHERE data @> '{"status": "delivered"}';
테이블이 크면 이 쿼리가 엄청 오래 걸릴 수 있어. 근데 GIN 인덱스가 있으면 훨씬 빨라져.
BTREE 인덱스 만드는 법
BTREE 인덱스를 만들려면 살짝 다르게 해야 해. 대부분의 경우, JSONB에서 BTREE를 쓰려면 전체 오브젝트가 아니라 그 안의 일부만 인덱싱해야 해. 예시를 볼게:
-- 특정 키에 BTREE 인덱스 만들기
CREATE INDEX idx_jsonb_total ON orders ((data->>'total'));
여기서 (data->>'total')은 JSONB 오브젝트에서 total 키의 값을 뽑아내는 거야. 이 값을 인덱싱하는 거지. 이제 total = 100인 주문을 찾으면 PostgreSQL이 이 인덱스를 써줄 거야.
아까랑 똑같은 데이터로 예시를 들어볼게:
| id | data |
|---|---|
| 1 | {"status": "pending", "total": 100} |
| 2 | {"status": "delivered", "total": 200} |
쿼리 예시:
-- total = 100인 모든 주문 찾기
SELECT * FROM orders WHERE data->>'total' = '100';
data->>'total'에 BTREE 인덱스가 있으면, 이 쿼리도 훨씬 빨라질 거야.
GIN vs BTREE 비교
| 특징 | GIN | BTREE |
|---|---|---|
| 뭐가 인덱싱됨? | JSONB 안의 키와 값 | 지정한 경로나 값 |
| 최적의 사용 시나리오 | 오브젝트 일부 검색 | 특정 값 검색 |
| 인덱스 생성 속도 | 느림 | 빠름 |
| 검색 속도 | 복잡한 구조에 더 빠름 | 고정된 값에 더 빠름 |
| 지원 연산자 | @>, ?, `? |
,?&` |
만약 네 JSONB 구조가 복잡하고 @>나 ? 같은 연산자를 자주 쓴다면 GIN을 써. 반대로, 특정 값이나 고정된 키만 자주 찾는다면 BTREE가 더 나을 수도 있어.
JSONB 인덱싱할 때 흔히 하는 실수와 함정
JSONB 인덱싱은 강력하지만, 몇 가지 조심해야 할 점이 있어.
- 필요한 곳에 인덱스가 없음. JSONB 데이터를
WHERE에서 자주 쓰는데 인덱스를 안 만들면, 쿼리가 엄청 느려질 수 있어. - 인덱스를 너무 많이 만듦. JSONB의 모든 키마다 인덱스를 만들면, 오히려 insert나 update가 느려질 수 있어.
- 인덱스 타입을 잘못 선택함. 쿼리가 복잡하고
@>나?같은 연산자를 쓰는데BTREE인덱스를 만들면, 성능 향상이 안 돼. - 경로(path)에 대한 이해 부족. 항상 중첩된 값을 찾는데, 그 경로에 인덱스를 안 만들면 쿼리가 여전히 느릴 거야 (예:
data->>'some_key').
정리: 언제 어떤 인덱스를 써야 할까?
GIN은 배열이나 복잡한 오브젝트, 키/값 검색이 많을 때 써.BTREE는 정확히 일치하는 값이나 특정 키만 자주 찾을 때 써.
GO TO FULL VERSION