본문으로 건너뛰기
5.1 파서·재작성기

5.1 파서·재작성기

PostgreSQL이 SQL 문자열 한 줄을 받아서 실제 데이터를 돌려주기까지는 파싱 → 재작성 → 계획 → 실행 네 단계를 거칩니다. 첫 두 단계를 봅니다.

전체 흐름

    flowchart LR
  RAW["SQL text<br/>(질의문자열)"]
  PARSE["Parser<br/>(flex + bison)"]
  RT["Parse Tree<br/>(raw)"]
  ANALYZE["Analyzer<br/>(이름·타입 해석)"]
  QT["Query Tree"]
  REWRITE["Rewriter<br/>(rule, view 풀기)"]
  QT2["Query Tree<br/>(재작성됨)"]
  PLAN["Planner / Optimizer"]
  EXEC["Executor"]

  RAW --> PARSE --> RT --> ANALYZE --> QT --> REWRITE --> QT2 --> PLAN --> EXEC

  classDef text fill:#fef3c7,stroke:#b45309,color:#78350f
  classDef stage fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
  classDef tree fill:#ede9fe,stroke:#6d28d9,color:#3b0764
  classDef exec fill:#d1fae5,stroke:#047857,color:#064e3b
  class RAW text
  class PARSE,ANALYZE,REWRITE,PLAN stage
  class RT,QT,QT2 tree
  class EXEC exec
  

PARSE → ANALYZE → REWRITE 까지. 다음 절(5.2)부터는 PLAN과 그 비용 모델.

1. 파서 — 문법만 본다

PostgreSQL 파서는 flex(렉서) + bison(파서 생성기) 조합입니다. 입력 SQL을 토큰으로 쪼개고, 문법 규칙에 맞게 Raw Parse Tree를 만듭니다.

이 단계에서 일어나는 검사:

  • 문법 오류 (syntax error at or near "...")
  • 키워드 위치
  • 괄호 매칭

아직 일어나지 않는 것:

  • 테이블·컬럼이 실제로 존재하는가
  • 타입이 맞는가
  • 권한이 있는가
SELECT * FROM no_such_table;
-- 문법은 OK — 파서 통과
-- ERROR는 다음 단계(Analyzer)에서 발생

pg_query나 PG 14+의 EXPLAIN은 이 단계를 노출하지 않지만, 문법만 확인하고 싶을 때 PREPARE를 쓰면 파싱+분석까지 진행되어 검사됩니다.

2. Analyzer — 이름·타입 해석

Raw Parse Tree를 받아 카탈로그(pg_class, pg_attribute, pg_type 등)와 대조해 Query Tree를 만듭니다.

해결되는 것:

  • 테이블·뷰 이름 → OID
  • 컬럼 이름 → attnum
  • 타입 추론과 캐스팅
  • 함수 시그니처 매칭
  • 권한 검사 (읽기/쓰기·EXECUTE)

오류 예:

ERROR:  column "foo" does not exist
ERROR:  operator does not exist: integer = text
ERROR:  permission denied for table accounts

이 시점에서 Query Tree의 노드는 RTE(Range Table Entry)·TargetEntry·FuncExpr·OpExpr 등으로 구성됩니다. 옵티마이저가 이해할 수 있는 internal representation입니다.

3. Rewriter — view와 rule을 풀어 펴기

재작성기는 Query Tree를 받아 view 정의rule을 끼워 넣어 확장된 Query Tree를 만듭니다.

view 풀기

CREATE VIEW recent_orders AS
  SELECT id, user_id, total
    FROM orders
   WHERE created_at > now() - interval '7 days';

SELECT count(*) FROM recent_orders;

재작성기 후:

SELECT count(*) FROM (
  SELECT id, user_id, total
    FROM orders
   WHERE created_at > now() - interval '7 days'
) recent_orders;

view는 항상 underlying 쿼리로 펼쳐진다. 그래서 view의 인덱스·통계는 원본 테이블 것을 사용합니다. PostgreSQL의 view 자체에 인덱스를 만들 수 없는 이유다 (필요하면 materialized view).

