PostgreSQL 인덱스 진단 (안 쓰는 인덱스·중복 찾기)

브라우저 안에서만 처리됩니다. 입력한 내용과 선택한 파일은 서버로 전송되지 않습니다. 익명 방문 통계만 집계합니다.

인덱스 사용 통계는 리셋 이후 누적입니다(기간이 아닙니다) — 레플리카는 자신이 처리한 조회만 셉니다. 아래 결과는 전부 검토 후보이지 결론이 아닙니다.

① 인덱스 통계 (필수)

진단 SQL

SELECT
  psi.schemaname AS schema_name,
  psi.relname AS table_name,
  psi.indexrelname AS index_name,
  psi.idx_scan AS idx_scan,
  pg_relation_size(psi.indexrelid) AS index_size_bytes,
  pg_get_indexdef(psi.indexrelid) AS index_def,
  pgi.indisunique AS is_unique
FROM pg_stat_user_indexes AS psi
JOIN pg_index AS pgi ON pgi.indexrelid = psi.indexrelid
ORDER BY psi.idx_scan ASC, index_size_bytes DESC;

복사한 SQL 을 psql --csv -f-(권장 — 파싱이 가장 견고합니다)로 실행하거나, psql 안에서 그대로 실행한 기본 출력도 지원합니다. 출력 전체를 아래에 붙여넣으세요.

필요 권한/확장 — 확장·특수 권한 불필요 — 해당 DB 접속 권한이면 조회 가능(카탈로그 기본 접근).

idx_scan=0 은 "검토 후보"일 뿐입니다 — 리셋 이후 누적이거나 레플리카는 자신의 idx_scan 만 셉니다. 유니크 인덱스는 제약 목적일 수 있어 삭제 후보에서 별도 취급합니다.

② 테이블 통계 (선택 — 붙여넣으면 seq scan 분석 추가)

진단 SQL

SELECT
  t.schemaname AS schema_name,
  t.relname AS table_name,
  t.seq_scan AS seq_scan,
  t.idx_scan AS idx_scan,
  t.n_live_tup AS n_live_tup,
  t.n_dead_tup AS n_dead_tup,
  t.last_vacuum AS last_vacuum,
  t.last_autovacuum AS last_autovacuum,
  t.autovacuum_count AS autovacuum_count,
  d.stats_reset AS stats_reset
FROM pg_stat_user_tables AS t
CROSS JOIN (SELECT stats_reset FROM pg_stat_database WHERE datname = current_database()) AS d
ORDER BY t.n_dead_tup DESC;

복사한 SQL 을 psql --csv -f-(권장 — 파싱이 가장 견고합니다)로 실행하거나, psql 안에서 그대로 실행한 기본 출력도 지원합니다. 출력 전체를 아래에 붙여넣으세요.

필요 권한/확장 — 확장·특수 권한 불필요 — 해당 DB 접속 권한이면 조회 가능(카탈로그 기본 접근).

pgindex(seq_scan·idx_scan) 와 pgvacuum(n_live_tup·n_dead_tup·last_(auto)vacuum·autovacuum_count) 이 이 SQL 하나를 같이 씁니다. stats_reset 은 DB 전체 기준이라 테이블별 리셋 시각이 아닙니다 — 초기화 직후처럼 리셋이 없었던 DB 에서는 비어 있을 수 있습니다.

사용법

  1. ① SQL 카드의 복사 버튼으로 인덱스 진단 SQL(pg_stat_user_indexes 조회)을 복사해 psql 에서 실행하고, 출력을 붙여넣습니다.
  2. 테이블 통계까지 보고 싶다면 ② 진단 SQL(pg_stat_user_tables 조회)도 같은 방식으로 실행해 붙여넣습니다 — 선택입니다, 안 넣어도 ①만으로 미사용·중복 인덱스는 나옵니다.
  3. 붙여넣으면 즉시 미사용 검토 후보 · 중복 프리픽스 후보 · (② 를 넣었다면) 시퀀셜 스캔 배율까지 분석됩니다.

자주 묻는 질문

붙여넣은 인덱스·테이블 정보가 서버로 전송되나요?

아니요. 붙여넣은 내용은 이 브라우저 안에서만 파싱·분석되고 어디로도 전송되지 않습니다.

idx_scan=0 인 인덱스는 바로 지워도 되나요?

아니요, "검토 후보"일 뿐입니다. 통계가 최근에 리셋됐거나(pg_stat_reset), 이 서버가 레플리카라면 자신이 처리한 조회만 셈해 실제로는 쓰이는 인덱스일 수 있습니다. 드물게만 실행되는 배치·리포트 쿼리가 이 인덱스를 쓰는 경우도 있으니, 삭제 전에 애플리케이션 코드에서 실제로 이 인덱스를 겨냥하는 쿼리가 없는지 확인하세요.

유니크 인덱스는 왜 미사용 후보에서 빠지나요?

유니크 인덱스는 조회 성능이 아니라 중복 방지라는 제약(constraint) 역할을 겸하는 경우가 많습니다. idx_scan=0 이어도 그 제약 자체가 필요해서 존재하는 것일 수 있어, 스캔 통계만으로 "안 쓴다"고 단정하지 않습니다.

"중복 프리픽스" 는 무슨 뜻이고, 어떻게 판정하나요?

같은 테이블에 (a) 인덱스와 (a, b) 인덱스가 함께 있으면, (a) 는 (a, b) 의 선행 컬럼과 겹치는 부분집합(prefix)이라 대부분의 조회에서 (a, b) 하나로 대체될 수 있습니다. indexdef 의 컬럼 목록을 그대로 비교해 이 접두어 관계만 찾고, WHERE 절이 있는 부분 인덱스는 조건이 다르면 같은 관계로 볼 수 없어 비교 대상에서 뺍니다.

시퀀셜 스캔 배율은 왜 "과다" 라고 판정해 주지 않나요?

몇 배부터 문제인지는 워크로드마다 다르고, PostgreSQL 공식 문서·wiki 어디에도 정해진 기준이 없습니다. 근거 없는 임계값을 들이대는 대신 seq scan ÷ idx scan 배율과 테이블 크기(추정 행 수)를 그대로 보여드리니, 실제 쿼리 패턴을 아는 분이 직접 판단해 주세요.

중복 프리픽스 검사가 정렬 순서(DESC)나 INCLUDE 컬럼도 고려하나요?

아니요. 컬럼 리스트는 인덱스 정의문의 텍스트를 그대로 비교하기 때문에, 같은 컬럼이라도 DESC·NULLS FIRST 같은 정렬 수식어나 연산자 클래스(opclass)가 붙으면 다른 문자열로 취급되어 후보에서 빠질 수 있습니다. INCLUDE(...) 로 덧붙인 컬럼은 애초에 비교 대상(USING 뒤 첫 괄호)에 들어가지 않습니다. 셋 다 "실제로는 겹치는데 후보에서 놓치는" 방향이라(반대로 겹치지 않는데 후보로 잘못 묶는 경우는 없습니다) 안전한 쪽으로 보수적입니다 — 이런 경우까지 확인하려면 indexdef 를 직접 비교해 주세요. B-tree 인덱스끼리만 비교하는 것도 같은 이유입니다(PostgreSQL 공식 문서가 "선행 컬럼 부분집합으로도 쓸 수 있다"는 원리를 B-tree 에 한정해 설명하기 때문에, GIN 등 다른 access method 쌍은 비교하지 않습니다).