명령어/DB

[PostgreSQL] GIN vs GiST — 전문검색·배열은 GIN, 범위·좌표·최근접은 GiST

jykim23 2026. 7. 23. 21:54
반응형

설치·접속: PostgreSQL 설치와 접속

부제: tsvector·배열·jsonb에 인덱스를 걸려는데 GIN과 GiST 중 뭘 골라야 할지 매번 헷갈릴 때

-- GIN: 값을 잘게 쪼개 "어떤 문서에 이 토큰이 있나"를 역색인. 전문검색·배열 포함
CREATE INDEX idx_docs_gin ON m5_docs USING gin(doc);
SELECT count(*) FROM m5_docs WHERE doc @@ to_tsquery('english','performance & planner');

-- GiST: 값을 경계 상자(bounding box)로 요약. 범위 겹침·좌표·최근접(<->)
CREATE INDEX idx_pts_gist ON m5_pts USING gist(loc);
SELECT id FROM m5_pts ORDER BY loc <-> point(500,500) LIMIT 5;   -- 가장 가까운 5개
-- GIN 전문검색: 50만 문서에서 71428건, 4.7ms 만에 후보 색출
 Bitmap Index Scan on idx_docs_gin  (rows=71428)   Execution Time: 44 ms
-- GiST 최근접(KNN): 50만 좌표 중 가장 가까운 5개를 인덱스 순회로
 Index Scan using idx_pts_gist on m5_pts  (Order By: loc <-> '(500,500)')
   rows=5   Execution Time: 0.081 ms          ← GIN으로는 불가능한 연산

둘 다 btree로 못 하는 검색을 담당하지만 강점이 갈린다. GIN(Generalized Inverted iNdex) 은 값을 토큰으로 쪼개 "이 토큰 → 이 행들" 역색인을 만든다. 그래서 전문검색(@@), 배열 포함(@>), jsonb 키 검색처럼 한 컬럼 안에 여러 원소가 들어 있고 그중 하나로 찾는 경우에 압도적이다. 검색은 빠르지만 여러 토큰을 갱신해야 해 쓰기·빌드가 무겁다 — 자주 바뀌는 컬럼엔 부담. GiST(Generalized Search Tree) 는 값을 경계 상자로 요약한 균형 트리다. 범위 겹침(&&), 좌표 검색, 그리고 GIN이 절대 못 하는 최근접 이웃(ORDER BY loc <-> point) 과 배타 제약(EXCLUDE)을 지원한다. 요약이라 lossy(재검사 필요)지만 쓰기가 가볍다. 정리하면: 텍스트/배열/jsonb를 많이 읽고 덜 바꾼다 → GIN, 범위·좌표·최근접·배타제약이 필요하다 → GiST.

이렇게도 쓴다

배열 컬럼의 "이 값들을 포함" 검색은 GIN이 정석이다. (조합: 배열 @>)

CREATE INDEX idx_docs_tags_gin ON m5_docs USING gin(tags);
SELECT count(*) FROM m5_docs WHERE tags @> ARRAY[7,12];   -- 4034건, 2.9ms

 

예약 시간대 겹침 검사는 GiST 범위 인덱스로. (조합: 범위 &&)

CREATE INDEX idx_rsv_gist ON m5_rsv USING gist(during);
SELECT count(*) FROM m5_rsv WHERE during && tsrange('2024-06-01','2024-06-02');

 

시간대 중복 예약을 DB 차원에서 원천 봉쇄한다(GiST만 가능). (조합: EXCLUDE 제약)

ALTER TABLE m5_rsv ADD CONSTRAINT no_overlap EXCLUDE USING gist (during WITH &&);

 

jsonb 키/값 검색은 GIN에 jsonb_path_ops로 더 작고 빠르게. (조합: jsonb 연산자 클래스)

CREATE INDEX idx_meta_gin ON m5_docs USING gin(meta jsonb_path_ops);

 

같은 컬럼에 두 인덱스를 만들어 크기를 나란히 비교한다. (조합: pg_relation_size)

SELECT pg_size_pretty(pg_relation_size('idx_docs_gin'));   -- 30 MB (GiST는 32 MB)

 

전문검색을 자주 쓰면 GIN 리스트 정리를 미뤄 쓰기 부담을 던다. (조합: fastupdate)

CREATE INDEX idx_fast ON m5_docs USING gin(doc) WITH (fastupdate=on);
반응형