rule (CREATE RULE)

거의 안 쓰는 옛 기능. 한 쿼리를 다른 쿼리로 변환하는 규칙을 등록할 수 있습니다.

CREATE RULE no_delete AS ON DELETE TO logs DO INSTEAD NOTHING;

복잡한 rule은 디버그가 어려워 운영에서는 trigger를 권장합니다. updatable view 구현 내부에서만 자주 보임.

row-level security 변환

RLS(CREATE POLICY)도 이 단계에서 적용됩니다. 정책이 정의한 USING/WITH CHECK 조건이 WHERE 절에 끼어 들어갑니다. 자세한 내용은 Part IX 9.3.

EXPLAIN과 단계

EXPLAIN은 PLAN 단계 결과를 보여 줍니다. 파서·분석기·재작성기는 EXPLAIN으로 안 보이지만, 이 단계의 비용이 누적되는 곳이 있다:

비용빈도
파싱·분석매 쿼리. prepared statement로 한 번만 가능
재작성매 쿼리
카탈로그 lookup매 쿼리 (system catalog cache로 완화)

매우 짧은 단순 SELECT에서는 이 앞 단계의 오버헤드가 실제 실행보다 큰 경우도 있습니다.

prepared statement로 단계 절약

PREPARE q1 (int) AS
  SELECT * FROM orders WHERE user_id = $1;

EXECUTE q1(42);
EXECUTE q1(43);
EXECUTE q1(44);

PREPARE는 파서·분석기·재작성기를 한 번만 거치고 결과 Query Tree를 캐시합니다. EXECUTE는 그 트리를 가져와 PLAN(나중에는 그것도 캐시 가능)부터 시작합니다.

ORM·드라이버 대부분이 내부적으로 prepared statement를 사용한다 (pgJDBC, psycopg, node-postgres 등). pgBouncer transaction 모드에서는 명시적인 PREPARE가 동작하지 않는다는 점만 주의.

카탈로그 조회 비용

분석기·재작성기는 매번 카탈로그를 봅니다. 운영에서 카탈로그 자체가 비대해지면 모든 쿼리가 느려집니다.

사례원인대응
pg_class·pg_attribute가 매우 큼수만 개 테이블/파티션파티션 수 조절, autovacuum on catalog
pg_proc 비대자동 생성된 함수 누적정기 정리
pg_statistic 비대매우 큰 테이블 + 많은 컬럼자동 vacuum이 잘 돌도록

relcache·syscache라는 backend 로컬 캐시가 있어, 같은 카탈로그를 반복 조회할 때는 빠릅니다. 단, 새 연결이 만들어질 때마다 캐시를 다시 채우므로 연결 풀링(13.4)이 중요.

view·rule·materialized view 비교

객체동작
view재작성기에서 항상 풀려 들어감. 자체 스토리지 없음
materialized view실제 테이블처럼 디스크에 저장, REFRESH MATERIALIZED VIEW로 갱신
rule (CREATE RULE)재작성기에서 쿼리 변환. 운영에서는 trigger 권장
trigger실행 시점에 함수 실행. 재작성 단계 무관

정리

  • SQL 처리는 파싱 → 분석 → 재작성 → 계획 → 실행 다섯 단계
  • 파서는 문법만, 분석기가 카탈로그 대조와 타입·권한 검사
  • 재작성기는 view·rule·RLS 정책을 underlying 쿼리에 펼침
  • view는 인덱스를 못 갖는 이유 — 항상 원본 쿼리로 풀려 들어가기 때문
  • prepared statement로 파서·분석기 비용 절약 가능
  • 매우 많은 객체(파티션·함수)는 카탈로그 자체를 비대하게 만들어 모든 쿼리 비용 증가

다음 절(5.2)에서는 분석된 Query Tree를 실제 실행 계획으로 만드는 옵티마이저와 비용 모델을 봅니다.