2025년 12월 20일·5분 읽기

B-tree vs GIN vs GiST 인덱스: 실무 PostgreSQL 가이드

B-tree, GIN, GiST 인덱스 비교: 필터, 검색, JSONB, 지오 쿼리, 고유도 컬럼에 대해 어떤 PostgreSQL 인덱스를 선택할지 결정표로 안내합니다.

B-tree vs GIN vs GiST 인덱스: 실무 PostgreSQL 가이드

인덱스를 고를 때 실제로 선택하는 것

대부분의 PostgreSQL 성능 문제는 비슷한 방식으로 시작합니다. 1,000행일 때는 빠르던 목록 보기가 1,000,000행에서 느려지거나, 테스트에서는 괜찮던 검색 상자가 프로덕션에서 수초간 멈춥니다. 그럴 때 “어떤 인덱스가 최고인가?”를 묻고 싶지만, 더 나은 질문은 “이 화면이 DB에 무엇을 요청하는가?”입니다.

같은 테이블이라도 화면마다 읽는 방식이 달라서 인덱스 유형이 달라질 수 있습니다. 어떤 뷰는 status로 필터하고 created_at으로 정렬합니다. 다른 뷰는 전체 텍스트 검색을 하고, 또 다른 뷰는 JSON 필드에 키가 있는지 확인하거나 지도에서 특정 지점 근처의 항목을 찾습니다. 접근 패턴이 다르니 한 인덱스 유형으로 모든 경우를 해결할 수 없습니다.

인덱스를 선택할 때 실제로 고르는 건 앱이 데이터를 어떻게 접근하는지입니다. 주로 정확한 매칭, 범위, 정렬을 하시나요? 문서나 배열 내부를 검색하나요? “이 위치 근처에 무엇이 있는가” 또는 “이 범위와 겹치는가”를 묻나요? 답에 따라 B-tree, GIN, GiST 중 적절한 선택이 달라집니다.

B-tree, GIN, GiST을 쉽게 설명하면

인덱스 선택은 컬럼 타입보다는 쿼리가 그 컬럼으로 무엇을 하는지에 달려 있습니다. PostgreSQL은 =, <, @> 또는 @@ 같은 연산자를 기준으로 인덱스를 활용합니다. 그래서 같은 필드라도 화면에 따라 필요한 인덱스가 달라질 수 있습니다.

B-tree: 정렬된 조회에 빠릅니다

B-tree는 기본이자 가장 흔한 선택입니다. 값 동등 비교, 범위 필터, 특정 정렬이 필요할 때 강합니다.

예를 들어 관리자가 상태로 필터하고 created_at으로 정렬하는 목록이 있다면 (status, created_at)에 대한 B-tree 인덱스가 필터와 정렬을 모두 도와줄 수 있습니다. 또한 유일성(unique) 제약에도 보통 B-tree를 사용합니다.

GIN: 각 행에 검색 가능한 키가 많을 때 빠릅니다

GIN은 한 행이 여러 키와 매칭될 수 있는 ‘이 행이 이 용어/값을 포함하나?’ 같은 질문에 최적화되어 있습니다. 일반적인 예는 전체 텍스트 검색(문서가 단어들을 포함하는지)과 JSONB/배열 포함 여부(키/값 포함)입니다.

