콘텐츠로 이동

(KO) PostgreSQL 질의 처리 — 섹션 개요

목차

이 서브카테고리는 읽기/계산 경로다. SQL 문자열이 백엔드에 도착한 시점부터 Plan 트리가 튜플을 생산하는 시점까지의 모든 것을 다룬다. 교과서적인 다섯 단계 파이프라인 — 파싱 → 분석 → 재작성 → 플래닝 → 실행 — 이 뼈대이고, 그 척추에 매달린 네 가지가 함께 다뤄진다. 병렬 질의, prepared statement·포털, 플래너 통계, 그리고 엔진 전체가 트리를 만드는 데 쓰는 Node 인프라가 그것이다.

단계별 진입점은 tcop/postgres.c의 함수들 — pg_parse_query, pg_analyze_and_rewrite_fixedparams, pg_rewrite_query, pg_plan_queries — 로 연결된다. 어느 한 단계를 깊이 파기 전에 이 파일을 먼저 훑으면 파이프라인 전체를 직선 코드로 확인할 수 있다.

이 섹션이 넘지 않는 경계를 명시해 두면 범위가 더 분명해진다.

  • SQL이 어디서 왔는지는 범위 밖이다. 쿼리 문자열을 전달하는 와이어 프로토콜, simple·extended 질의 메시지, 인증은 모두 client-protocol 소관이다. 이 섹션은 백엔드가 query_string을 손에 쥔 시점부터 시작한다.
  • 실행기 스캔이 실제로 무엇을 건드리는지는 범위 밖이다. SeqScan이나 IndexScan 노드가 튜플을 꺼낼 때 호출하는 테이블 접근 방법인덱스 접근 방법, 그 아래의 버퍼 매니저와 페이지 레이아웃은 storage-engine 소관이다. 이 섹션은 스캔을 구동하는 노드를 다루고, storage-engine은 노드가 읽어 오는 힙 페이지를 소유한다.
  • 스캔 아래의 가시성과 잠금은 범위 밖이다. 스캔이 읽는 튜플이 현재 스냅샷에 보이는지, 실행기가 어떤 잠금을 거는지는 각각 txn-recovery(MVCC, 스냅샷, procarray)와 server-architecture(잠금 매니저) 소관이다. 실행기가 물으면, 그 섹션들이 답한다.
  • DDL과 유틸리티 문은 범위 밖이다. pg_plan_queries최적화 가능한 문(SELECT/INSERT/UPDATE/DELETE/MERGE)만 플래닝한다. CREATE TABLE 같은 유틸리티 문은 ProcessUtility를 거쳐 ddl-schema로 라우팅된다. 이 섹션은 최적화 가능한 질의 경로만 다룬다.
  • 플래너가 읽는 카탈로그는 서브시스템으로서 범위 밖이다. 옵티마이저의 비용 모델은 단일 컬럼 통계와 릴레이션 메타데이터를 소비하지만, 그것을 서비스하는 relcache·catcache 기계는 system-catalog 소관이다. 이 섹션은 소비자(선택도 추정, 확장 통계)를 소유하고 캐시는 소유하지 않는다.

범위 안에서 독자가 앞으로 가지고 있어야 할 핵심 주장이 있다. 플랜 생성에는 두 가지 표현이 있다 — 탐색 중에는 최소 비용 우선의 Path 노드, 승자가 결정되면 그것으로부터 만들어지는 단 하나의 실행 가능한 Plan 트리. 조인 순서는 동적 프로그래밍으로 결정하며, 조인 수가 geqo_threshold를 넘으면 GEQO 유전 알고리즘이 폴백으로 작동한다. 실행기는 모든 노드가 ExecProcNode에 응답하며 자식에서 당겨 오는 demand-pull 트리다.

