(KO) PostgreSQL 통계 — ANALYZE, pg_statistic, 확장 통계
목차
- 이론적 배경
- DBMS 공통 설계
- PostgreSQL의 접근 방식
- 소스 코드 워크스루
- 소스 검증 (2026-06-05 기준)
- PostgreSQL 너머 — 비교 설계와 연구 전선
- 출처
이론적 배경
섹션 제목: “이론적 배경”비용 기반 옵티마이저는 인덱스 스캔과 순차 스캔 중 하나를, 또는 중첩 루프 조인과 해시 조인 중 하나를 고르기 위해 각 연산자가 방출할 행 수를 먼저 추정해야 한다. 이 추정값이 **카디널리티 추정(cardinality estimate)**이다. 비용은 행 수에 대략 선형으로 비례하므로, 이 수치가 한 자릿수만 틀려도 플랜 전체가 한 자릿수 오차를 갖게 된다. 카디널리티 추정은 다시 **선택도(selectivity)**에서 도출된다. 선택도란 WHERE 절이 통과시킬 행의 비율이다.
Database System Concepts (Silberschatz, Korth & Sudarshan, 7판), 16장 “Query Optimization”, §16.3 “Statistical Information for Cost Estimation”은 옵티마이저가 릴레이션과 애트리뷰트별로 유지하는 카탈로그 수량을 나열한다. 그 중 §16.3.1의 핵심 두 가지는 다음과 같다.
- n_r — “릴레이션 r의 튜플 수.”
- V(A, r) — “릴레이션 r의 애트리뷰트 A에 나타나는 서로 다른 값의 수.” 균등 분포 가정 아래 동등 조건 선택도는
1 / V(A, r)이고, 예상 결과 튜플 수는n_r / V(A, r)이다(§16.3.2).
하지만 균등 분포는 현실에서 성립하지 않는 경우가 많다. 교재 역시 이를 인정하며 히스토그램을 표준 보완책으로 제시한다. §16.3.1은 **히스토그램(histogram)**을 “애트리뷰트 값을 여러 범위로 나누고 각 범위에 해당하는 튜플 수를 연결한 구조”로 정의한 뒤 두 종류를 구분한다.
- 등너비 히스토그램(equi-width histogram) — 범위를 동일한 너비로 나눈다.
- 등깊이 히스토그램(equi-depth histogram) — 각 범위에 동일한 수의 값이 오도록 경계를 조정한다. 교재는 “등깊이 히스토그램이 등너비보다 더 나은 추정을 제공하므로 선호된다”고 명시한다.
교재는 또한 PostgreSQL이 가장 적극적으로 활용하는 실용적 개선책, 즉 빈도 높은 값을 따로 분리하는 방식도 설명한다. 히스토그램이 “가장 자주 등장하는 값(most common values)과 그 카운트를 함께 저장”하면 히스토그램 본체는 빈도가 낮은 잔여 값만 묘사하게 된다(§16.3.1). 통계 수집 방법에 대해서도 §16.3.4는 전수 스캔이 불필요하다고 명시한다. “수천 개 튜플의 표본으로도 충분히 정확한 히스토그램을 구성할 수 있다.” 즉, 전수 조사가 아닌 표본 추출이 기본 방법이다.
아래 내용 전체를 이해하는 데 필요한 이론적 사실 두 가지를 짚어 둔다. 첫째, 표본에서 V(A, r) — 고유값 수 — 를 추정하는 것은 본질적으로 어렵다. 표본에 한 번 등장한 값이 테이블 전체에서 유일한 값일 수도 있고, 수백만 개 중 하나일 수도 있기 때문이다. 통계학 문헌(Haas & Stokes, “Estimating the number of classes in a finite population”, 그리고 PostgreSQL 소스에 직접 인용된 IBM Research Report RJ 10025)은 편향 보정 추정치를 제시하고, PostgreSQL은 이를 그대로 채택한다. 둘째, 여러 컬럼에 걸친 조건의 결합 선택도 P(a AND b)를 구할 때 보편적으로 쓰이는 가정이 독립성(independence), 즉 P(a,b) = P(a) * P(b)다. 컬럼들이 실제로 상관되어 있을 때(도시 → 주(州), 모델 → 제조사) 독립성 곱은 지나치게 작은 값이 나온다. 이 구조적 오차는 열별 정확도를 아무리 높여도 해결되지 않는다. **확장 통계(extended statistics)**가 존재하는 이유가 바로 이것이다.
DBMS 공통 설계
섹션 제목: “DBMS 공통 설계”시스템을 막론하고 통계 기계장치는 동일한 구성 요소로 분해되며, 설계 선택지도 몇 가지 축을 중심으로 모인다.
표본 추출, 전수 스캔 아님
섹션 제목: “표본 추출, 전수 스캔 아님”전수 통계는 정확하지만 ANALYZE마다 전체 스캔 비용이 든다. 테라바이트 테이블에서 이는 현실적이지 않다. 운영 환경의 모든 옵티마이저는 표본을 사용한다. 설계 질문은 어떻게 표본을 추출할 것인가(균일 행 표본 vs. 블록 표본 vs. 두 단계 방식), 얼마나 추출할 것인가(고정 예산 vs. 테이블 비율), 편향 여부다. 블록 수준 표본 추출은 페이지 I/O를 크게 줄이지만 상관된 행이 뭉쳐 있다. 순수 행 표본은 편향이 없지만 I/O 부담이 크다. 예산 크기에 대해서는 이론적으로 깔끔한 답이 있다. Chaudhuri, Motwani & Narasayya(SIGMOD 1998, “Random sampling for histogram construction: how much is enough?”)는 필요 표본 크기가 히스토그램 해상도에는 비례하지만 테이블 크기에 대해서는 로그 증가에 그침을 보였다. 따라서 target당 고정 예산이면 충분하다.
열별 압축 레코드 하나
섹션 제목: “열별 압축 레코드 하나”표준 출력물은 열마다 작은 고정 형태 레코드로, 널 비율, 평균 너비, 고유값 수, 가장 빈번한 값(MCV, most-common-value) 목록과 빈도, 나머지 값의 히스토그램을 담는다. MCV와 히스토그램을 분리하는 방식은 거의 보편적이다. 왜냐하면 비대칭 분포의 뾰족한 머리(MCVs)와 매끄러운 꼬리(히스토그램)를 전체 빈도표를 저장하지 않고도 포착할 수 있기 때문이다. 물리/논리 **상관계수(correlation)**는 PostgreSQL 고유의 추가 정보로, 코스트 모델이 인덱스 스캔 시 힙 가져오기의 순차성을 판단하는 데 쓰인다.
고유값 추정은 까다로운 문제
섹션 제목: “고유값 추정은 까다로운 문제”V(A, r)은 동등 선택도를 좌우하면서도 표본에서 복원하기 가장 어려운 수량이다. 그래서 시스템들은 (a) 표본 기반 추정기(Haas-Stokes 계열), (b) HyperLogLog 같은 스케치로 정확하게 유지, (c) DBA가 직접 지정, 세 가지 중 하나를 택한다. PostgreSQL은 (a)에 수동 재정의 탈출구를 추가한 방식을 사용한다.
독립성, 그리고 그 극복
섹션 제목: “독립성, 그리고 그 극복”기본 결합 선택도 모델은 독립성이다. 추가 저장 공간이 필요 없고 조합이 간단하다. 상관된 컬럼에서 실패한다는 사실은 잘 알려져 있으며, 현대적 해답은 선택적 다중 컬럼 통계다. DBA가 상관 관계가 있는 컬럼 그룹을 선언하면 시스템이 해당 그룹에 대해서만 결합 정보(다중 컬럼 고유값 수, 다중 컬럼 MCV 목록, 함수 종속성 요약)를 저장한다. SQL Server “multi-column statistics” / Oracle “extended statistics” / PostgreSQL “CREATE STATISTICS”가 이 계열이다. 모든 컬럼 조합의 경우의 수가 지수적이기 때문에 선택적 방식을 취한다.
이론 ↔ PostgreSQL 매핑
섹션 제목: “이론 ↔ PostgreSQL 매핑”| 교재 개념 (DSC §16.3) | PostgreSQL 구현체 |
|---|---|
| n_r (릴레이션 카디널리티) | pg_class.reltuples (VACUUM이 설정, rel->tuples로 읽음) |
| V(A, r) (고유값 수) | pg_statistic.stadistinct; compute_scalar_stats의 Haas-Stokes Duj1 추정기 |
| 선택도 1/V(A,r) | var_eq_const 폴백 (1 - sumcommon - nullfrac)/otherdistinct |
| 가장 빈번한 값 | STATISTIC_KIND_MCV 슬롯; analyze_mcv_list 컷오프 |
| 등깊이 히스토그램 | STATISTIC_KIND_HISTOGRAM 슬롯; 정렬된 잔여분의 등간격 분위수 |
| 수천 튜플 표본 추출 | acquire_sample_rows, targrows = 300 * statistics_target |
| 다중 컬럼 상관 오차 교정 | 확장 통계: pg_statistic_ext / statistics/ (ndistinct, dependencies, mcv) |
PostgreSQL의 접근 방식
섹션 제목: “PostgreSQL의 접근 방식”PostgreSQL은 통계 세계를 카탈로그에서만 만나는 두 반쪽으로 나눈다.
생성 반쪽은 ANALYZE다(autovacuum 내부 실행 및 VACUUM ANALYZE의 한 단계로도 동작한다). analyze_rel이 릴레이션 잠금을 획득하고 do_analyze_rel로 분기한다. do_analyze_rel은 (1) 처리할 컬럼을 결정하고 examine_attribute로 컬럼별 VacAttrStats를 구성한다. (2) 표본 크기 예산 targrows를 각 컬럼 minrows의 최댓값으로 계산한다. (3) acquire_sample_rows로 공유 표본을 하나 수집한다. (4) 해당 표본으로 컬럼별 compute_stats 콜백을 실행한다. (5) update_attstats로 결과를 pg_statistic에 기록한다. (6) 동일 표본에서 BuildRelationExtStatistics를 호출해 선언된 확장 통계 객체를 계산한다.
소비 반쪽은 utils/adt/selfuncs.c의 선택도 함수들이다. 플래너의 clauselist_selectivity 기계장치(담당: postgres-cost-model.md)가 이를 호출한다. eqsel / scalarltsel 등이 get_attstatsslot으로 pg_statistic 행을 가져와 MCV 목록, 히스토그램, stadistinct를 [0,1] 범위의 수치로 변환한다.
미리 짚어 둘 설계 결정 세 가지가 있다.
테이블 비율이 아닌 고정 표본 예산
섹션 제목: “테이블 비율이 아닌 고정 표본 예산”std_typanalyze는 소스 내 주석에서 Chaudhuri-Motwani-Narasayya Corollary 1 to Theorem 5를 직접 인용하며 stats->minrows = 300 * stats->attstattarget으로 설정한다. 기본값 default_statistics_target = 100에서 이는 30,000행이다. 테이블이 천 행이든 조 단위든 상관없이 동일하다. “300×” 상수가 테이블 크기 항의 로그 항을 흡수하므로 예산을 n에 따라 키울 필요가 없다.
pg_statistic 행당 다섯 개의 stat-kind 슬롯
섹션 제목: “pg_statistic 행당 다섯 개의 stat-kind 슬롯”pg_statistic 튜플은 다섯 개의 범용 (stakind, staop, stacoll, stanumbers[], stavalues[]) 슬롯을 가진다(STATISTIC_NUM_SLOTS = 5). 슬롯 종류는 작은 정수로 표현된다. STATISTIC_KIND_MCV = 1, STATISTIC_KIND_HISTOGRAM = 2, STATISTIC_KIND_CORRELATION = 3(배열과 범위 타입을 위한 MCELEM/RANGE 종류도 있다). 이 슬롯 체계 덕분에 타입별 typanalyze가 스키마 변경 없이 임의 형태의 통계를 저장할 수 있다. 스칼라 컬럼은 MCV + 히스토그램 + 상관계수를, 배열 컬럼은 최빈 요소 슬롯을 채우는 식이다.
stadistinct: 부호가 의미를 갖는 필드
섹션 제목: “stadistinct: 부호가 의미를 갖는 필드”stadistinct는 float4 하나에 두 가지 의미를 담는다. 양수는 절대 고유값 수다. 음수는 행 수의 비율이다. stadistinct = -1은 “고유”(고유값 수가 행 수에 1:1 비례), -0.5는 “행 수의 절반만큼 고유값 존재”를 뜻한다. compute_scalar_stats는 절대 추정값이 테이블의 10%를 초과하면 상대 형식으로 전환한다. 이렇게 하면 ANALYZE 실행 간에 테이블이 커져도 고유값 수가 행 수를 따라간다.
네 가지 스칼라 compute_stats 루틴
섹션 제목: “네 가지 스칼라 compute_stats 루틴”std_typanalyze는 컬럼 타입이 제공하는 연산자에 따라 세 가지 표준 알고리즘 중 하나를 선택한다.
compute_scalar_stats—<와=둘 다 존재할 때(정렬 가능 타입, 일반적 경우). 널 비율, 너비,stadistinct, MCV, 히스토그램, 상관계수를 생성한다.compute_distinct_stats—=만 존재할 때(해시 가능하지만 정렬 불가). 히스토그램과 상관계수 제외한 나머지를 생성한다.compute_trivial_stats— 둘 다 없을 때. 널 비율과 너비만 생성한다.
(타입이 자체 typanalyze를 제공할 수 있다. tsvector의 ts_typanalyze, 배열의 array_typanalyze 등이 그 예다. 기본 경로는 위 세 가지가 담당한다.)
확장 통계: 선택적 결합 정보
섹션 제목: “확장 통계: 선택적 결합 정보”CREATE STATISTICS s (ndistinct, dependencies, mcv) ON a, b FROM t는 pg_statistic_ext 카탈로그 행을 등록한다. ANALYZE 중 BuildRelationExtStatistics가 등록된 객체를 순회하며, 요청된 종류 문자(STATS_EXT_NDISTINCT = 'd', STATS_EXT_DEPENDENCIES = 'f', STATS_EXT_MCV = 'm', STATS_EXT_EXPRESSIONS = 'e')마다 대응 빌더를 호출하고 결과를 pg_statistic_ext_data에 직렬화한다. 플래닝 시점에 statext_clauselist_selectivity와 dependencies_clauselist_selectivity가 이를 참조해 절 그룹을 함께 추정한다. 독립성 기반 열별 경로는 남은 절에 대해서만 실행된다.
소스 코드 워크스루
섹션 제목: “소스 코드 워크스루”안정적 핸들은 심볼 이름이지 줄 번호가 아니다. 함수명·구조체명은 대부분의 리팩터링에서 살아남지만 줄 번호는 헤더 삽입 한 번에 밀린다. 아래 위치 힌트 테이블의 줄 번호는 커밋
273fe94(REL_18_STABLE, 2026-06-05) 기준 관측값이다.git grep -n '<symbol>' src/backend/로 재확인할 수 있다.
워크스루는 데이터 흐름을 따라간다. 먼저 생성 경로(ANALYZE → pg_statistic), 다음으로 소비 경로(selfuncs.c), 마지막으로 확장 통계 빌더와 추정기 순이다.
flowchart TD
AR["analyze_rel()<br/>릴레이션 잠금, acquirefunc 선택"] --> DAR["do_analyze_rel()"]
DAR --> EA["examine_attribute()<br/>컬럼별 VacAttrStats 구성<br/>std_typanalyze가 compute_stats 선택"]
DAR --> CER["ComputeExtStatisticsRows()<br/>확장 통계를 위해 targrows 상향"]
EA --> ASR["acquire_sample_rows()<br/>두 단계 블록 표본 + Vitter<br/>targrows = max(300*stattarget)"]
CER --> ASR
ASR --> CS["컬럼별 compute_stats():<br/>compute_scalar_stats /<br/>compute_distinct_stats /<br/>compute_trivial_stats"]
CS --> UA["update_attstats()<br/>pg_statistic 행 기록"]
ASR --> BRE["BuildRelationExtStatistics()<br/>ndistinct / dependencies / mcv<br/>→ pg_statistic_ext_data"]
UA --> CAT[("pg_statistic")]
BRE --> CATX[("pg_statistic_ext_data")]
진입과 표본 추출 (analyze.c)
섹션 제목: “진입과 표본 추출 (analyze.c)”analyze_rel은 명령 수준 진입점이다. 잠금을 획득하고, pg_statistic 자체 분석을 거부하며, 획득 함수를 선택한다(힙 기본값은 acquire_sample_rows). do_analyze_rel은 표본 예산을 계산한다. 예산은 분석 대상 컬럼 전체의 minrows 최댓값이며, 100을 하한으로 하고 확장 통계 요구로 추가 상향된다.
// do_analyze_rel — analyze.c (표본 예산 계산)targrows = 100;for (i = 0; i < attr_cnt; i++){ if (targrows < vacattrstats[i]->minrows) targrows = vacattrstats[i]->minrows;}/* ... 인덱스 표현식 컬럼도 고려됨 ... */minrows = ComputeExtStatisticsRows(onerel, attr_cnt, vacattrstats);if (targrows < minrows) targrows = minrows;표본 추출은 헤더 주석에 명시된 두 단계 방식이다. 1단계(BlockSampler_Init / BlockSampler_Next, ReadStream으로 구동)는 최대 targrows개의 랜덤 블록을 고른다. 2단계는 해당 블록의 행들에 Vitter 저수지 알고리즘을 적용해 단일 패스로 모든 행이 동일 선택 확률을 갖도록 한다.
// acquire_sample_rows — analyze.c (Vitter 저수지 핵심)if (numrows < targrows) rows[numrows++] = ExecCopySlotHeapTuple(slot);else{ if (rowstoskip < 0) rowstoskip = reservoir_get_next_S(&rstate, samplerows, targrows);
if (rowstoskip <= 0) { /* Found a suitable tuple, replacing one old tuple at random */ int k = (int) (targrows * sampler_random_fract(&rstate.randstate)); Assert(k >= 0 && k < targrows); heap_freetuple(rows[k]); rows[k] = ExecCopySlotHeapTuple(slot); } rowstoskip -= 1;}samplerows += 1;수집된 행은 **물리 순서(item-pointer 순)**로 반환된다. 마지막의 qsort_interruptible(rows, ..., compare_rows, ...)가 이를 보장하며, compute_scalar_stats가 물리 순서를 이용해 상관계수를 측정하기 때문이다.
알고리즘 선택 (std_typanalyze)
섹션 제목: “알고리즘 선택 (std_typanalyze)”examine_attribute가 VacAttrStats를 구성하고, 타입에 커스텀 typanalyze가 없으면 std_typanalyze를 호출한다. std_typanalyze는 compute_stats 콜백을 선택하고 Chaudhuri-Motwani-Narasayya 한계에서 minrows를 설정한다.
// std_typanalyze — analyze.cif (OidIsValid(eqopr) && OidIsValid(ltopr)){ /* Seems to be a scalar datatype */ stats->compute_stats = compute_scalar_stats; /* r = 4 * k * ln(2n/gamma) / f^2 ; with f=0.5,gamma=0.01 => ~300*k */ stats->minrows = 300 * stats->attstattarget;}else if (OidIsValid(eqopr)){ /* We can still recognize distinct values */ stats->compute_stats = compute_distinct_stats; stats->minrows = 300 * stats->attstattarget;}else{ /* Can't do much but the trivial stuff */ stats->compute_stats = compute_trivial_stats; stats->minrows = 300 * stats->attstattarget;}attstattarget은 이미 확정된 값이다. examine_attribute가 컬럼별 pg_attribute.attstattarget을 읽어 null이면 -1(“기본값 사용”)로 처리하고, 0이면 컬럼을 아예 비활성화한다(examine_attribute가 NULL을 반환). 값이 여전히 음수이면 std_typanalyze가 default_statistics_target으로 대체한다.
고유값 추정기 (compute_scalar_stats)
섹션 제목: “고유값 추정기 (compute_scalar_stats)”표본을 정렬한 뒤 compute_scalar_stats는 ndistinct, nmultiple(두 번 이상 등장한 값 수), toowide_cnt를 센다. 고유값 수는 Haas-Stokes Duj1 추정기다. 소스에 IBM Research Report 인용이 달려 있다.
// compute_scalar_stats — analyze.c (Haas-Stokes Duj1 고유값 추정기)int f1 = ndistinct - nmultiple + toowide_cnt; /* values seen once */int d = f1 + nmultiple; /* distinct in sample */double n = samplerows - null_cnt; /* sample non-nulls */double N = totalrows * (1.0 - stats->stanullfrac); /* table non-nulls */double stadistinct;
if (N > 0) stadistinct = (n * d) / ((n - f1) + f1 * n / N);else stadistinct = 0;
/* Clamp to sane range in case of roundoff error */if (stadistinct < d) stadistinct = d;if (stadistinct > N) stadistinct = N;stats->stadistinct = floor(stadistinct + 0.5);두 가지 특수 케이스가 이를 감싼다. 값이 하나도 반복되지 않으면(nmultiple == 0) 컬럼을 고유로 처리해 stadistinct = -1.0 * (1.0 - stanullfrac)으로 설정한다. 표본의 모든 값이 반복되고 추적 목록이 완전하면 정확히 그만큼의 고유값을 가진다고 가정한다. 절대 수치가 테이블의 10%를 넘으면 상대 형식으로 전환한다.
// compute_scalar_stats — analyze.c (상대 고유값으로 전환)if (stats->stadistinct > 0.1 * totalrows) stats->stadistinct = -(stats->stadistinct / totalrows);MCV 컷오프와 등깊이 히스토그램
섹션 제목: “MCV 컷오프와 등깊이 히스토그램”MCV 목록을 단순히 목표 수만큼 채우지는 않는다. analyze_mcv_list가 추적된 값 중 비-MCV 평균보다 “유의미하게 더 빈번한” 값의 수를 결정한다. 표본의 모든 값이 들어가고 테이블의 모든 값이라고 믿어지는 완전한 목록만 이 검사를 우회한다. 히스토그램은 그 뒤에 잔여분 — 표본에서 MCV 항목을 제외한 나머지 — 으로 등간격 분위수, 즉 등깊이 히스토그램으로 구성된다.
// compute_scalar_stats — analyze.c (등깊이 히스토그램 분위수)num_hist = ndistinct - num_mcv;if (num_hist > num_bins) num_hist = num_bins + 1;if (num_hist >= 2){ /* i'th value is values[(i*(nvals-1))/(num_hist-1)], computed by * carrying integer + fractional parts to avoid overflow */ delta = (nvals - 1) / (num_hist - 1); deltafrac = (nvals - 1) % (num_hist - 1); pos = posfrac = 0; for (i = 0; i < num_hist; i++) { hist_values[i] = datumCopy(values[pos].value, ...); pos += delta; posfrac += deltafrac; if (posfrac >= (num_hist - 1)) { pos++; posfrac -= (num_hist - 1); } } stats->stakind[slot_idx] = STATISTIC_KIND_HISTOGRAM; stats->staop[slot_idx] = mystats->ltopr; /* ... */}상관계수 슬롯은 물리 순서에서 정렬된 x(논리)와 y(물리) 값 집합이 모두 0..values_cnt-1이라는 사실을 활용한다. Pearson 계수가 정렬 비교자 실행 중 누적된 단일 교차합 corr_xysum으로 닫힌 형태로 축약된다.
// compute_scalar_stats — analyze.c (물리/논리 상관계수)corr_xsum = ((double)(values_cnt - 1)) * ((double) values_cnt) / 2.0;corr_x2sum = ((double)(values_cnt - 1)) * ((double) values_cnt) * (double)(2 * values_cnt - 1) / 6.0;corrs[0] = (values_cnt * corr_xysum - corr_xsum * corr_xsum) / (values_cnt * corr_x2sum - corr_xsum * corr_xsum);stats->stakind[slot_idx] = STATISTIC_KIND_CORRELATION;카탈로그 기록 (update_attstats)
섹션 제목: “카탈로그 기록 (update_attstats)”update_attstats는 RowExclusiveLock으로 pg_statistic을 열고 분석된 컬럼마다 튜플 하나를 기록한다. 슬롯별 배열을 다섯 개의 stakindN / staopN / stacollN / stanumbersN / stavaluesN 카탈로그 컬럼으로 마샬링한다.
// update_attstats — analyze.c (스칼라 헤더 필드 마샬링)values[Anum_pg_statistic_stanullfrac - 1] = Float4GetDatum(stats->stanullfrac);values[Anum_pg_statistic_stawidth - 1] = Int32GetDatum(stats->stawidth);values[Anum_pg_statistic_stadistinct - 1] = Float4GetDatum(stats->stadistinct);i = Anum_pg_statistic_stakind1 - 1;for (k = 0; k < STATISTIC_NUM_SLOTS; k++) values[i++] = Int16GetDatum(stats->stakind[k]); /* stakindN */소비 경로 — 동등 선택도 (var_eq_const)
섹션 제목: “소비 경로 — 동등 선택도 (var_eq_const)”eqsel(=에 등록된 선택도 추정기)은 상수 피연산자 비교를 var_eq_const에 위임한다. 먼저 MCV 목록을 탐색한다. 상수가 MCV와 일치하면 저장된 빈도 자체가 선택도다. 그렇지 않으면 비공통, 비널 질량을 나머지 고유값들에 균등 배분한다.
// var_eq_const — selfuncs.c (비-MCV 동등 폴백)double sumcommon = 0.0;double otherdistinct;
for (i = 0; i < sslot.nnumbers; i++) sumcommon += sslot.numbers[i];selec = 1.0 - sumcommon - nullfrac;CLAMP_PROBABILITY(selec);
/* all not-common values share the remainder equally */otherdistinct = get_variable_numdistinct(vardata, &isdefault) - sslot.nnumbers;if (otherdistinct > 1) selec /= otherdistinct;
/* selectivity can't exceed the least common MCV */if (sslot.nnumbers > 0 && selec > sslot.numbers[sslot.nnumbers - 1]) selec = sslot.numbers[sslot.nnumbers - 1];get_variable_numdistinct에서 stadistinct의 부호 규약이 구체적 수치로 복호화된다. 양수는 그대로 사용하고, 음수는 rel->tuples를 곱하며, 0(미지)은 DEFAULT_NUM_DISTINCT = 200으로 폴백한다.
// get_variable_numdistinct — selfuncs.c (stadistinct 부호 복호화)if (stadistinct > 0.0) return clamp_row_est(stadistinct); /* absolute count */if (vardata->rel == NULL) { *isdefault = true; return DEFAULT_NUM_DISTINCT; }ntuples = vardata->rel->tuples;if (ntuples <= 0.0) { *isdefault = true; return DEFAULT_NUM_DISTINCT; }if (stadistinct < 0.0) return clamp_row_est(-stadistinct * ntuples); /* relative -> absolute */소비 경로 — 범위 선택도 (ineq_histogram_selectivity)
섹션 제목: “소비 경로 — 범위 선택도 (ineq_histogram_selectivity)”<, <=, >, >= 조건에서 scalarineqsel은 MCV 스캔(mcv_selectivity)과 히스토그램 조회를 결합한다. 히스토그램 조회는 상수를 감싸는 구간 빈을 이진 탐색으로 찾은 뒤 그 빈 내부에서 선형 보간한다. 등깊이 구조이므로 전체 빈 하나는 전체 모집단의 1/(nvalues-1)에 해당한다.
// ineq_histogram_selectivity — selfuncs.c (감싸는 빈 이진 탐색)int lobound = 0; /* first possible slot */int hibound = sslot.nvalues; /* last+1 slot */while (lobound < hibound){ int probe = (lobound + hibound) / 2; bool ltcmp; /* (endpoints may be refreshed via get_actual_variable_range) */ ltcmp = DatumGetBool(FunctionCall2Coll(opproc, collation, sslot.values[probe], constval)); if (isgt) ltcmp = !ltcmp; if (ltcmp) lobound = probe + 1; else hibound = probe;}탐색 결과가 배열 내부(values[i-1] <= const <= values[i])에 위치하면 빈 내 위치를 binfrac으로 변환하고 histfrac = (i - 1 + binfrac) / (nvalues - 1)을 구성한다. 양 끝 빈은 get_actual_variable_range로 현재 최솟값/최댓값에 맞게 갱신할 수 있다. 이는 serial PK, 타임스탬프처럼 단조 증가하는 키에서 마지막 ANALYZE 이후 추정값이 어긋나지 않도록 하는 중요한 장치다.
flowchart TD
Q["WHERE a = $1 AND b = $2<br/>(상관된 컬럼)"] --> CL["clauselist_selectivity<br/>(코스트 모델)"]
CL --> EXT{"확장 통계가<br/>이 컬럼들을 포함하는가?"}
EXT -->|"MCV / 종속성 존재"| SCS["statext_clauselist_selectivity<br/>+ dependencies_clauselist_selectivity"]
SCS --> COMBINE["결합: P(a,b) =<br/>f*Min(P(a),P(b)) +<br/>(1-f)*P(a)*P(b)"]
EXT -->|"없음 / 나머지 절"| INDEP["컬럼별 경로:<br/>eqsel / scalarineqsel"]
INDEP --> PROD["독립성 곱<br/>P(a)*P(b)"]
COMBINE --> OUT["결합 선택도"]
PROD --> OUT
SCS -.->|"처리 못 한 절"| INDEP
확장 통계 — 빌드 (extended_stats.c)
섹션 제목: “확장 통계 — 빌드 (extended_stats.c)”BuildRelationExtStatistics는 ANALYZE 시점 드라이버다. 등록된 객체마다 per-object 통계 타겟(statext_compute_stattarget)을 확정하고, 표현식 컬럼을 평가한 뒤 요청된 종류별로 디스패치한다.
// BuildRelationExtStatistics — extended_stats.c (종류별 디스패치)foreach(lc2, stat->types){ char t = (char) lfirst_int(lc2);
if (t == STATS_EXT_NDISTINCT) ndistinct = statext_ndistinct_build(totalrows, data); else if (t == STATS_EXT_DEPENDENCIES) dependencies = statext_dependencies_build(data); else if (t == STATS_EXT_MCV) mcv = statext_mcv_build(data, totalrows, stattarget); else if (t == STATS_EXT_EXPRESSIONS) /* build_expr_data + compute_expr_stats + serialize */ ;}statext_store(stat->statOid, inh, ndistinct, dependencies, mcv, exprstats, stats);확장 통계 — 함수 종속성 (dependencies.c)
섹션 제목: “확장 통계 — 함수 종속성 (dependencies.c)”함수 종속성 (a,...) -> z는 **유효도(degree of validity)**를 측정해 구성한다. 표본을 사전순으로 정렬하고, 앞 k-1 컬럼으로 그룹을 나눈 뒤, 마지막 컬럼이 일정한 그룹의 행 수를 센다. 유효도는 지지 행 수 / 전체 행 수다.
// dependency_degree — dependencies.c (정렬+그룹으로 유효도 측정)for (i = 1; i <= nitems; i++){ if (i == nitems || multi_sort_compare_dims(0, k - 2, &items[i - 1], &items[i], mss) != 0) { if (n_violations == 0) n_supporting_rows += group_size; /* group is consistent */ n_violations = 0; group_size = 1; continue; } else if (multi_sort_compare_dim(k - 1, &items[i - 1], &items[i], mss) != 0) n_violations++; /* same prefix, different z */ group_size++;}return (n_supporting_rows * 1.0 / data->numrows);플래닝 시점에 dependencies_clauselist_selectivity는 완전히 매칭되는 종속성 중 가장 강한 것을 찾는다(find_strongest_dependency, 애트리뷰트 수 → 유효도 순으로 정렬). 유효도 가중 블렌드로 열별 선택도를 결합하되, 결합 추정값이 어느 개별 성분도 초과하지 않도록 한다.
// clauselist_apply_dependencies — dependencies.c (결합 공식, 주석 인용) * P(a,b) = f * Min(P(a), P(b)) + (1-f) * P(a) * P(b) * to ensure that the combined selectivity is never greater than either * individual selectivity.f = 1(완전 종속성)이면 결합 선택도는 정확히 Min(P(a),P(b))다. a를 알면 b도 알 수 있으므로 두 번째 조건은 아무 정보도 추가하지 않는다. f = 0이면 독립성 곱으로 퇴화한다.
확장 통계 — 다변량 n-distinct (mvdistinct.c)
섹션 제목: “확장 통계 — 다변량 n-distinct (mvdistinct.c)”statext_ndistinct_build는 크기 2..k의 모든 컬럼 조합을 열거하고, 각 조합에서 고유 튜플 수와 단일 등장 수를 센 뒤 열별 경로와 동일한 Duj1 추정기를 재사용한다.
// estimate_ndistinct — mvdistinct.c (Duj1, analyze.c 공식과 동일)numer = (double) numrows * (double) d;denom = (double) (numrows - f1) + (double) f1 * (double) numrows / totalrows;ndistinct = numer / denom;if (ndistinct < (double) d) ndistinct = (double) d;if (ndistinct > totalrows) ndistinct = totalrows;return floor(ndistinct + 0.5);조합의 고유 튜플 수는 다중 컬럼 정렬 후 단일 인접 차분 패스(ndistinct_for_combination)로 구한다. 단일 컬럼 경로가 정렬 후 고유값을 세는 방식과 정확히 같다. 플래너는 이 조합 수를 estimate_num_groups(GROUP BY / DISTINCT 카디널리티)에서 활용한다. n_distinct(a) * n_distinct(b)가 실제 (a,b) 그룹 수를 크게 초과하는 고전적 과다 계산을 방지하기 위해서다.
위치 힌트 (2026-06-05, REL_18 273fe94 기준)
섹션 제목: “위치 힌트 (2026-06-05, REL_18 273fe94 기준)”| 심볼 | 파일 | 줄 |
|---|---|---|
analyze_rel | commands/analyze.c | 109 |
do_analyze_rel | commands/analyze.c | 278 |
examine_attribute | commands/analyze.c | 1036 |
acquire_sample_rows | commands/analyze.c | 1199 |
update_attstats | commands/analyze.c | 1655 |
std_typanalyze | commands/analyze.c | 1891 |
compute_trivial_stats | commands/analyze.c | 1969 |
compute_distinct_stats | commands/analyze.c | 2059 |
compute_scalar_stats | commands/analyze.c | 2402 |
eqsel | utils/adt/selfuncs.c | 235 |
var_eq_const | utils/adt/selfuncs.c | 303 |
var_eq_non_const | utils/adt/selfuncs.c | 474 |
scalarineqsel | utils/adt/selfuncs.c | 588 |
mcv_selectivity | utils/adt/selfuncs.c | 740 |
histogram_selectivity | utils/adt/selfuncs.c | 831 |
ineq_histogram_selectivity | utils/adt/selfuncs.c | 1049 |
scalarltsel | utils/adt/selfuncs.c | 1479 |
get_variable_numdistinct | utils/adt/selfuncs.c | 6274 |
BuildRelationExtStatistics | statistics/extended_stats.c | 111 |
statext_compute_stattarget | statistics/extended_stats.c | 344 |
choose_best_statistics | statistics/extended_stats.c | 1216 |
statext_clauselist_selectivity | statistics/extended_stats.c | 1991 |
dependency_degree | statistics/dependencies.c | 221 |
statext_dependencies_build | statistics/dependencies.c | 348 |
find_strongest_dependency | statistics/dependencies.c | 929 |
clauselist_apply_dependencies | statistics/dependencies.c | 1014 |
dependencies_clauselist_selectivity | statistics/dependencies.c | 1370 |
statext_ndistinct_build | statistics/mvdistinct.c | 88 |
ndistinct_for_combination | statistics/mvdistinct.c | 425 |
estimate_ndistinct | statistics/mvdistinct.c | 521 |
statext_mcv_build | statistics/mcv.c | 180 |
mcv_clauselist_selectivity | statistics/mcv.c | 2048 |
STATISTIC_KIND_MCV (= 1) | catalog/pg_statistic.h | 190 |
STATISTIC_KIND_HISTOGRAM (= 2) | catalog/pg_statistic.h | 210 |
STATISTIC_KIND_CORRELATION (= 3) | catalog/pg_statistic.h | 222 |
STATISTIC_NUM_SLOTS (= 5) | catalog/pg_statistic.h | 127 |
STATS_EXT_NDISTINCT (= ‘d’) | catalog/pg_statistic_ext.h | 84 |
STATS_EXT_DEPENDENCIES (= ‘f’) | catalog/pg_statistic_ext.h | 85 |
STATS_EXT_MCV (= ‘m’) | catalog/pg_statistic_ext.h | 86 |
DEFAULT_NUM_DISTINCT (= 200) | utils/selfuncs.h | 52 |
DEFAULT_EQ_SEL (= 0.005) | utils/selfuncs.h | 34 |
소스 검증 (2026-06-05 기준)
섹션 제목: “소스 검증 (2026-06-05 기준)”각 항목은 커밋
273fe94(REL_18_STABLE) 현재 소스에 대한 사실을 앞세운다. 확인 방법은 항목 끝에 명시한다. 미해결 항목은 열린 질문으로 별도 기록한다.
검증된 사실
섹션 제목: “검증된 사실”-
표본 예산은
300 * statistics_target, 하한 100, 확장 통계 요구로 추가 상향된다.std_typanalyze가 Chaudhuri-Motwani-Narasayya 도출을 주석으로 달며stats->minrows = 300 * stats->attstattarget으로 설정한다.do_analyze_rel의targrows루프는 100에서 시작해 컬럼 최댓값을 취하고ComputeExtStatisticsRows로 추가 상향한다. 2026-06-05에std_typanalyze와do_analyze_rel의targrows루프를 직접 읽어 확인.default_statistics_target = 100이면 열별 예산은 30,000행이다. -
표본 추출은 두 단계다.
BlockSampler로 랜덤 블록을 고르고, 그 행들에 Vitter 저수지를 적용한다.acquire_sample_rows가BlockSampler_Init을 초기화하고ReadStream으로 선택된 블록을 순회한다. 처음targrows개 행을 직접rows[]에 복사한 뒤reservoir_get_next_S/sampler_random_fract로 저수지 슬롯을 무작위 교체한다.acquire_sample_rows를 읽어 확인. 상관계수 측정을 위해compare_rows가 결과를 item-pointer 순으로 재정렬한다. -
stadistinct는 Haas-Stokes Duj1 추정기이며 소스에 IBM Research Report RJ 10025가 명시적으로 인용된다.compute_scalar_stats(및compute_distinct_stats)가(n*d)/((n-f1)+f1*n/N)을 계산하고[d, N]으로 클램핑한다.f1은 한 번 등장한 값,d는 표본 내 고유값,n/N은 표본/테이블 비널.compute_scalar_stats추정기 블록과 주석을 읽어 확인. 동일 공식이mvdistinct.c의estimate_ndistinct로 분리되어 있다. -
stadistinct의 부호는 의미가 있다. 양수는 절대값, 음수는 행 수 비율, 0은 미지다.compute_scalar_stats가0.1 * totalrows를 초과하는 절대 추정값을-(stadistinct/totalrows)로 뒤집는다.get_variable_numdistinct는 음수 값을-stadistinct * ntuples로, 0을DEFAULT_NUM_DISTINCT(200)으로 복호화한다. 두 함수를 읽어 확인.compute_*는 명백히 고유한 컬럼에stadistinct = -1.0 * (1.0 - stanullfrac)을 설정한다. -
히스토그램은 비-MCV 잔여분의 등깊이 히스토그램이며
STATISTIC_KIND_HISTOGRAM에 저장된다.compute_scalar_stats가 먼저values[]에서 MCV 항목을 제거한 뒤num_hist개의 등간격 분위수를 복사한다(넘침 방지를 위해 정수부+소수부를 따로 추적). 히스토그램 블록을 읽어 확인.num_hist >= 2일 때만 구성된다. -
동등 선택도는 상수가 MCV이면 그 빈도를 그대로 읽고, 그렇지 않으면 잔여 질량을
otherdistinct값에 균등 배분한다.var_eq_const가 동등 함수로 MCV 슬롯을 순회한다. 일치하면selec = sslot.numbers[i], 그렇지 않으면selec = (1 - sumcommon - nullfrac) / otherdistinct이며 최소 MCV 빈도로 상한이 설정된다.var_eq_const를 읽어 확인. -
범위 선택도는 히스토그램을 이진 탐색하고 끝점을 현재 최솟값/최댓값으로 갱신한다.
ineq_histogram_selectivity가 감싸는 빈을 이분 탐색하고, 빈 내부에서binfrac을 보간한 뒤,get_actual_variable_range로 히스토그램의 첫/마지막 항목을 현재 컬럼 극값으로 교체한다. 함수를 읽어 확인. 이 메커니즘 덕분에 단조 증가하는 키에서 ANALYZE 간격 중에도 추정값이 합리적으로 유지된다. -
확장 통계는 동일한 ANALYZE 표본에서 구성되며 종류 문자별로 디스패치된다.
BuildRelationExtStatistics가pg_statistic_ext항목을 가져와statext_compute_stattarget으로 per-object 타겟을 계산하고,'d'/'f'/'m'/'e'를statext_ndistinct_build/statext_dependencies_build/statext_mcv_build/ 표현식 경로로 라우팅한 뒤statext_store로 직렬화한다.BuildRelationExtStatistics를 읽어 확인. -
함수 종속성의 유효도는 지지 행 수 / 전체 행 수이며 정렬+그룹으로 측정한다.
dependency_degree가k컬럼으로 표본을 정렬하고 앞k-1로 그룹을 순회하며, 마지막 컬럼 위반이 없는 그룹의group_size만n_supporting_rows에 누적한다.dependency_degree를 읽어 확인. 반환값은n_supporting_rows * 1.0 / data->numrows다. -
종속성 인식 선택도는
f*Min(P(a),P(b)) + (1-f)*P(a)*P(b)를 사용하며 어느 개별 성분도 초과하지 않는다. 결합 공식은clauselist_apply_dependencies헤더 주석에 명시되어 있으며,find_strongest_dependency가 종속성을 선택한 뒤 적용된다(애트리뷰트 수 → 유효도 순). 두 함수를 읽어 확인.f=1이면Min으로,f=0이면 독립성 곱으로 퇴화한다.
열린 질문
섹션 제목: “열린 질문”-
statext_mcv_build는 목록 길이를 어떻게 결정하고 다변량 MCV 목록을 어떻게 직렬화하는가? 이 문서는 빌드 디스패치와 열별analyze_mcv_list컷오프를 읽었지만, 다변량 MCV 구성(mcv.c, 약 2175행,mcv_clauselist_selectivity와 기저 선택도 보정 포함)을 문장 단위로 추적하지는 않았다. 조사 경로:statistics/mcv.c의statext_mcv_build와mcv_clauselist_selectivity, 그리고statistics/README.mcv설계 노트를 읽는다. -
플래닝 시점에 세 가지 확장 통계 종류의 우선순위와 절 소비 순서는 어떻게 되는가?
statext_clauselist_selectivity(MCV 경로)와dependencies_clauselist_selectivity모두*estimatedclauses를 표시해 독립성 경로가 이를 건너뛰지만, 정확한 순서와choose_best_statistics가 겹치는 객체 중에서 선택하는 방식은 부분적으로만 읽었다. 조사 경로:clausesel.c에서statext_clauselist_selectivity와 그 호출자를 추적한다(담당:postgres-cost-model.md). -
상속/파티셔닝은
stainherit플래그와 어떻게 상호작용하는가?update_attstats와statext_store가 모두inh매개변수를 받아 상속/비상속 행을 별도 저장하고,do_analyze_rel이 상속 트리를 두 번 실행하지만, 파티셔닝 쿼리에서 플래너가 어느 행을 읽을지 선택하는 방식은 여기서 검증하지 않았다. 조사 경로:examine_variable/get_relation_stats_hook소비자의stainherit처리를 읽는다.
PostgreSQL 너머 — 비교 설계와 연구 전선
섹션 제목: “PostgreSQL 너머 — 비교 설계와 연구 전선”포인터만 제공한다. 각 항목은 후속 문서를 위한 출발점이며, 의도적으로 깊이를 낮춘다.
-
Selinger와 카탈로그 기반 선택도의 기원. Selinger et al., “Access Path Selection in a Relational Database Management System”(SIGMOD 1979, System R 옵티마이저;
dbms-papers/systemr-optimizer.md에 캡처됨)은 카탈로그 카디널리티와 통계가 없는 조건에 대한 고정 “선택도 인수”로 조인과 접근 경로 선택을 구동하는 관행을 도입했다. PostgreSQL의DEFAULT_EQ_SEL = 0.005와DEFAULT_INEQ_SEL = 1/3은 Selinger의 매직 상수를 직접 계승한 것이며,pg_statistic은 System R per-column 카탈로그 수치의 현대적·히스토그램 포함 버전이다. -
CUBRID의 per-attribute 통계 vs. PostgreSQL의 MCV+히스토그램. CUBRID는
db_stat방식의 per-attribute 최솟값/최댓값, 카디널리티, B-트리 기반 값 분포를 유지하지만, 역사적으로 별도의 최빈값 목록이나 선택적 다중 컬럼 통계 없이 운영해 왔다.WHERE city = ? AND state = ?같은 비대칭+상관 워크로드를 두 엔진이 어떻게 처리하는지 나란히 비교하면, PostgreSQL MCV 목록과CREATE STATISTICS가 오추정 방지에서 어떤 이점을 주는지 더 선명해진다. -
HyperLogLog와 정확한 고유값 수. PostgreSQL의 표본 기반
stadistinct는 잘 알려진 약점이다. 소스 자체도 Duj1 추정기가 “보통 과대 추정으로 이어진다”고 경고한다. 스케치 기반 방식(Flajolet et al.의 HyperLogLog,postgres_hll확장, Redshift·BigQuery의APPROX_COUNT_DISTINCT)은 전체 재스캔 없이 제한된 오차로 고유값 수를 점진적으로 유지한다. 스케치가 코어의stadistinct를 대체하거나 보완할 수 있는지는 -hackers 메일링에서 반복 논의되는 주제다. -
학습 기반·자가 교정 카디널리티 추정. 연구 전선에서는 고정 히스토그램을 데이터나 쿼리 피드백으로 학습된 모델로 대체한다. DeepDB와 Naru(심층 자기회귀 밀도 모델), 그리고 실행된 플랜의 실제 행 수를 추정기에 피드백하는 DB2 LEO(“LEarning Optimizer”, Stillger et al., VLDB 2001)가 대표적이다. PostgreSQL 코어에는 실행 피드백 루프가 없다. 모든 추정은 마지막 ANALYZE에서 순수하게 도출된다. 비교 연구는 반복 오추정 쿼리에서 피드백 채널이 얼마나 회복 가능한지를 정량화할 수 있다.
-
다차원 히스토그램 vs. PostgreSQL의 세 가지 확장 통계 종류. PostgreSQL은 진정한 다차원 히스토그램을 의도적으로 피한다. 저장 및 구성 비용이 차원 수에 지수적이기 때문이다. 대신 더 저렴한 세 가지 결합 요약을 선택했다. 함수 종속성, 다변량 n-distinct, 다변량 MCV. 고전적 대안은 GENHIST / 다차원 히스토그램 계열(Gunopulos et al.)이다. 트레이드오프는 구성 비용과 저장 공간 대 임의 다중 컬럼 조건 추정 정확도다. PostgreSQL의 각 종류가 실제로 어떤 쿼리 형태에 도움이 되는지(종속성은 동등 체인, n-distinct는 GROUP BY, MCV는 비대칭 조합)를 전체 2차원 히스토그램이 커버할 범위와 대비하는 것이 자연스러운 후속 작업이다.
소스 내 README
섹션 제목: “소스 내 README”src/backend/statistics/README(및README.dependencies,README.mcv) — 확장 통계 설계 노트. 열별 독립성이 왜 실패하는지, 함수 종속성 유효도 모델, 다변량 MCV 표현, 절 결합 공식을 다룬다.
교재 챕터 (knowledge/research/dbms-general/ 아래)
섹션 제목: “교재 챕터 (knowledge/research/dbms-general/ 아래)”- Database System Concepts (Silberschatz, Korth & Sudarshan, 7판), 16장 “Query Optimization”, §16.3 “Statistical Information for Cost Estimation” — §16.3.1 카탈로그 수량(n_r, V(A,r), 히스토그램, 등너비 vs. 등깊이, 최빈값 저장), §16.3.2 선택 크기 추정(
n_r / V(A,r)), §16.3.4 “수천 튜플 표본으로 충분히 정확한 히스토그램 구성 가능.”
논문 (knowledge/research/dbms-papers/ 아래)
섹션 제목: “논문 (knowledge/research/dbms-papers/ 아래)”- Access Path Selection in a Relational Database Management System (Selinger et al., SIGMOD 1979) —
dbms-papers/systemr-optimizer.md: 카탈로그 기반 선택도 인수,DEFAULT_EQ_SEL의 선조. - (비교 목적, 미캡처) Chaudhuri, Motwani & Narasayya 1998 “Random sampling for histogram construction” (
300*k한계); Haas & Stokes / IBM RR RJ 10025 (Duj1 고유값 추정기); Stillger et al. 2001 “LEO – DB2’s LEarning Optimizer” — “PostgreSQL 너머” 섹션 참조.
PostgreSQL 소스 (/data/hgryoo/references/postgres/, REL_18 273fe94)
섹션 제목: “PostgreSQL 소스 (/data/hgryoo/references/postgres/, REL_18 273fe94)”src/backend/commands/analyze.c—analyze_rel/do_analyze_rel,acquire_sample_rows(두 단계 표본),examine_attribute,std_typanalyze, 세 가지compute_*_stats루틴,update_attstats.src/backend/statistics/extended_stats.c—BuildRelationExtStatistics,statext_compute_stattarget,statext_clauselist_selectivity,choose_best_statistics.src/backend/statistics/dependencies.c—dependency_degree,statext_dependencies_build,find_strongest_dependency,clauselist_apply_dependencies,dependencies_clauselist_selectivity.src/backend/statistics/mvdistinct.c—statext_ndistinct_build,ndistinct_for_combination,estimate_ndistinct(Duj1).src/backend/statistics/mcv.c—statext_mcv_build,mcv_clauselist_selectivity.src/backend/utils/adt/selfuncs.c—eqsel/var_eq_const,scalarineqsel/ineq_histogram_selectivity,mcv_selectivity,histogram_selectivity,get_variable_numdistinct.src/include/catalog/pg_statistic.h— 다섯 슬롯 튜플 레이아웃과STATISTIC_KIND_*코드;src/include/catalog/pg_statistic_ext.h—STATS_EXT_*종류 문자;src/include/utils/selfuncs.h—DEFAULT_*_SEL,DEFAULT_NUM_DISTINCT,CLAMP_PROBABILITY.
교차 참조 (인접 메커니즘을 담당하는 형제 문서)
섹션 제목: “교차 참조 (인접 메커니즘을 담당하는 형제 문서)”postgres-cost-model.md—clauselist_selectivity와 선택도가 경로 비용에 반영되는 방식. 확장 통계 추정기를 호출하는 절 결합 진입점을 담당한다.postgres-system-catalogs.md—pg_statistic,pg_statistic_ext,pg_statistic_ext_data카탈로그 레이아웃과pg_stats/pg_stats_ext뷰.postgres-planner-overview.md— 경로 생성 과정에서 카디널리티 추정값이 어떻게 소비되는지.postgres-vacuum.md— autovacuum의 analyze 임계값과 유지하는pg_class.reltuples/relpages(n_r 입력값).postgres-executor.md—EXPLAIN ANALYZE의 실제 행 수, 이 추정값을 판단하는 기준.