예: 고객 레코드에 JSONB preferences가 있고 화면에서 {\"newsletter\": true}를 포함하는 사용자를 필터한다면, 이것은 GIN 스타일의 조회입니다.

GiST: 범위, 지리, 유사도에 유연합니다

GiST는 단순한 정렬로는 표현하기 어려운 데이터 타입을 위한 일반 프레임워크입니다. 범위(겹침, 포함), 기하학·지리 쿼리(근접, 내부 포함), 일부 유사도 검색에 자연스럽게 맞습니다.

B-tree vs GIN vs GiST 중에서 고를 때는 먼저 가장 바쁜 화면들이 어떤 연산자를 사용하는지 적어보세요. 그러면 적절한 인덱스가 더 명확해집니다.

일반 화면(필터, 검색, JSON, 지오)을 위한 결정표

대부분의 앱에는 몇 가지 인덱스 패턴만 필요합니다. 핵심은 화면 동작을 쿼리 연산자와 매칭하는 것입니다.

화면 패턴일반적 쿼리 형태권장 인덱스 유형예시 연산자
단순 필터(status, tenant_id, email)많은 행 중에서 동등 비교로 좁힘B-tree= IN (...)
날짜/숫자 범위 필터시간 창이나 최소/최대B-tree>= <= BETWEEN
정렬 + 페이징(피드, 관리자 목록)필터 후 ORDER BY ... LIMITB-tree(복합 인덱스가 흔함)ORDER BY created_at DESC
고유도가 높은 컬럼(user_id, order_id)매우 선택적인 조회B-tree=
전체 텍스트 검색 박스필드 전반의 텍스트 검색GIN@@ on tsvector
“포함” 텍스트 검색%term% 같은 부분 문자열보통 없음(또는 특수 trigram 구성)LIKE '%term%'
JSONB 포함(태그, 플래그, 속성)JSON 형태나 키/값 매칭GIN on jsonb@>
JSONB 단일 키 동등 비교특정 JSON 키를 자주 필터할 때표현식 기반 B-tree(data->>'plan') = 'pro'
지오 근접/반경“내 주변” 및 지도 뷰GiST(PostGIS geometry/geography)ST_DWithin(...) <->
범위, 겹침(일정, 가격대)구간의 겹침 검사GiST(range types)&&
선택성 낮은 필터(부울, 작은 enum)대부분의 행이 일치인덱스가 거의 도움 안 됨is_active = true

엔드포인트가 다르면 두 개의 인덱스가 공존할 수 있습니다. 예: 관리자 목록에는 정렬을 위한 (tenant_id, created_at) B-tree가 필요하지만, 검색 페이지는 @@를 위한 GIN이 필요할 수 있습니다. 두 쿼리 패턴이 모두 자주 쓰인다면 둘 다 유지하세요.

확신이 서지 않으면 먼저 연산자(operator)를 살펴보세요. 인덱스는 데이터베이스가 테이블의 큰 부분을 건너뛰게 해줄 때 진짜로 도움이 됩니다.

필터와 정렬: B-tree가 주로 유리한 곳

일상적인 화면에서는 B-tree가 안정적으로 작동합니다. 쿼리가 “컬럼이 특정 값과 같다 → 정렬 → 페이지 1” 같은 형태라면 B-tree가 보통 첫 번째 시도 후보입니다.

동등 비교 필터가 전형적인 경우입니다. status, user_id, account_id, type, tenant_id 같은 컬럼은 대시보드나 관리자 패널에 자주 등장합니다. B-tree는 바로 일치하는 값을 찾아줍니다.

범위 필터도 B-tree에 잘 맞습니다. 시간이나 숫자 범위로 필터할 때 정렬된 구조가 도움이 됩니다: created_at >= ..., price BETWEEN ..., id > .... UI에서 “지난 7일”이나 “$50~$100” 같은 옵션을 제공한다면 B-tree가 제 몫을 합니다.

정렬과 페이지네이션에서 B-tree는 특히 유용합니다. 인덱스 순서가 ORDER BY와 일치하면 PostgreSQL은 큰 집합을 메모리에서 정렬하는 대신 이미 정렬된 행을 반환할 수 있습니다.

-- 자주 쓰이는 화면: "내 오픈 티켓, 최신순"
CREATE INDEX tickets_user_status_created_idx
ON tickets (user_id, status, created_at DESC);

복합 인덱스는 한 가지 간단한 규칙을 따릅니다: PostgreSQL은 인덱스의 선행(leftmost) 부분만 효율적으로 사용할 수 있습니다. 왼쪽에서 오른쪽으로 생각하세요. (user_id, status, created_at)에서는 user_id로 필터할 때(선택적으로 status도) 혜택을 봅니다. status만으로 필터하면 보통 이 인덱스의 이점이 적습니다.

Partial index는 화면이 데이터의 일부만 관심 있을 때 강력한 업그레이드입니다. 예: “활성 행만”, “soft-deleted가 아닌 것만”, “최근 활동만” 같은 경우 인덱스가 작아져 빠르고 효율적입니다.

고유도 컬럼과 인덱스 추가 비용

Make an internal tool
Create internal tools for ops and support with Postgres, auth, and business logic built in.
Start now

고유도가 높은 컬럼은 고유한 값이 많은 컬럼입니다(예: user_id, order_id, email, 초 단위 created_at). 이런 열에는 인덱스가 잘 맞습니다. 필터가 테이블의 아주 작은 조각으로 바로 좁혀주기 때문입니다.

반대로 선택성이 낮은 컬럼(부울, 작은 enum)은 인덱스가 실망스러울 수 있습니다. 각 값이 테이블의 큰 부분과 매칭되면 PostgreSQL은 인덱스를 건너뛰고 순차 검색(sequential scan)을 선택할 수 있습니다.

또 다른 미묘한 비용은 행을 가져오는 추가 작업입니다. 인덱스가 매칭 ID를 빨리 찾더라도 데이터베이스는 나머지 컬럼을 얻기 위해 테이블을 방문해야 할 수 있습니다. 쿼리가 필요한 필드가 몇 개뿐이라면 covering index가 도움이 되지만, 인덱스가 커지고 유지비가 늘어납니다.

추가 인덱스는 모두 쓰기 비용을 발생시킵니다. 삽입 시 각 인덱스에 기록해야 하고, 인덱스된 컬럼을 업데이트하면 인덱스도 갱신해야 합니다. "혹시 몰라" 하고 인덱스를 많이 추가하면 앱 전체가 느려질 수 있습니다.

실용적인 권장사항:

  • 바쁜 테이블에는 1~2개의 주력 인덱스로 시작하세요. 실제 필터와 정렬을 기반으로 합니다.
  • WHEREORDER BY에 쓰이는 고유도 컬럼을 우선하세요.
  • 부울이나 작은 enum에 대해서는 조심하세요. 다른 선택성 높은 컬럼과 결합되거나 partial 인덱스일 때만 고려하세요.
  • 인덱스는 정확히 어떤 쿼리를 빠르게 할지 이름을 댈 수 있을 때만 추가하세요.

예: assignee_id(고유도)로 필터하는 지원 티켓 목록은 인덱스를 얻어 혜택을 보지만, is_archived = false만으로는 대개 도움이 되지 않습니다.

검색 화면: 전체 텍스트, 접두사, 포함 검색

검색 상자는 단순해 보이지만 사용자 기대는 높습니다: 여러 단어, 단어 형태의 변형, 합리적인 랭킹 등. PostgreSQL에서는 보통 전체 텍스트 검색을 사용합니다. tsvector(미리 처리된 텍스트)를 저장하고 사용자가 입력한 내용을 tsquery로 변환하여 질의합니다.

전체 텍스트 검색에는 GIN이 기본 선택인 경우가 많습니다. 여러 행에서 “이 문서가 이 용어들을 포함하는가?”를 빠르게 답하기 때문입니다. 단, 쓰기 비용이 더 큽니다(삽입·업데이트 시 비용 증가).

GiST도 전체 텍스트 검색에 쓸 수 있습니다. GiST 인덱스는 보통 더 작고 업데이트 비용이 적지만 읽기 성능은 GIN보다 느릴 수 있습니다. 데이터 변경이 잦은 테이블에서는 이 읽기·쓰기 균형이 중요할 수 있습니다.

접두사 검색은 전체 텍스트가 아닙니다

접두사 검색은 “시작하는지”를 찾는 것으로, 이메일 접두사로 고객을 검색하는 경우가 예입니다. 접두사 패턴에는 문자열 정렬과 맞는 B-tree 인덱스가 도움이 됩니다.

ILIKE '%error%' 같은 포함 검색에는 B-tree가 보통 도움이 되지 않습니다. 이 경우 트라이그램 인덱스나 다른 검색 설계가 필요합니다.

필터와 텍스트 검색을 함께 사용하는 경우

대부분의 실제 화면은 검색과 필터를 함께 사용합니다: 상태, 담당자, 날짜 범위, 테넌트 등. 실용적인 구성은 다음과 같습니다:

  • tsvector 컬럼에는 GIN(또는 경우에 따라 GiST) 인덱스.
  • 가장 선택적인 필터들(예: account_id, status, created_at)에는 B-tree 인덱스.
  • 인덱스를 최소한으로 유지: 너무 많으면 쓰기 성능이 느려집니다.

예: “refund delayed”를 검색하고 status = 'open' 및 특정 account_id로 필터하는 지원 티켓 화면에서는 전체 텍스트가 관련 행을 찾고, B-tree가 계정·상태로 범위를 좁혀줍니다.

JSONB 필드: GIN과 표현식 B-tree 사이에서 고르기

Add search without guesswork
Set up full-text search flows and keep logic in one place with drag-and-drop processes.
Try it now

JSONB는 유연하지만, 일반 컬럼처럼 다루면 느린 쿼리가 생길 수 있습니다. 핵심 결정은 간단합니다: “JSON 전체에서 아무 곳이나 검색하나?” 아니면 “특정 경로 몇 개만 자주 필터하나?”

문서 포함 검사(metadata @> '{"plan":"pro"}') 같은 경우에는 GIN이 보통 첫 선택입니다. 문서가 특정 모양을 포함하는지 묻는 작업에 최적화되어 있고, 키 존재 검사(?, ?|, ?&)도 지원합니다.

반면 앱이 주로 한두 개의 JSON 경로로 필터한다면 표현식 기반 B-tree 인덱스가 더 빠르고 작습니다. 또한 추출한 값으로 정렬하거나 숫자 비교가 필요할 때도 유리합니다.

-- 포함 및 키 검사에 대한 광범위한 지원
CREATE INDEX ON customers USING GIN (metadata);

-- 특정 JSON 경로에 대한 대상 필터 및 정렬
CREATE INDEX ON customers ((metadata->>'plan'));
CREATE INDEX ON events (((payload->>'amount')::numeric));

간단한 경험 법칙:

  • 여러 키, 태그, 중첩 구조를 사용자가 검색하면 GIN을 사용하세요.
  • 반복해서 특정 경로를 필터하면 표현식 B-tree 인덱스를 사용하세요.
  • 실제 화면에 나오는 것만 인덱스하세요, 모든 것을 인덱스하지 마세요.
  • 성능이 몇몇 JSON 키에 의존한다면 그 키를 실제 컬럼으로 옮기는 것을 고려하세요.

예: 지원 화면에서 metadata->>'priority'로 필터하고 created_at으로 정렬한다면 JSON 우선 경로(expression) 인덱스와 일반 created_at 인덱스를 만드세요. 사용자가 태그나 중첩 속성도 검색한다면 광범위한 GIN 인덱스를 추가 고려합니다.

지오 및 범위 쿼리: GiST가 적합한 경우

지오와 범위 화면에서는 GiST가 명확한 선택인 경우가 많습니다. GiST는 “이 객체가 겹치는가, 포함하는가, 그 위치가 이 점에 가까운가” 같은 질문을 빠르게 답하기 위해 만들어졌습니다.

지오 데이터는 보통 포인트(매장 위치), 라인(경로), 폴리곤(배송 구역) 등을 의미합니다. 흔한 화면은 “내 주변 매장”, “반경 X km 내의 일자리”, “이 지도 박스 안의 항목 표시”, “주소가 서비스 영역 내부인가?” 등입니다. GiST 인덱스(보통 PostGIS의 geometry/geography 타입을 통해)는 이러한 공간 연산자에서 대부분의 행을 건너뛰고 필요한 행만 검사하게 해줍니다.

범위도 비슷합니다. PostgreSQL의 daterange, int4range 같은 범위 타입은 겹침(overlap) 검사에 자주 쓰이며, GiST는 이러한 연산을 효율적으로 지원합니다. 그래서 캘린더·스케줄링·가용성 검사에 흔히 사용됩니다.

그래도 많은 지오 관련 화면에서 B-tree가 필요할 수 있습니다. 예: “우리 회사 배송만, 지난 7일치, 거리순” 같은 경우 GiST가 공간 조건을 처리하지만 B-tree는 테넌트나 상태 같은 선택적 필터와 정렬을 도와줍니다.

단계별 인덱스 선택 방법

Ship an admin panel
Add search, filters, and pagination to a web app with generated backend and UI.
Create app

인덱스 선택은 컬럼 이름보다 연산자에 관한 일입니다. 같은 컬럼이라도 =, >, LIKE 'prefix%', 전체 텍스트, JSON 포함, 지오 거리 등 어떤 연산을 쓰느냐에 따라 필요한 인덱스가 달라집니다.

쿼리를 체크리스트처럼 읽으세요: WHERE는 어떤 행이 자격이 되는지, JOIN은 테이블을 어떻게 연결하는지, ORDER BY는 출력 순서를, LIMIT는 실제로 몇 행이 필요한지를 결정합니다. 최적의 인덱스는 보통 처음 20행을 빠르게 찾도록 도와주는 것입니다.

대부분 화면에 통하는 간단한 절차:

  1. 화면이 사용하는 정확한 연산자를 적으세요(예: status =, created_at >=, name ILIKE, meta @>, ST_DWithin).
  2. 가장 선택적인 필터나 기본 정렬과 일치하는 인덱스부터 시도하세요. 화면이 created_at DESC로 정렬하면 거기서 시작합니다.
  3. 동일한 필터 조합이 자주 보일 때만 복합 인덱스를 추가하세요. 동등 비교 컬럼을 먼저, 그다음 범위 컬럼, 마지막으로 정렬 키를 두세요.
  4. 화면이 항상 일부 슬라이스만 본다면 partial index를 쓰고, 계산된 값을 쿼리하면 표현식 인덱스를 사용하세요(예: 대소문자 무시 검색을 위해 lower(email)).
  5. EXPLAIN ANALYZE로 검증하세요. 실행 시간과 읽은 행 수를 크게 줄이면 유지하세요.

구체적 예: 지원 대시보드가 status로 필터하고 최신순으로 정렬한다면 (status, created_at DESC) B-tree가 좋은 첫 시도입니다. 같은 화면이 meta @> '{"vip": true}' 같은 JSONB 플래그로도 필터하면 그 연산은 다른 인덱스가 필요합니다.

시간 낭비와 쓰기 지연을 초래하는 흔한 실수

Deploy where you need
Deploy your app to AppMaster Cloud, AWS, Azure, Google Cloud, or export source code.
Create project

잘못된 연산자에 맞는 “옳은” 인덱스를 골라 실망하는 경우가 흔합니다. PostgreSQL은 쿼리가 인덱스가 답하도록 만들어졌을 때만 인덱스를 사용합니다. 예를 들어 ILIKE '%term%'를 자주 쓰면서 텍스트 컬럼에 일반 B-tree만 만들면 그 인덱스는 사용되지 않고 테이블을 스캔하게 됩니다.

또 다른 함정은 “혹시 몰라” 하며 거대한 다중 열 인덱스를 만드는 것입니다. 안전해 보이지만 유지 비용이 크고 실제 쿼리 패턴과 맞지 않을 때가 많습니다. 왼쪽 열이 필터에 사용되지 않으면 인덱스의 나머지 부분도 도움이 되지 않습니다.

선택성이 낮은 컬럼에 대한 과도한 인덱스도 흔한 실수입니다. 부울이나 작은 enum에 대한 단독 B-tree는 거의 가치가 없습니다. 이럴 때는 partial index가 훨씬 효율적입니다.

JSONB는 또 다른 주의점이 있습니다. 광범위한 GIN 인덱스는 유연하지만, 많은 JSONB 경로 검사는 추출한 값에 대한 표현식 인덱스가 더 빠릅니다. 화면이 payload->>'customer_id'를 항상 필터한다면 그 표현식을 인덱스하는 것이 전체 문서를 인덱스하는 것보다 작고 빠릅니다.

마지막으로, 모든 추가 인덱스는 쓰기 부하를 늘립니다. 티켓이나 주문처럼 자주 업데이트되는 테이블에서는 인덱스가 많을수록 삽입·업데이트 비용과 VACUUM 압력이 증가합니다.

인덱스를 추가하기 전에 멈춰서 확인하세요:

  • 인덱스가 쿼리에서 실제로 사용하는 정확한 연산자와 일치하는가?
  • 넓은 다중 열 인덱스를 하나 또는 두 개의 집중된 인덱스로 대체할 수 있는가?
  • 이걸 partial index로 만들어 선택성 낮은 잡음을 피할 수 있는가?
  • JSONB라면 표현식 인덱스가 화면에 더 적합한가?
  • 테이블이 쓰기 중심이라 인덱스 비용이 이득보다 큰가?

인덱스를 추가(또는 유지)하기 전의 빠른 점검

새 인덱스를 만들기 전에 앱이 실제로 무엇을 하는지 구체적으로 파악하세요. "있으면 좋을" 인덱스는 종종 쓰기 성능과 저장공간만 늘리고 실질적 이득은 적습니다.

상위 3개의 실제 화면(또는 API 엔드포인트)을 골라 그들의 정확한 쿼리 형태를 적으세요: 필터, 정렬 순서, 사용자가 입력하는 내용까지. 많은 인덱스 문제는 쿼리가 무엇인지 불명확하기 때문에 생깁니다. B-tree vs GIN vs GiST를 논하기 전에 연산자를 명확히 하세요.

간단한 체크리스트:

  • 실제 화면 3개를 고르고 정확한 WHEREORDER BY 패턴(방향과 NULL 처리 포함)을 적으세요.
  • 연산자 유형을 확인하세요: 동등(=), 범위(>,BETWEEN), 접두사, 포함, 겹침, 거리 등.
  • 공통 화면 패턴당 하나의 인덱스를 선택하고 테스트한 뒤 실행 시간이나 읽는 페이지 수를 의미있게 줄이는 인덱스만 남기세요.
  • 테이블이 쓰기 중심이라면 더 엄격하게 하세요: 추가 인덱스는 쓰기 비용을 곱합니다.
  • 기능이 바뀐 후에는 다시 확인하세요. 새 필터, 새 기본 정렬, 또는 "starts with"에서 "contains"로 변경되면 기존 인덱스가 의미 없어질 수 있습니다.

예: 대시보드에 기본 정렬 last_activity DESC가 추가되었다면 status만 인덱스해 둔 상태에서는 필터는 빠를지 몰라도 정렬을 위해 추가 작업이 필요해질 수 있습니다.

예: 실제 앱 화면을 적절한 인덱스에 매핑하기

Design your database visually
Model tables in the Data Designer and let your indexes follow real UI queries.
Start building

결정표는 실제로 배포하는 화면에 매핑할 수 있을 때만 유용합니다. 아래는 세 가지 흔한 화면과 보통 맞는 인덱스 예시입니다.

화면쿼리 패턴보통 맞는 인덱스이유
관리자 목록: 필터 + 정렬 + 자유 텍스트 검색status = 'open' + created_at 정렬 + 제목/메모 검색(status, created_at) B-tree 및 tsvector에 대한 GIN필터·정렬은 B-tree, 전체 텍스트 검색은 보통 GIN.
고객 프로필: JSON 설정 + 플래그prefs->>'theme' = 'dark' 또는 키 존재 검사유연한 키 조회에는 JSONB 전체에 대한 GIN, 자주 쓰는 1-2개 키에는 표현식 B-tree많은 키를 검색하면 GIN, 몇 개의 고정 경로만 쓰면 표현식 인덱스.
근처 위치: 거리 + 카테고리 필터X km 내 장소, category_id로 필터geometry/geography에 대한 GiST 및 category_id에 대한 B-tree거리/내부 검사에는 GiST, 표준 필터는 B-tree.

적용하는 실용적 방법은 UI에서 시작하는 것입니다:

  • 결과를 좁히는 모든 컨트롤(필터)을 나열하세요.
  • 기본 정렬 순서를 적으세요.
  • 검색 동작이 전체 텍스트인지, 접두사인지, 포함인지 구체화하세요.
  • “특별한” 필드(JSONB, 지오, 범위)를 표시하세요.

다음 단계: 인덱싱을 개발 프로세스의 일부로 만드세요

좋은 인덱스는 사람들이 클릭하는 필터, 기대하는 정렬, 실제 사용하는 검색 상자와 함께 성장합니다. 개발 과정에서 인덱싱을 습관으로 삼으면 나중에 성능 문제 대부분을 피할 수 있습니다.

반복 가능한 방식으로 하세요: 화면이 실행하는 1~3개의 쿼리를 식별하고, 그 쿼리와 일치하는 가장 작은 인덱스를 추가한 뒤, 현실적인 데이터로 테스트하고 효과가 없으면 제거하세요.

내부 툴이나 고객 포털을 만든다면 인덱스 필요를 일찍 계획하세요. 이런 앱은 필터와 목록 화면이 늘어나며, 초기에 잘 설계하면 나중에 성능을 잡기 쉽습니다. AppMaster (appmaster.io)로 개발할 경우, 각 화면의 필터와 정렬 구성을 구체적인 쿼리 패턴으로 취급하고 실제 클릭을 지원하는 인덱스만 추가하는 것이 도움이 됩니다.

자주 묻는 질문

How do I choose between B-tree, GIN, and GiST for a real screen?

가장 바쁜 화면들이 SQL 관점에서 실제로 어떤 일을 하는지(사용되는 WHERE 연산자, ORDER BY, LIMIT) 적어보는 것부터 시작하세요. 일반적으로 =/범위/정렬에는 B-tree가, 문서 내 포함 여부(contains)나 전체 텍스트/JSONB 포함 검색에는 GIN이, 겹침(overlap)·거리·영역 관련 쿼리에는 GiST가 적합합니다.

When is a B-tree index the right choice?

값 일치 필터, 범위 필터, 또는 특정 정렬이 필요할 때 B-tree가 적절합니다. 대시보드·관리자 목록·페이지네이션 등 “필터 → 정렬 → 제한” 패턴에 자주 쓰입니다.

When should I use a GIN index?

각 행이 여러 키나 용어와 매칭될 수 있고, 쿼리가 “이 행이 X를 포함하는가?”를 묻는 경우 GIN을 사용하세요. tsvector에 대한 전체 텍스트 검색(@@)이나 JSONB/배열의 포함 연산자(@> 등)에 흔히 쓰입니다.

What is GiST best for in PostgreSQL?

GiST는 자연스러운 순서(order)가 없는 데이터에 좋습니다. 근접성, 겹침, 포함 같은 질의를 다루는 경우 적합하며, PostGIS의 “내 주변” 쿼리나 범위 타입의 겹침 검사에 흔히 사용됩니다.

How do I order columns in a composite B-tree index?

쿼리가 필터와 정렬을 모두 한다면, 동등 비교(예: user_id = ...)를 먼저 두고 그다음 범위, 그리고 정렬 키 순으로 인덱스 열 순서를 정하세요. 예: (user_id, status, created_at DESC)user_idstatus로 항상 필터링하고 최신 순으로 보여줄 때 유용합니다.

When does a partial index make sense?

화면이 항상 특정 부분집합(예: status = 'open', soft-deleted가 아닌 것 등)을 본다면 partial index가 유리합니다. 인덱스가 더 작아지고 불필요한 행을 포함하지 않아 유지비가 줄어듭니다.

Should I index low-cardinality columns like booleans or status?

단독으로는 많은 행에 매칭되는 부울 값이나 작은 열거형에 대한 일반 인덱스는 기대만큼 도움이 되지 않습니다. tenant_id처럼 선택성이 높은 열과 결합하거나 partial index로 원하는 슬라이스만 대상으로 할 때만 가치가 있습니다.

For JSONB, when do I choose GIN vs an expression B-tree index?

전체 JSONB 컬럼에 유연한 포함/키 검사를 자주 한다면 GIN을 사용하세요. 반면 특정 JSON 경로 몇 개를 반복해서 필터하거나 정렬한다면, (metadata->>'plan') 같은 표현(expression) B-tree 인덱스가 더 작고 빠릅니다.

Why doesn’t my index help with ILIKE '%term%' searches?

접두사 검색(email LIKE 'abc%')은 문자열 정렬과 맞아 B-tree가 도움이 됩니다. 하지만 ILIKE '%abc%' 같은 포함 검색에는 일반 B-tree가 거의 사용되지 않습니다. 그런 경우에는 트라이그램 인덱스 등 다른 접근이 필요합니다.

What’s the safest way to add indexes without slowing down writes?

특정 고부하 쿼리 패턴을 정확히 지원하는 가장 작은 인덱스를 만들고 EXPLAIN ANALYZE로 검증하세요. 너무 많은 인덱스는 쓰기 성능을 떨어뜨리므로, 실제로 트래픽이 많은 쿼리만 골라 인덱스화해야 합니다.

쉬운 시작
멋진만들기

무료 요금제로 AppMaster를 사용해 보세요.
준비가 되면 적절한 구독을 선택할 수 있습니다.

시작하다