flowchart TB
  SQL["SQL 텍스트 (query_string)"]

  subgraph FRONT["파이프라인 앞단 — tcop/postgres.c가 구동"]
    PARSE["postgres-parser<br/>pg_parse_query: scan.l + gram.y<br/>-> 원시 파스 트리 (RawStmt)"]
    ANALYZE["postgres-analyze-transform<br/>parse_analyze: 이름 해석,<br/>타입 강제 변환 -> Query 트리"]
    REWRITE["postgres-rewriter<br/>규칙 시스템: 뷰 확장,<br/>RLS, DO INSTEAD -> Query 목록"]
  end
  SQL --> PARSE --> ANALYZE --> REWRITE

  subgraph PLAN["플래너 / 옵티마이저 — optimizer/README"]
    PREP["postgres-prep-rewrites<br/>서브쿼리 끌어올리기, 조건 정규화"]
    PATHS["postgres-path-generation<br/>후보 Path, 최소 비용 우선"]
    JOINS["postgres-join-ordering<br/>DP 조인 탐색 + GEQO 폴백"]
    COST["postgres-cost-model<br/>costsize.c + 선택도"]
    CREATE["postgres-plan-creation<br/>승리 Path -> 실행 가능 Plan 트리"]
    STATS["postgres-extended-statistics<br/>선택도용 다중 컬럼 통계"]
  end
  REWRITE --> PREP --> PATHS
  PATHS --> JOINS
  COST -. 입력 .-> PATHS
  COST -. 입력 .-> JOINS
  STATS -. 입력 .-> COST
  JOINS --> CREATE

  subgraph EXEC["실행기 — executor/README"]
    PORTAL["postgres-portals-prepared<br/>PortalStart/PortalRun, 플랜 캐시"]
    DRIVER["postgres-executor<br/>ExecProcNode demand-pull 트리"]
    EXPR["postgres-expression-eval<br/>switch-threaded 인터프리터"]
    SCANS["postgres-scan-nodes"]
    JOINNODES["postgres-join-nodes"]
    AGGSORT["postgres-agg-sort-nodes"]
    PARALLEL["postgres-parallel-query<br/>Gather가 shm_mq 위에서 워커 fork"]
    JIT["postgres-jit<br/>선택적 LLVM 표현식 컴파일"]
    SORT["postgres-tuplesort<br/>외부 병합 정렬 / 스필"]
  end
  CREATE --> PORTAL --> DRIVER
  DRIVER --> SCANS
  DRIVER --> JOINNODES
  DRIVER --> AGGSORT
  DRIVER --> PARALLEL
  DRIVER --> EXPR
  EXPR -. 선택적으로 .-> JIT
  AGGSORT -. 스필 경유 .-> SORT

  NODES["postgres-node-trees<br/>위 모든 트리가 기반을 두는 Node/List 기반 구조"]
  NODES -. 기반 .-> FRONT
  NODES -. 기반 .-> PLAN
  NODES -. 기반 .-> EXEC

  SCANS --> AM["테이블 / 인덱스 접근 방법 (storage-engine)"]

다이어그램이 담고 있는 구조적 사실이 세 가지 있다.

  • 스트림이 아니라 트리다. 각 단계는 다음 단계에 Node들의 트리를 넘긴다. 원시 파스 트리, Query 트리, Plan 트리, 런타임 PlanState 트리가 그것이다. postgres-node-trees는 이 섹션의 다른 문서들이 전제로 삼는 Node/List 기계(gen_node_support.pl이 생성하는 copy/equal/out/read 함수)를 소유한다.
  • 플래너에는 두 가지 문법이 있다. 탐색 중에는 Path 노드를 만들고 비교한다(만드는 비용이 낮고, 최소 비용 우선으로 관리). 승자만 postgres-plan-creationPlan으로 변환한다. 비용(postgres-cost-model)과 통계(postgres-extended-statistics)는 탐색의 입력이지 단계가 아니다. 그래서 점선 “입력” 엣지로 표현된다.
  • 병렬 질의는 같은 트리의 fork다. Gather/GatherMerge 노드가 서브플랜 사본을 실행하는 백그라운드 워커를 띄우고 shm_mq로 튜플을 스트리밍한다. 별도의 실행 모델이 아니다.

앞에서 읽은 문서에 기대는 순서로 배열했다.

  1. postgres-node-trees — 가장 먼저 읽는다. 하위의 모든 것은 Node들의 트리이며, copy/equal/out/read와 List 규약은 이 섹션의 다른 모든 문서가 전제한다.
  2. 파이프라인 앞단postgres-parserpostgres-analyze-transformpostgres-rewriter 순서로 읽는다. 분량이 짧고 선형적이며, 플래너가 소비하는 Query 트리를 확립한다.
  3. 플래너postgres-planner-overview를 먼저 읽는다(옵티마이저 전체를 구성하고 optimizer/README의 본거지다). 이어서 탐색을 위해 postgres-path-generationpostgres-join-ordering, 입력을 위해 postgres-cost-modelpostgres-extended-statistics, Path→Plan 변환을 위해 postgres-plan-creation을 읽는다. postgres-prep-rewrites는 후반에 읽어도 된다(플래너의 전처리 단계다).
  4. 실행기postgres-executor(demand-pull 구동부)를 노드별 문서 postgres-scan-nodes, postgres-join-nodes, postgres-agg-sort-nodes보다 먼저 읽는다. postgres-expression-eval은 실행기와 짝을 이루고, postgres-tuplesort는 집계/정렬 노드가 의존하는 스필 기계다.
  5. 횡단 네 가지postgres-parallel-query, postgres-portals-prepared, 그리고 선택적 리프인 postgres-jit를 마지막에 읽는다. 이것들은 플랜과 실행기 모델을 전제한다.

아래는 전방 참조다. 각 모듈 문서가 아직 존재하지 않을 수 있다. 각각이 소유할 내용을 한 줄씩 정리했다.

