본문 바로가기
Database2026년 8월 18일5분 읽기

카디널리티 추정 — 플래너가 행 수를 예측하는 법

YS
김영삼
조회 5
카디널리티 추정 — 플래너가 행 수를 예측하는 법

PostgreSQL 플래너가 어떤 실행 계획을 고를지는 결국 "이 조건에 몇 행이나 맞을까"라는 추정에 달려 있습니다. 이 추정을 카디널리티 추정(cardinality estimation)이라 하고, 그 근거가 되는 것이 ANALYZE가 수집해 pg_statistic에 저장하는 통계입니다. 추정이 맞으면 좋은 계획이 나오고, 추정이 크게 빗나가면 인덱스를 두고도 순차 스캔을 하거나, 중첩 루프로 수백만 행을 돌리는 참사가 벌어집니다.

"인덱스도 있는데 왜 안 타?"의 진짜 원인은 인덱스가 아니라 틀린 행 수 추정인 경우가 정말 많습니다. 저도 이걸 늦게 깨달았습니다.

플래너는 무엇을 보고 추정하나

ANALYZE는 테이블을 표본 추출해 컬럼별로 몇 가지 통계를 저장합니다. 대표적으로:

  • n_distinct — 그 컬럼의 서로 다른 값의 개수(선택도의 기준)
  • MCV(most common values) — 가장 자주 나오는 값들과 그 빈도
  • 히스토그램 — 값의 분포(범위 조건 >, BETWEEN 추정에 사용)
  • correlation — 컬럼 값 순서와 물리적 저장 순서의 상관(인덱스 스캔 비용에 영향)

이 통계는 pg_stats 뷰로 사람이 읽기 좋게 볼 수 있습니다.

SELECT attname, n_distinct, most_common_vals, correlation
FROM pg_stats
WHERE tablename = 'orders' AND attname IN ('status','customer_id');

추정이 맞는지 보는 법 — 계획 대 현실

EXPLAIN ANALYZE는 각 노드에 추정 행 수(rows=)실제 행 수(actual rows=)를 나란히 보여줍니다. 이 둘의 차이가 문제의 출발점입니다.

EXPLAIN ANALYZE
SELECT * FROM orders WHERE status = 'shipped' AND region = 'KR';
-- Seq Scan on orders
--   (cost=... rows=50 ...) (actual ... rows=48000 ...)
--                  ▲ 추정 50행                 ▲ 실제 48000행  ← 1000배 오차!

추정 50, 실제 48000. 이렇게 어긋나면 플래너는 "50행밖에 안 되니 중첩 루프로 붙이자"고 판단했다가 실제로는 4만 8천 번을 도는 재앙을 맞습니다. 이 추정/실제 비율을 보는 습관이 쿼리 튜닝의 절반입니다.

추정이 틀어지는 3대 원인

원인증상처방
오래된 통계대량 적재 직후 계획이 이상함ANALYZE 수동 실행
컬럼 간 상관AND 조건 결합 시 과소추정CREATE STATISTICS(확장 통계)
표본 부족치우친 분포를 못 잡음SET STATISTICS로 표본 확대

두 번째가 특히 교묘합니다. 플래너는 기본적으로 컬럼들이 독립이라 가정합니다. 그래서 city='서울' AND region='경기'처럼 서로 모순되거나 강하게 얽힌 조건을 곱셈으로 계산하다 심하게 과소추정합니다. 이럴 때 확장 통계가 답입니다.

-- 상관된 컬럼들의 함수 종속·분포를 함께 학습시킨다
CREATE STATISTICS orders_geo (dependencies, ndistinct)
  ON city, region FROM orders;
ANALYZE orders;
-- 치우친 컬럼은 표본(버킷 수)을 늘려 정밀도 향상
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 1000;
ANALYZE orders;

자주 묻는 질문

ANALYZE는 언제 자동으로 도나요?

autovacuum이 변경된 행 수가 임계치를 넘으면 자동 ANALYZE를 걸어줍니다. 다만 대량 INSERT/COPY 직후나 임시 테이블은 자동 타이밍을 못 기다리는 경우가 많으니, 그럴 땐 수동 ANALYZE 테이블;을 명시적으로 돌리는 게 안전합니다.

default_statistics_target을 무작정 올리면 좋나요?

정밀도는 오르지만 ANALYZE 비용과 계획 수립 시간이 늘고 통계 저장 공간도 커집니다. 전역으로 크게 올리기보다, 문제되는 특정 컬럼만 SET STATISTICS로 국소적으로 올리는 편이 낫습니다.

추정이 맞는데도 계획이 나쁘면요?

그건 통계가 아니라 비용 파라미터(random_page_cost 등)나 인덱스 설계 문제일 수 있습니다. 추정/실제가 근사한데 느리면 통계 튜닝을 멈추고 다른 원인을 봐야 합니다. 추정부터 검증하는 이유가 이겁니다.

n_distinct가 실제와 다르게 나옵니다.

표본 기반이라 값이 아주 많은 컬럼에서 어긋날 수 있습니다. 이럴 땐 ALTER TABLE ... ALTER COLUMN ... SET (n_distinct = -0.5)처럼 직접 힌트를 심어 플래너 추정을 바로잡을 수 있습니다(음수는 행 수 대비 비율).

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록