문서한 줄 범위
postgres-parser.md렉서(scan.l)와 바이슨 문법(gram.y)이 SQL 텍스트를 원시 파스 트리로 전환하는 과정; 파스 시점에 결정되는 것과 그렇지 않은 것.
postgres-analyze-transform.md파스 분석(analyze.c, parse_*): 이름·릴레이션 해석, 타입 강제 변환, 원시 파스 트리를 Query로 변환.
postgres-rewriter.md규칙 시스템(rewriteHandler.c): 뷰 확장, DO INSTEAD 규칙, Query 트리에 대한 행 수준 보안 조건 주입.
postgres-planner-overview.mdstandard_planner/subquery_planner 최상위 흐름; Path-vs-Plan 모델과 옵티마이저 구성 요소 배치(optimizer/README 본거지).
postgres-path-generation.md릴레이션별 후보 접근 Path 구성(allpaths.c, indxpath.c): 순차 스캔/인덱스 스캔/비트맵 경로, pathkey, 최소 비용 관리.
postgres-join-ordering.md조인 순서 탐색: 조인 릴레이션에 대한 동적 프로그래밍(joinrels.c, joinpath.c)과 geqo_threshold를 넘을 때의 GEQO 유전 폴백.
postgres-cost-model.md비용 함수(costsize.c)와 선택도 추정(selfuncs.c): 행 추정값과 *_cost GUC에서 Path의 total_cost를 계산하는 방법.
postgres-plan-creation.md승리 Path를 실행 가능한 Plan 트리로 변환(createplan.c, setrefs.c, subselect.c): 변수 수정, 서브플랜 처리.
postgres-prep-rewrites.md플래너 전처리(optimizer/prep/*): 서브쿼리 끌어올리기/평탄화, 조건 정규화, 집합 연산 및 목표 목록 준비.
postgres-extended-statistics.md다중 컬럼 확장 통계(statistics/*): ndistinct, 함수 의존성, MCV 목록, ANALYZE가 이것들을 채워 선택도를 높이는 방법.
postgres-executor.mddemand-pull 엔진: ExecInitNode/ExecProcNode/ExecEndNode, PlanState 트리, 튜플 슬롯, 실행기 생명 주기.
postgres-expression-eval.md표현식을 opcode 단계 배열로 컴파일(execExpr.c)하고 switch-threaded 인터프리터(execExprInterp.c, ExecInterpExpr)로 실행하는 과정.
postgres-scan-nodes.md리프 스캔 실행기 노드: 순차 스캔, 인덱스 스캔, 인덱스 전용 스캔, 비트맵 힙 스캔, tid·tid-범위 스캔, 접근 방법 호출 방식.
postgres-join-nodes.md조인 실행기 노드: 중첩 루프, 해시 조인(nodeHash/nodeHashjoin), 병합 조인 — 알고리즘, 배치 처리, 메모리 동작.
postgres-agg-sort-nodes.md그룹화/정렬 노드: 해시 집계·정렬 집계(nodeAgg), 정렬, 증분 정렬, 그룹, 윈도우 집계.
postgres-parallel-query.md병렬 실행: Gather/GatherMerge, execParallel 워커 설정, shm_mq 튜플 큐, 병렬 인식 노드(README.parallel 참조).
postgres-portals-prepared.md포털(pquery.c)과 prepared statement(prepare.c, plancache.c): 플랜 캐싱, 제네릭 플랜 대 커스텀 플랜, 커서 실행.
postgres-jit.md선택적 LLVM JIT(jit/llvm/*): 표현식과 튜플 해제가 인터프리팅 대신 컴파일되는 조건, 발동 비용 임계값.
postgres-tuplesort.md범용 정렬(tuplesort.c)과 tuplestore: 메모리 내 퀵소트, logtape를 이용한 외부 병합, 집계/정렬/병합 조인이 사용하는 스필 경로.
postgres-node-trees.mdNode/List 기반 구조(nodes/*, gen_node_support.pl): 태그 노드, copy/equal/out/read 코드 생성, Bitmapset/tidbitmap 도우미.
  • storage-engine (postgres-overview-storage-engine.md) — 실행 아래에 있는 섹션이다. 실행기의 스캔 노드는 테이블/인덱스 접근 방법 경계에서 멈추고, storage-engine이 AM, 버퍼 매니저, 페이지 레이아웃, 힙을 소유한다. 엔진에서 가장 자주 넘나드는 경계다.
  • txn-recovery (postgres-overview-txn-recovery.md) — 스캔이 읽는 스냅샷과, 어느 튜플 버전이 질의에 보이는지를 결정하는 가시성 규칙을 공급한다. 실행기가 “이 튜플이 내 스냅샷에 보이는가?” 하고 물으면 txn-recovery가 답한다.
  • system-catalog (postgres-overview-system-catalog.md) — 플래너의 비용 모델과 파서의 이름 해석 둘 다 relcache/catcache에서 카탈로그 메타데이터(릴레이션, 타입, 연산자, 통계)를 읽는다. 이 섹션은 그 캐시를 소비하고, system-catalog가 소유한다.
  • client-protocol (postgres-overview-client-protocol.md) — 파싱 앞에 있는 섹션이다. query_string을 전달하고, 확장 질의 프로토콜이 제공하는 Parse/Bind/Execute 생명 주기는 이 섹션의 prepared statement·포털로 매핑된다.
  • server-architecture (postgres-overview-server-architecture.md) — 이 파이프라인 전체를 실행하는 백엔드가 B_BACKEND이고, 병렬 질의 워커는 공유 메모리 머신에 붙은 백그라운드 워커다. 프로세스 모델, 잠금, 병렬 질의가 타는 shm_mq/DSM 기반이 모두 그곳에 있다.