Scan me!
2026 · Vancouver
Simon Fraser University — Vancouver Campus

Row Pattern Recognition PostgreSQL 20을 향해

PostgreSQL 위에 구현된 ISO/IEC 19075-5 · SQL R020 — 무엇을 만들었고, 무엇이 아직 남아 있으며, 실제로 어떻게 동작하는지.

PGConf.dev 2026 포스터 세션의 사내 follow-up입니다. 2023년 Tatsuo Ishii 의 원작 제안 이후 매 CommitFest 마다 커밋을 향한 도전이 이어졌고, 올해부터 INZENT 가 합류NFA with Context Absorption · Navigation on 1-Slot Tuple Swap (PREV/NEXT/FIRST/LAST · compound) 같은 핵심 설계를 CommitFest 레디 상태까지 다듬은 뒤 PG20-1 CommitFest (2026-05 진입 → 2026-07 회기 마무리) 에서 Ready for Committer 선언을 다시 노립니다.
같은 자료를 Handbook 모드로 펼치면 후속 참조 문서로 사용할 수 있습니다.
관련: pgsql-hackers 스레드 · 최신 패치 v47 · CommitFest #4460
石井 達夫 (Tatsuo Ishii) — SRA OSS K.K.  ishii@postgresql.org  — PostgreSQL Committer · RPR 원작자
최현수 (Henson Choi) — INZENT CO., LTD.  assam@inzent.com  — RPR 공저자 (v38~) · lieutenant
v47 제출 · v48 작업 중 · 2026-07 PG20-1 CF Ready for Committer ▶

What is Row Pattern Recognition?

도입
START UP ▲ UP ▲ DOWN ▼ DOWN ▼ ✓ MATCHED RANGE no match DEFINE START AS TRUE UP price > PREV(price) DOWN price < PREV(price) PATTERN (START UP+ DOWN+)

PATTERN 으로 시퀀스의 형태정규 표현식으로, DEFINE 으로 각 변수의 의미를 선언하면
NFA 엔진이 매치된 구간을 찾아내고, 윈도우 함수가 그 구간을 집계한다.

DEFINE 은 기존 WHERE 와 달리 행 간(row-to-row) 비교가 가능하다 — 네비게이션 함수로 이전·이후 행을 참조한다.

주요 성과 — What Makes This Implementation Different

개발 스토리
① Context Absorption — PATTERN (A+)
Row data C1 (start=1) C2 (start=2) C3 (start=3) 1 A A: cnt=1 2 A A: cnt=2 A: cnt=1 cnt 2>=1 -> absorbed 3 A A: cnt=3 A: cnt=1 cnt 3>=1 -> absorbed 4 A A: cnt=4 5 B B -> FIN OK Only C1 survives. N contexts -> 1 context. Earlier start = higher count. Older context covers newer.
② Navigation: 1-Slot Tuple Swap
PREV(price) — how does it access the previous row? ecxt_outertuple = current row NAV_SET ecxt_outertuple = prev row <- swapped eval expr result price value obtained NAV_RESTORE ecxt_outertuple = current row <- restored
③ Performance vs Trino — AFTER MATCH SKIP PAST LAST ROW
MATCH SUCCESS — PATTERN (A+ B+ C+ D) Rows PostgreSQL Trino Speedup 20,000 19 ms ~0.3 s ~16x 60,000 57 ms ~1.5 s ~26x 100,000 99 ms ~3.3 s ~33x Both scale O(n) — single-pass after match confirmed MATCH FAILURE — PATTERN (A+ B+ C+ E) Rows PostgreSQL Trino Speedup 20,000 17 ms 358 s 21,000x 60,000 51 ms 4,424 s 86,700x 100,000 92 ms 20,014 s 217,000x PG: O(n) via absorption — Trino: O(n^2+), no absorption

RPR — Ready for Committer 까지의 여정

개발 스토리
시점변곡점
2023-06 ~ 2025 Ishii 원작 v1 → v37 (hackers 첫 공개 2023-06-25) · 매 CommitFest 마다 커밋 도전 · parser/analysis/executor 분리
StringSet · regex 기반 매칭 · 3-slot PREV/NEXT — 커뮤니티 리뷰 라운드
2025-12INZENT 의 최현수 (Henson Choi) 합류 · gcov + Valgrind 기반 첫 review report 제출
2026-01-15regex → NFA 설계안 제시 및 채택 (v37 → v38 squash) · StringSet 제거
패턴 변수 한계 26 → RPR_VARID_MAX 252, count int16→int32 · PostgreSQL Committer 石井 達夫 (Tatsuo Ishii) 가 공저자로 등재
2026-01-18Context Absorption O(n²) → O(n) + PREFIX / fixed-length 그룹 absorption 확장
2026-01-19NFA 실행을 Match → Absorb → Advance 3-phase 사이클로 정착 — interleaved 흡수 동기화 깨짐 해결
2026-02-22DFS 순서가 곧 lexical priority 발견 → altPriority 필드 제거 + state-pruning · Reluctant quantifier · 빈 매치 가능한 그룹의 무한 루프 차단
2026-03INZENT 의 장성준 (SungJun Jang) — Oracle 교차검증 (reluctant quantifier 버그 발견) + Trino 성능 비교 (match 실패시 PG O(n) vs Trino O(n²⁺))
2026-03 ~ 04Navigation 확장: offset 없는 PREV/NEXT → offset 있는 PREV/NEXT/FIRST/LAST + 복합 네비게이션 (PREV(FIRST(·)) 등)
3-slot → 1-Slot Tuple Swap opcode (EEOP_RPR_NAV_SET/RESTORE) + JIT
DEFINE boolean coercion · Planner 통합 (cost, pushdown, RPR Var 보존)
2026-05-02DEFINE volatile / NextValueExpr 거부 + walker 단일화 (4중→1) · Recursive query 내 RPR 거부 · v47 hackers 제출 — 9-patch 시리즈, PG20-1 CF 진입 (Needs review)
2026-05-21PGConf.dev 2026 (Vancouver) — 포스터 Q&A 세션 · 4년 작업의 외부 공표 + Trino 비교 성능 공개 · 포스터에 INZENT CO., LTD. 사명 등재
2026-07 (계획)PG20-1 CommitFest 회기 마무리 — 리뷰어 8명 대면 · v47 안정화 + 품질 검증 완료 → Ready for Committer 선언
RPR 전체 완료까지 lieutenant 협업 관계 지속 전망

* lieutenant — Linus Torvalds 가 Linux 커널 서브시스템 메인테이너들을 "kernel lieutenants" 라고 부른 데서 유래. 커미터에게 신뢰받는 부관/대리인 역할.

Ready for Committer 이후 — 진짜 시작

개발 스토리

통념과 달리 Ready for Committer 는 대기열의 끝이 아니라 본격 검토의 시작이다. RPR 은 2023-06 부터 이미 15 CommitFest 를 통과해 여기 도달했고, 같은 사이클이 다시 돌아갈 가능성이 크다.

What Grinds You Down From Here

  • 한 통의 메일 — 수개월 작업이 다시 들춰진다. 설계, 코드, 컨벤션, 문서 표현까지 전부
  • 신규 리뷰어 한 명 — 합류 즉시 그동안의 합의는 리셋된다
  • 줄 안 보이는 큐 — 내 자리는 어디인지 아무도 알려주지 않는다. 더 작거나 더 익은 패치가 먼저 처리된다
  • 수개월의 침묵 — 그리고 한 통의 메일이 v48 을 강제한다
  • 머지 뒤의 영구 책임 — 회귀, ABI, 사용자 보고.
    호환성 무게를 커미터·기여자·INZENT 가 함께 짊어진다
가혹함은 늘 한 줄짜리 어구로 흘러나온다 —
  • — "이 패치가 리뷰를 견뎌낸다면"
  • — "한 가지 중대한 우려가 있다"
  • — "내가 통과시켜도 다른 리뷰어가 같은 지적을 할 것이다"
  • — "나는 다른 구현 방식을 작업해왔다"
  • — "일단 이건 미뤄두자"
  • — "패치가 이미 너무 크다"

한 줄이 패치를 몇 달, 몇 CF 단위로 정체시킨다.

The Cycle Ahead

Ready for Committer 2026-07 RfC 선언 (목표) Committer Review 한 통의 메일 · 수개월의 침묵 신규 리뷰어 합류 → 합의 리셋 대부분 Returned with Feedback v48, v49, ... 끝없는 재출시 반복 일부 Committed (master) 끝이 아니다 — 영구 유지보수의 시작 PROS — 유리한 요인 커미터(Ishii) 가 직접 주도 SQL/RPR R020 (ISO 19075-5) CONS — 불리한 요인 거대 규모 (+46K LOC) 수정 범위가 크다

시계열 패턴을 SQL 로 표현하기

전체 윤곽
START UP ▲ UP ▲ DOWN ▼ DOWN ▼ ✓ MATCHED RANGE no match DEFINE START AS TRUE UP price > PREV(price) DOWN price < PREV(price) PATTERN (START UP+ DOWN+)
Use Cases in the Wild
  • 금융·트레이딩 — 주가 V-shape · 이중바닥 · 변동성 돌파 · 캔들 패턴 탐지
  • 관측·모니터링 — latency spike, error 클러스터, 메트릭 이상치 시퀀스
  • 사용자 행동 분석 — funnel(전환 경로), 세션화, 이탈 직전 행동 패턴
  • 로그·보안 — login → action → logout 시퀀스, 침입·이상 트래픽 패턴
  • IoT·산업 — 센서 상태 전이, 설비 이상 징후, 품질 곡선 분석

공통점: 순서가 본질인 분석 — 단순 집합/필터로는 표현이 어렵고, 자기 조인/LAG 누적은 곧 복잡해진다.

Example — PATTERN (START UP+ DOWN+)
SELECT company, tdate, price,
  first_value(price) OVER w AS start_price,
  last_value(price)  OVER w AS end_price
FROM stock
WINDOW w AS (
  PARTITION BY company ORDER BY tdate
  ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
  AFTER MATCH SKIP PAST LAST ROW
  PATTERN (START UP+ DOWN+)
  DEFINE
    START AS TRUE,
    UP    AS price > PREV(price),
    DOWN  AS price < PREV(price)
);
Result — Query Output
tdate price start end pattern
2024-01-01 100 100 108 START
2024-01-02 110 UP ▲
2024-01-03 120 UP ▲
2024-01-04 115 DOWN ▼
2024-01-05 108 DOWN ▼ ✓
2024-01-06 130 no match
DEFINE 은 조건을 행 간(row-to-row)으로 평가한다.

같은 패턴, 두 가지 표현 — 기존 SQL vs RPR

전체 윤곽
기존 SQL — LAG / Self-Join
WITH d AS (
  SELECT ts, price,
    CASE WHEN price < LAG(price) OVER (ORDER BY ts) THEN 'D'
         WHEN price > LAG(price) OVER (ORDER BY ts) THEN 'U'
    END AS dir
  FROM ticks WHERE symbol='AAA'
)
SELECT ...  -- run-length encoding 을 또 직접 짜야 함
  • 반복 길이 (DOWN 이 몇 행 연속됐는가) — 추가 윈도우 함수 + Self-Join 필요
  • 중첩 패턴 (V 안의 mini-V) — 거의 불가능. 임시 테이블 폭증
  • 재사용 불가 — 다른 데이터에 적용하려면 통째로 재작성
참고 사례 — DuckDB Lambrecht et al., "Democratize MATCH_RECOGNIZE!" (VLDB 2025)MATCH_RECOGNIZE재귀 CTE + 윈도우 함수 로 변환하는 transpiler 를 만들었다.
DuckDB 위에서 Trino 426s → DuckDB 40s (~10× 빠름). 표현이 복잡해도 엔진이 좋으면 충분히 빠를 수 있다 — RPR 의 가치는 속도가 아니라 표현력에 있다.
RPR — 패턴을 선언한다
SELECT ts, price,
  first_value(price) OVER w AS bottom_price
FROM ticks
WINDOW w AS (
  PARTITION BY symbol ORDER BY ts
  ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
  PATTERN (DOWN+ UP+)
  DEFINE
    DOWN AS price < PREV(price),
    UP   AS price > PREV(price)
)
  • 패턴이 한 줄에DOWN+ UP+
  • 의미는 DEFINE 에 — DOWN, UP 이 각각 무엇인지
  • 나머지는 엔진이 — 매치 시작·길이·출력 모두 자동
RPR — Row Pattern Recognition · SQL/RPR (ISO/IEC 19075-5)

표준 — "Feature R020, 'Row pattern recognition: WINDOW clause' of ISO/IEC 9075-2 enhances the WINDOW clause to include row pattern matching."

기존 윈도우는 행을 ① 파티션하고 ② 정렬한 뒤 ③ 각 행 R 마다 window frame 을 정의한다. R020 은 여기에 한 단계를 더한다 — "Using R020, row pattern recognition may be used to further reduce the window frame." PATTERN 으로 시퀀스의 형태를, DEFINE 으로 각 변수의 의미를 행 간(row-to-row) 비교로 선언한다.

매치가 확인되면 표준 용어로 "full window frame""reduced window frame" 으로 축소되고, 기존 윈도우 함수(first_value, last_value, sum 등)는 이 축소된 프레임 위에서 평소처럼 동작한다 — 새로운 실행 모델 없이 "순서 있는 시퀀스" 가 SQL 의 1급 표현으로 격상된다.

패턴이 SQL 문 안에 복잡하게 묻히는 대신, SQL 표면에 "순서 있는 시퀀스" 라는 새 차원으로 선언된다.

SQL/RPR 표준 — R010 vs R020

SQL 표준 정리
R010 — MATCH_RECOGNIZE (PG 미채택)
SELECT *
FROM ticks MATCH_RECOGNIZE (
  PARTITION BY symbol ORDER BY ts
  MEASURES CLASSIFIER() AS var,
           MATCH_NUMBER() AS mno
  ALL ROWS PER MATCH
  PATTERN (DOWN+ UP+)
  DEFINE ...
)
R010 고유 차이점
  • 위치 — FROM 절의 독립 구문, derived table
  • 출력 모델 — 매치 단위 행
  • ONE ROW / ALL ROWS PER MATCH 선택
  • ALL ROWS 3 옵션 — SHOW EMPTY · OMIT EMPTY · WITH UNMATCHED ROWS
  • Anchors ^ / $ PATTERN 내 허용
  • MEASURES — 출력 행 컬럼으로 노출
R020 — 윈도우 절 내 RPR (PG 채택)
SELECT ts, price,
  first_value(price) OVER w
FROM ticks
WINDOW w AS (
  PARTITION BY symbol ORDER BY ts
  ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
  PATTERN (DOWN+ UP+)
  DEFINE ...
)
R020 고유 차이점
  • 위치 — 기존 WINDOW 절 안의 확장
  • 출력 모델 — 각 행마다 reduced window frame, 윈도우 함수가 소비
  • 매치 시작 — 항상 CURRENT ROW
  • 프레임 단위 — ROWS only (RANGE / GROUPS 금지)
  • BETWEEN CURRENT ROW AND … 필수, EXCLUDE NO OTHERS
  • INITIAL (기본) / SEEK — 매치 시작 위치 모드
  • Anchors ^ / $ PATTERN 내 금지
  • MEASURES — 선택, 윈도우 함수 형태로 호출
공통 기능 — R010 · R020 모두 표준 정의

PATTERN · DEFINE · MEASURES · SUBSET · PERMUTE · exclusion {- … -} · empty pattern () · CLASSIFIER() · MATCH_NUMBER() · PREV/NEXT/FIRST/LAST (+ compound) · 정규식 한정자 + * ? {n,m} · reluctant · alternation · grouping · AFTER MATCH SKIP (전 옵션)

R020 부터 접근한 이유 — PostgreSQL 의 기존 Tuple Store 인프라를 재활용 해 구현이 간결하다. R010 은 R020 완성 후 같은 인프라를 활용해 구현 가능.

기능 인벤토리 — PATTERN / 한정자 / 그룹 / 앵커

SQL 표준 정리
정규식 엔진 선택 — Traditional NFA (Perl 모델)

표준은 DFA · Traditional NFA · POSIX NFA 세 가지를 검토한 끝에 POSIX 를 거부했다.

POSIX 는 "leftmost longest" 규칙을 강제해 모든 매치를 다 찾은 뒤 최장을 선택해야 하므로 orders of magnitude 느릴 수 있고, reluctant quantifier 자체를 정의할 수 없다.

결국 "Perl was adopted as the design target for pattern matching rules." — 매치는 가장 이른 행에서 시작하고 발견 즉시 exit, 우선순위는 ① lexicographic ② greedy / reluctant.

SQL/RPR 은 일반 정규식과 달리 알파벳에 특수기호 ^ $ ( ) - [ ] 를 함께 포함하는 parenthesized language 로 NFA 상태를 모델링 — 그룹 진입/이탈, 대안 선택, 제외 구간을 모두 식별 가능한 형태로 추적한다.

✅ 구현 (v47 제출분)
  • 정규식 연산자 + * ? |, 그룹화 ( )
    1회 이상 / 0회 이상 / 0~1회 반복, 대안(|) 과 그룹 — 패턴 표현의 핵심 도구
  • 한정자 {n} · {n,} · {,m} · {n,m}
    정확한 반복 횟수 또는 범위를 지정. 예: A{3,5} = 3~5회 반복
  • Reluctant quantifier *? · +? · ?? · {n}?
    greedy 의 반대 — 가능한 한 짧은 매치 선호. 다중 매치 분리에 유용
  • INITIAL anchor
    매치를 파티션 시작 행에서만 시도 (R020 기본값). 대안 SEEK 은 미구현
⚠️ 표준 금지 · ❌ 미구현
  • ⚠️ 패턴 anchors ^ / $
    매치를 파티션 시작/끝에 고정. R020 윈도우 절 RPR 에서는 표준이 명시적으로 금지 — INITIAL 로 대체
  • ⚠️ 수량자 직접 중첩 A+ +
    A+ + 같은 표현은 모호하므로 금지. 그룹을 통해서만 중첩 허용 — (A+)+
  • ❌ Empty pattern ()
    0 개 행에 매치하는 빈 패턴. 표준이 정의하지만 현 PG 구현은 거부 — 현 아키텍처에서 추가 가능한 항목
  • PERMUTE
    PERMUTE(A,B,C) = 변수들의 모든 순열에 매치. NFA 확장(상태 폭발 차단 포함) 필요
PG 구현 현황 — Traditional NFA 채택부터 v47 제출까지

v37 까지는 기존 regex 기반으로 구현했으나 패턴 변수 한계와 상태 폭발 문제에 도달, v38 부터 자체 NFA 엔진으로 전환 — 표준의 "Perl as the design target" 와 일치하는 traditional NFA 채택.

표준 거부 항목 — R020 의 anchors ^/$ 는 표준이 "precisely the same as in MATCH_RECOGNIZE, except that the anchors (^ and $) are not permitted with row pattern matching in windows." 로 명시 금지 → parser 가 차단. 수량자 직접 중첩 A+ + 도 모호성 때문에 표준이 그룹 강제((A+)+) → 동일하게 parser 거부.

후속 패치로 분리된 항목Empty pattern () 은 빈 매치 무한 루프 방지 로직만 추가하면 현 NFA 위에 land 가능. PERMUTE"lexicographic expansion" 규칙으로 변수 순열을 펼쳐야 하므로 NFA 상태 머신 확장 + 상태 폭발 차단이 필요해 별도 시리즈로 분리.

기능 인벤토리 — 전체 조감 (DEFINE · Navigation · SKIP · 출력 · 패턴 확장)

SQL 표준 정리

표준 SQL/RPR R020 이 정의하는 기능 전체를 4 카테고리로 조감 — 좌상 DEFINE · Navigation, 우상 AFTER MATCH SKIP, 좌하 출력 모델, 우하 패턴·식별자 확장.

상태는 세 가지 — ✅ v47 구현 (즉시 사용 가능), ❌ 후속 패치 (표준 정의되어 있으나 미구현), ⚠️ 표준 금지 (R020 표준이 명시적으로 배제).

미구현 항목 대부분은 match history 인프라 라는 단일 의존성으로 묶여 있어 인프라 한 번 구축으로 다수가 풀린다 — 그래서 후속 패치 시리즈의 우선순위 최상위가 이 인프라다.

✅ DEFINE & Navigation
  • DEFINE — 변수별 부울 조건식 (UP AS price > PREV(price)). 미정의 변수는 항상 참
  • PREV / NEXT — 이전·다음 행 참조 (1-slot 운용)
  • FIRST / LAST — 매치 내 시작·끝 행 참조
  • Compound navPREV(FIRST(x)) · PREV(LAST(x)) · NEXT(FIRST(x)) · NEXT(LAST(x))
❌ 출력 모델 — 후속 패치
  • MEASURES — 매치당 계산 식
  • ONE / ALL ROWS PER MATCH — 출력 모델 선택
  • CLASSIFIER() · MATCH_NUMBER()
  • SUBSET — 변수 합집합 선언
  • RUNNING / FINAL semantics

→ 공통 의존: match history 인프라

✅ AFTER MATCH SKIP + ❌ 일부
  • SKIP PAST LAST ROW — 기본, 매치 직후로 점프
  • SKIP TO NEXT ROW — 매치 시작 다음 행, 겹침 허용
  • SKIP TO FIRST / LAST var — match history 의존, 후속 패치
❌ 패턴·식별자 확장 · ⚠️ 표준 금지
  • PERMUTE — 변수의 모든 순열에 매치하는 패턴 연산자
  • INITIAL · SEEK — 파티션 내 임의 위치 시작, 현 아키텍처에서 추가 가능
  • Empty pattern () — 현 아키텍처에서 가능
  • DEFINE 내 pattern-qualified ref (A.price) — match history 의존
  • ⚠️ DEFINE 내 outer / range-qualified ref — 표준이 "not allowed in DEFINE" 명시 금지
  • ⚠️ {- -} exclusion — R010 전용, R020 의미 없음
왜 match history 인가

match history 는 "어느 행이 어느 패턴 변수에 매핑됐는가" 를 매치 단위로 인덱싱 가능한 형태로 보존 하는 인프라다. 현 v47 의 NFA 실행 상태는 루프 카운터의 깊이(현재 어떤 변수에서 몇 번째 반복인지) 만 보존하고, 매치가 확정되면 시작·끝 행 위치와 카운터만 남는다 — 행→변수 매핑은 명시적으로 기록되지 않는다.

이 정보가 없으면 — MEASURES 는 매치 단위 계산 시 어느 행이 어느 변수인지 알 수 없고, CLASSIFIER() 는 행→변수 매핑 자체를 반환할 수 없으며, SUBSETA.price 같은 qualified reference 는 평가 대상 행 집합을 정할 수 없다. SKIP TO FIRST/LAST var 는 특정 변수의 시작/끝 위치를 모른다.

윈도우 절에 RPR 끼워넣기 — 골격 · 결과 읽기

사용법
골격 — PATTERN · DEFINE 추가
SELECT ts, price,
  count(*)             OVER w AS match_len,
  first_value(price)   OVER w AS start_price,
  last_value(price)    OVER w AS end_price,
  sum(volume)          OVER w AS total_vol
FROM ticks
WINDOW w AS (
  PARTITION BY symbol
  ORDER BY ts
  ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
  PATTERN (DOWN+ UP+)
  DEFINE
    DOWN AS price < PREV(price),
    UP   AS price > PREV(price)
);
  • 프레임 — ROWS BETWEEN CURRENT ROW AND ... 필수.
    금지: RANGE / GROUPS, CURRENT ROW 아닌 시작점, EXCLUDE CURRENT ROW/TIES/GROUP (EXCLUDE NO OTHERS 만 허용)
  • AFTER MATCH SKIP — 옵션, 기본 PAST LAST ROW
  • INITIAL / SEEK — 옵션, 기본 INITIAL (매치 시작 모드). SEEK 미구현
  • PATTERN + DEFINE — RPR 의 필수 키워드 두 개
  • SUBSET · MEASURES — 부가 옵션. ❌ 둘 다 미구현 (match history 의존)
  • 매치는 각 행마다 시도 — "이 행이 매치의 시작인가?" 평가
결과 읽기 — 윈도우 함수와 결합
윈도우 함수 반환값 용도
count(*) OVER w매치 길이 (행 수)시작점 + 길이
first_value(price) OVER w매치 첫 행 값시작 가격·시점
last_value(price) OVER w매치 끝 행 값종료 가격
sum(volume) OVER w매치 구간 누적거래량 합계
  • R020 은 MEASURES 없이도 윈도우 함수로 매치 정보 추출
  • 매치 시작 행: count(*) OVER w > 0 인 행
  • 나머지 행은 0 — AFTER MATCH SKIP PAST LAST ROW 효과
PATTERN (DOWN+ UP+) — 하락(DOWN) 이 1회 이상 이어진 뒤 상승(UP) 이 1회 이상 따라오는 V-shape.
DEFINEDOWN직전보다 낮은 가격, UP직전보다 높은 가격. PREV(price) 가 이전 행 가격을 참조한다.
왜 일부 행만 결과를 갖는가 — RPR 은 각 행을 매치 시작 후보로 보고 패턴 시도, 성공한 시작 행에서만 윈도우 함수가 의미 있는 값(매치 길이·first/last value·누적 합계)을 반환한다.
기본 AFTER MATCH SKIP PAST LAST ROW 정책 — 매치 도중·비매치 행은 모두 매치 시작점이 아닌 행 으로 분류되어 결과가 0 / 로 비워진다.

PATTERN — 정규식 문법 (연산자 · 한정자 · Reluctant)

사용법
반복 연산자
연산자의미예시
+1회 이상A+ — A 가 최소 1번
*0회 이상A* — A 가 0번 이상
?0 또는 1회A? — 있거나 없거나
-- "내려간 뒤 (잠시 평이하다가) 올라간"
PATTERN ( DOWN+ FLAT* UP+ )
한정자 (범위 지정)
한정자의미
{n}정확히 n 번
{n,}n 번 이상
{,m}최대 m 번
{n,m}n 번 이상 m 번 이하
-- UP 이 3~5 행 연속
PATTERN ( UP{3,5} )
표준 금지 — 한정자 직접 중첩 A+ + 은 표준 금지 (구문 오류). 그룹으로만 중첩 허용 — (A+)+. PATTERN 내 anchors ^/$ 도 R020 금지 — INITIAL 로 대체.
택일 · 그룹
연산자의미예시
|택일UP | DOWN — 둘 중 하나
( )그룹화(A B)+ — A·B 쌍 반복
-- 상승 또는 하락 연속
PATTERN ( (UP | DOWN)+ )

-- UP 뒤 DOWN 쌍이 2~4 번
PATTERN ( (UP DOWN){2,4} )
Reluctant Quantifier — 가장 짧은 매치 선호
Greedy (기본)Reluctant차이
A+A+?1회 이상이되 가장 짧게
A*A*?0회 이상이되 가장 짧게
A{n,m}A{n,m}?n~m 이되 n 에 가깝게

실측PATTERN (A+) 으로 5행 데이터: 첫 행 match_len=5 (한 번에 흡수).
PATTERN (A+?): 모든 행 match_len=1.

-- DOWN 은 짧게, UP 은 끝까지
PATTERN ( DOWN+? UP+ )
언제 Reluctant 를 쓰나 — Greedy 는 매치를 최대한 길게 잡아 한 번에 흡수하므로 같은 파티션에서 다중 매치가 어렵다. Reluctant 는 최소 단위로 매치를 끊어 다음 시작점을 빠르게 노출시킨다.

매치 시작 모드 (INITIAL/SEEK) · DEFINE

사용법
INITIAL — 현재 행에서만 매치 시도 (R020 기본값)
WINDOW w AS (
  ORDER BY ts
  ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
  INITIAL PATTERN (A+)
  DEFINE A AS true
)
  • 각 평가 행 R 마다 R 이 매치의 첫 행이 되는 매치만 시도. R 에서 시작하지 않으면 매치 없음
  • "각 행에서 시작하는 누적 패턴" 분석에 적합 — 예: 매 시점에서 그 행을 시작점으로 한 시퀀스 길이 측정
SEEK — 현재 행에서 시작 안해도 진행하며 첫 매치 찾기 (미구현)
WINDOW w AS (
  ORDER BY ts
  ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
  SEEK PATTERN (A+)   -- ERROR
  DEFINE A AS true
)
  • 현재 행 R 에서 매치가 시작하지 않더라도 R+1, R+2 ... 끝까지 진행하며 첫 매치를 찾는다
  • 현 PG 구현은 parser 에서 거부 — SEEK is not supported · Use INITIAL instead
❌ 미구현 · 유효한 활용처

SEEK 는 각 행 R 에 그 자체가 아닌 "R 이후 첫 매치" 의 결과를 연관시킨다.

  • 각 시점에서 "다음 패턴이 어디서 일어나는가" — 다음 V-shape·이상치·세션 시작까지의 거리
  • 드물게 발생하는 시퀀스에 대해 모든 행에 동일한 매치 결과 부여 — "이 행 이후 다음 사건"
  • 이벤트 스트림에서 "다음 트리거" 의 시작점·길이·집계 측정
DEFINE — 각 변수의 부울 조건식
PATTERN (START DOWN+ UP+)
DEFINE
  -- START 는 DEFINE 없음 → 항상 참 (모든 행이 START 후보)
  DOWN AS price < PREV(price),      -- 전 행보다 낮으면 DOWN
  UP   AS price > PREV(price)        -- 전 행보다 높으면 UP
  • 각 변수 = 부울 표현식. 현재 행이 그 변수에 해당하는지 판단.
  • 표현식 안에 PREV·NEXT·FIRST·LAST 네비게이션, 일반 SQL 비교/논리 연산자 사용 가능.
  • PATTERN 에 있고 DEFINE 없는 변수는 항상 참 — START AS true 효과.
  • DEFINE 변수가 PATTERN 에 없으면 구문 오류DEFINE variable "..." is not used in PATTERN.
DEFINE 안에서 금지되는 것
  • ⚠️ 표준 거부 · outer / range-qualified column 참조"not allowed in DEFINE clause"
  • 🔒 PG 정책 · sequence (nextval()) — 부작용 차단
  • 🔒 PG 정책 · volatile 함수 (random(), clock_timestamp() 등) — 표준은 navigation offset 인수에만 결정성 요구(run-time constant).
    현 PG 는 DEFINE 전체에서 volatile 함수 거부 (커미터 정책 결정)
  • ❌ 미구현 · pattern-qualified ref (A.price) — 표준 허용, match history 의존
  • ❌ 과잉 거부 · Subquery — 표준은 "if they do not perform RPR themselves and do not reference row pattern variables" 조건부 허용.
    현 PG 는 모든 subquery 일괄 거부 (의도적, 후속 패치에서 조건부 허용으로 완화 예정)

✅ 안전 사용 — immutable/stable 함수, 현재 행 컬럼, PREV/NEXT/FIRST/LAST, 리터럴·상수

Navigation — PREV/NEXT · FIRST/LAST · Compound

사용법
PREV / NEXT — 행 기준 이동
DEFINE
  DOWN AS price < PREV(price)            -- 1행 전
  BIG  AS price > PREV(price, 3)         -- 3행 전
  NEW  AS NEXT(price) > price * 1.1   -- 다음 행 10% 위?
  • PREV(x) = 직전 행의 x (기본 offset = 1, PREV(x, 1) 와 동일)
  • NEXT(x) = 직후 행의 x (기본 offset = 1, NEXT(x, 1) 와 동일)
  • 2번째 인자 = 거리 (PREV(x, N) = N 행 전, NEXT(x, N) = N 행 후).
    표준은 run-time constant 만 허용 (literal·embedded variable 등, column/subquery 금지)
  • 파티션 경계 너머는 NULL (DEFINE 부울식에서 거짓)
FIRST / LAST — 매치 기준
DEFINE
  UP   AS price > FIRST(price)         -- 매치 첫 행 대비
  PEAK AS price > LAST(price)          -- 매치 직전까지 최고+ε
  • FIRST(x) = 현재 매치의 첫 행에서의 x (기본 offset = 0, FIRST(x, 0) 와 동일)
  • LAST(x) = 현재 매치의 직전 행(마지막 매핑 행)에서의 x (기본 offset = 0, LAST(x, 0) 와 동일)
  • 2번째 인자 = 매치 내 오프셋. 표준은 run-time constant 만 허용
  • 매치 범위 밖이면 NULL (DEFINE 부울식에서 거짓)
  • FIRST 는 참조 기준이 다르다 — PREV/NEXT/LAST 의 오프셋은 현재 행 기준이지만, FIRST 는 매치 시작 행 기준이라 보관 행 범위 계산이 별도로 이루어진다
Compound — 2단 합성 (4 조합)
형식의미
PREV(FIRST(x))매치 첫 행의 1행 전
PREV(LAST(x))매치 직전 행의 1행 전
NEXT(FIRST(x))매치 첫 행의 1행 후
NEXT(LAST(x))매치 직전 행의 1행 후 (= 현재 행)

금지 조합
· 같은 종류 중첩 — PREV(PREV(x)), NEXT(NEXT(x)), FIRST(FIRST(x)), LAST(LAST(x))
· 역방향 — FIRST(PREV(x)), FIRST(NEXT(x)), LAST(PREV(x)), LAST(NEXT(x))
· 매치 기준끼리 / 3단 이상 중첩 — FIRST(LAST(x)), PREV(NEXT(FIRST(x)))
· 중간 표현식 삽입 — PREV(FIRST(x) + 1), PREV(abs(FIRST(x)))

Context Absorption 안전성
Navigationmatch_start dep.absorption
PREV / NEXTnone✓ safe
LAST (no offset)none✓ safe
LAST (with offset)boundary check✗ unsafe
FIRST (any)direct✗ unsafe

Context Absorption — 같은 변수에 연속 매핑되는 행들을 NFA 상태로 통합해 매치 비용을 O(n²) → O(n) 으로 줄이는 핵심 최적화.
흡수 결과는 "매치 시작 행 위치" 정보를 잃기 때문에, 시작 행에 의존하는 navigation(특히 FIRST, offset 있는 LAST)은 흡수와 함께 쓰면 잘못된 값이 나올 수 있다.
그래서 매치 시작 위치에 의존하는 navigation 은 흡수 대상에서 제외하거나 별도 경로로 평가한다.

AFTER MATCH SKIPPAST LAST ROW vs TO NEXT ROW

사용법
SKIP PAST LAST ROW — 기본 · 겹침 없음
WINDOW w AS (
  ORDER BY ts
  ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
  AFTER MATCH SKIP PAST LAST ROW   -- 기본값
  PATTERN (A+)
  DEFINE A AS true
)

매치가 끝나면 매치된 마지막 행의 바로 다음 부터 재시도. 5행 데이터:

x | count
--+------
1 |   5   ← 첫 행에서 5행 매치
2 |   0   ← skip
3 |   0
4 |   0
5 |   0

매치들이 겹치지 않는다. 가장 보편적인 정책.

❌ 미구현 모드 — SKIP TO var

표준은 4 모드 정의 — PAST LAST ROW · TO NEXT ROW · TO FIRST var · TO LAST var. 뒤 두 모드는 PG 미구현.

SKIP TO FIRST/LAST var 는 매치에 참여한 특정 패턴 변수(var) 의 첫·마지막 행으로 점프하는 모드. 매치 안의 부분 구조를 골라 재시도 위치를 정밀하게 제어할 수 있다.

match history 인프라 의존 — MEASURES·SUBSET·CLASSIFIER 와 함께 후속 패치에서 풀린다.

SKIP TO NEXT ROW — 겹침 허용
WINDOW w AS (
  ORDER BY ts
  ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
  AFTER MATCH SKIP TO NEXT ROW
  PATTERN (A+)
  DEFINE A AS true
)

매치가 끝나면 매치 시작 행의 바로 다음 행 부터 재시도. 같은 5행 데이터:

x | count
--+------
1 |   5
2 |   4
3 |   3
4 |   2
5 |   1   ← 각 시점부터의 매치 길이

용도 — 각 시점에서 "여기서 시작하면 얼마나 갈까" 분포 보기. 매치 카운트가 PAST LAST ROW 보다 훨씬 많아지므로 주의.

Context Absorption 과의 관계

PAST LAST ROW — 매치 직후로 점프해 한 매치 안에서 흡수 최적화가 그대로 작동, 전체 비용 O(n) 유지.

TO NEXT ROW — 매 시작 행마다 재시도해 흡수 상태가 매번 초기화. 같은 행이 여러 매치에 중복 참여하므로 누적 비용이 빠르게 증가하며, 거대 파티션에서는 두 모드 간 격차가 수십~수백 배까지 벌어질 수 있다.

Context Absorption — A+ 같은 무한 반복의 처리

사용법
Context Absorption — PATTERN (A+)
Row data C1 (start=1) C2 (start=2) C3 (start=3) 1 A A: cnt=1 2 A A: cnt=2 A: cnt=1 cnt 2≥1 → absorbed 3 A A: cnt=3 A: cnt=1 cnt 3≥1 → absorbed 4 A A: cnt=4 5 B B → FIN ✓ Only C1 survives. N contexts → 1 context. Earlier start = higher count. Older context always covers newer. O(N²) O(N) naïve: N contexts × N rows with absorption: 1 context

표준은 매처가 매 행을 잠재적 매치 시작으로 고려할 것을 요구한다. 순진하게는 N 행마다 새 컨텍스트가 생겨 메모리 O(n²) 로 증가한다.

핵심 관찰 — 같은 패턴 위치에 있는 두 컨텍스트가 무한 수량자 아래 동일한 미래를 가질 때, 오래된(=카운트가 더 높은) 컨텍스트만 남기면 새 컨텍스트는 폐기해도 안전하다. 결과: N 컨텍스트 → 1 컨텍스트, 메모리 O(n²) → O(n).

"같은 운명" 조건을 깨는 술어(FIRST·offset LAST)나 출력 모드(SKIP TO NEXT ROW)가 없을 때 적용된다.

어떤 패턴이 흡수되나
✓ 흡수 가능
단순 무제한 반복
A+ · A* · A{n,}
예: PATTERN (A+) · PATTERN (A+ B+ C+) — 첫 무제한 segment(A+)만 흡수, 그 뒤 B+·C+ 는 일반 처리
고정 길이 group 의 무제한 반복
(A B){n,}
(A (B{2}){3} C)+
예: PATTERN ((UP DOWN){2,}) — 한 group iteration = 고정 길이, 그 그룹이 무제한 반복. 복합 중첩도 outer 가 무제한이면 흡수

(A+ B | C D | E* F) 같은 구조 — 각 대안이 별개 NFA 분기로 전개되고, 분기마다 자체 absorbability 가 판정된다.

📋 PREFIX absorption — 후속 패치
접두사 + 무제한 반복 + 접미사
A B+ C
예: PATTERN (A B+ C) — prefix A 와 무제한 B+ 까지 흡수, suffix C 는 일반 처리

처리 방법 — 고정 길이 접두를 shadow path 로 추적하며 무한 본체에 흡수 적용.

✗ 흡수 불가 (구조적)
Reluctant 한정자
A+?, A*?, A{n,}?
예: PATTERN (A+? B+) — 가능한 짧게 매치, 흡수의 "더 길게" 와 충돌
Fixed length quantifier
A{3}, A{3,5}
예: PATTERN (A{3} B+) — 컨텍스트마다 남은 반복 횟수가 달라 같은 운명 깨짐
매치 시작 의존 DEFINE
FIRST(x), offset LAST(x)
예: DEFINE UP AS price > FIRST(price) — 컨텍스트별 미래가 다름
겹침 허용 SKIP
AFTER MATCH SKIP TO NEXT ROW
출력 측에서 모든 매치 보고 요구 — 흡수해도 결과를 잃음

규칙 — 같은 운명이면 흡수 가능, 다른 운명이면 컨텍스트 보존.

NFA 가 어떻게 동작하나 — 설계와 구동

사용법
플래너 흐름 — 실행 전 패턴 최적화
▸ 패턴 AST 최적화
PATTERN 트리에 다음 정규화/축약을 적용한 뒤 평탄 NFA 배열로 컴파일:
  • 중첩 평탄화 — SEQ(A, SEQ(B, C))SEQ(A, B, C)
  • 인접 변수·그룹 병합 — A AA{2} · (A B)+ (A B)+(A B){2,}
  • 인접 ALT 병합·중복 제거 — (A | B) (A | B)(A | B){2} · (A | (B | C))(A | B | C) · (A | B | A)(A | B)
  • 접두사/접미사 흡수 — A B (A B)+(A B){2,}
  • 안전한 중첩 한정자 축소 — (A+)+A+ · (A{2,3}){5}A{10,15}
이어 각 요소의 흡수 가능 여부, DEFINE 의 매치 시작 의존성, 네비게이션의 lookback/lookahead 거리를 사전 판정해 실행기에 전달.
실행 흐름 — 매 행마다 3 단계
▸ MATCH — 현재 행 소비
현재 행에 대해 DEFINE 의 모든 변수 술어를 한 번에 평가하고, 결과를 살아있는 상태들이 공유 참조한다. 행 참조(PREV·NEXT·FIRST·LAST)는 단일 슬롯에서 대상 행으로 임시 교체 후 복원하는 방식으로 처리된다.
매치 시작에 의존하는 변수(FIRST, offset 있는 LAST)는 컨텍스트마다 시작점이 달라 별도 재평가가 필요하다.
▸ ABSORB — 중복 컨텍스트 통합
Match 직후, 살아있는 컨텍스트들을 NFA 위치별로 비교한다. 같은 위치에 있고 더 오래된(=카운트가 더 높은) 컨텍스트가 새 컨텍스트의 미래를 덮으면 새 컨텍스트는 안전하게 폐기된다.
플래너가 사전 판정한 자격에 따라 활성화되며, 매치 시작 의존 술어나 겹침 허용 SKIP 이 있으면 비활성화. O(n²) → O(n) 효과는 이 단계에서 만들어진다.
▸ ADVANCE — 다음 행 준비
살아남은 상태들을 깊이 우선으로 진행한다 — 다음 패턴 요소로 전이, 그룹 종료, 대안 분기 탐색, FIN(매치 후보) 도달. 분기 순서는 어휘적 우선순위로 정해진다.
같은 분기 안에서 같은 패턴 요소를 다시 방문하려는 시도는 차단되고, 정당한 빈 반복은 그룹을 즉시 종료해 우회한다.
FIN 도달 시 매치 후보 확정 → AFTER MATCH SKIP 정책에 따라 컨텍스트 내 후순위 상태와 매치 범위 내 다른 시작점의 컨텍스트가 정리된다.
가지치기 8가지 — 각 단계에서 상태/컨텍스트를 잘라낸다
▸ Match (2)
술어 실패현재 행에서 DEFINE 술어가 거짓으로 평가된 변수 상태 — 더 이상 진행할 수 없으므로 죽은 분기로 폐기
한정 프레임 컷오프사용자 프레임(예: ROWS BETWEEN ... AND k FOLLOWING) 상한을 넘어선 컨텍스트 — 더 확장할 수 없으므로 강제 mismatch 처리
▸ Absorb (1)
Context absorption같은 NFA 위치에 있고 오래된 컨텍스트가 새 컨텍스트의 미래를 덮을 때 새 컨텍스트는 안전하게 폐기. N 시작점 → 1 컨텍스트로 축약하는 핵심 최적화
▸ Advance (5)
상태 중복 제거서로 다른 DFS 분기를 통해 같은 위치·같은 카운트에 도달한 상태들 — 어휘적으로 앞선 것 하나만 살리고 동등물은 폐기. 매치 선호 규칙(lexicographic preferment)도 이로써 강제된다
후보 매치 시 후순위 상태 폐기컨텍스트 내 — 한 분기가 FIN(=매치 후보)에 도달한 시점에 DFS 큐에 남은 보류 상태들. 모두 어휘적 후순위라 영향 없어 즉시 폐기
매치 범위 내 후행 컨텍스트 폐기컨텍스트 간 — 매치 범위 안에 시작하는 다른 컨텍스트들. SKIP PAST LAST ROW 하에서 출력 불가 → 일괄 제거
Cycle guard같은 DFS 안에서 같은 패턴 요소를 다시 방문하려는 엡실론 확장 차단. nullable 그룹이 빈 매치로 무한히 반복되는 루프를 막는다
Empty-loop fast-forwardcycle guard 가 죽일 수밖에 없는 정당한 빈 반복을 살리는 보완. 남은 필요 반복을 빈 것으로 선언하고 그룹을 즉시 종료해 사이클을 우회한다

Cycle guard 는 무한 루프를 막지만 정당한 빈 매치까지 죽일 수 있고, Empty-loop fast-forward 가 그 빈 매치를 살려준다 — 둘은 짝.

EXPLAIN 으로 RPR 확인

사용법

RPR 은 별도 노드가 아니라 WindowAgg 노드의 속성으로 동작 — EXPLAIN ANALYZE 가 NFA 레벨 통계를 함께 노출한다.

예제 출력 EXPLAIN ANALYZE RPR 쿼리
EXPLAIN ANALYZE
SELECT ts, count(*) OVER w
FROM ticks
WINDOW w AS (
  ORDER BY ts
  ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
  AFTER MATCH SKIP PAST LAST ROW
  INITIAL PATTERN (down+ up+)
  DEFINE
    down AS price < PREV(price),
    up   AS price > PREV(price)
);

WindowAgg
  Output: ts, count(*) OVER w
  Window: w AS (ROWS BETWEEN CURRENT ROW
                AND UNBOUNDED FOLLOWING)
  Pattern: down+" up+
  Nav Mark Lookback: 1
  NFA States: 4 peak, 312 total, 87 merged
  NFA Contexts: 3 peak, 101 total, 47 pruned
  NFA: 12 matched (len 4/18/7.3), 35 mismatched (len 1/3/1.4)
  NFA: 21 absorbed (len 2/15/4.2), 12 skipped (len 0/0/0)
  -> Sort
       Sort Key: ticks.ts
       -> Seq Scan on public.ticks
읽는 법 — 각 항목의 의미
Pattern:플래너 AST 최적화(평탄화·인접 병합·중복 제거 등) 후의 최적화된 패턴이 그대로 출력된다. 식별자는 소문자. 흡수 표기 — " 는 흡수 판정점, ' 는 흡수 분기 영역
Nav Mark LookbackPREV 와 offset 있는 LAST 의 최대 후방 도달. tuplestore 가 몇 행을 보관해야 navigation 이 안전한지 결정. 상수면 정수, 비상수 표현식이면 runtime, 범위 미상이면 retain all
Nav Mark LookaheadFIRST 와 compound PREV/NEXT(FIRST) 의 최대 전방 도달. 매치 시작 행 기준으로 측정. FIRST 가 쿼리에 사용되었을 때만 출력
NFA Statespeak 동시 살아있던 상태의 최댓값 · total 쿼리 수명 동안 생성된 누적 · merged 중복 제거로 병합되어 사라진 수
NFA Contextspeak · total · pruned 가지치기 (후순위 상태·매치 범위 후행 컨텍스트 제거) 로 제거된 컨텍스트 수
NFA: matched / mismatched한 줄에 둘 같이 출력. 성공한 매치 / 실패한 매치 시도 카운트 + 매치 길이의 min/max/avg 분포. 실패 길이가 길면 NFA 가 헛수고를 많이 한다는 신호
NFA: absorbed / skipped한 줄에 둘 같이 출력. 흡수로 통합된 컨텍스트 / AFTER MATCH SKIP 으로 평가 없이 폐기된 컨텍스트 + 길이 분포

성능 진단의 핵심 — matched 와 absorbed 의 비중이 크면 NFA 가 효율적으로 동작 중.
mismatched 의 카운트나 길이가 비정상적으로 크면 매치되지 않는 경로를 길게 추적하고 있다는 뜻.
States/Contexts 의 peak 가 데이터 크기에 비례해 증가하면 흡수가 작동하지 않는 패턴일 가능성이 크다.

최종 로드맵 — 잔여 기능 · 거부 사항 · 다음 단계

로드맵
Match History 독립 — 단독 패치로 항목별 진행
SEEK현재 행 R 에서 매치가 시작하지 않더라도 R+1, R+2 … 끝까지 진행해 첫 매치를 찾는다. 각 시점에서 "다음 패턴이 어디서 일어나는가" 측정에 유용
Empty pattern ()0 개 행에 매치하는 빈 패턴. 표준이 정의하지만 PG 가 거부
PREFIX absorption접두사 + 무제한 반복 + 접미사 형태 (예: A B+ C) 의 앞부분 흡수 확장. shadow path 로 접두 길이 추적하며 무한 본체에 흡수 규칙 적용
PERMUTEPERMUTE(A,B,C) = 변수들의 모든 순열에 매치 (ABC·ACB·BAC·…)
DEFINE subquery 조건부 허용표준은 (a) 중첩 RPR 없음 + (b) outer pattern var 미참조 두 조건 만족 시 허용. 현 PG 는 일괄 거부 — 정적 검사 두 항목 추가로 완화
Match History 의존 — 인프라 패치 선행 후 조각 패치로 분할
MEASURES매치당 계산식을 윈도우 함수 형태로 노출 (R020 의 optional 절). 매치 내 변수별 집계·내비게이션 결과를 직접 조회 가능
MATCH_NUMBER()파티션 내 순차 매치 번호 반환 — 같은 행이 여러 매치에 참여할 때 매치 식별, 매치 카운트 집계
CLASSIFIER()현재 행이 어느 패턴 변수에 매핑됐는지 변수명을 반환
SUBSETprimary 변수들의 union 을 별도 식별자로 선언 (예: SUBSET U = (A, B, C)). MEASURES·DEFINE 에서 묶음 단위로 참조 가능
패턴 한정 참조 (UP.price)DEFINE/MEASURES 안에서 특정 패턴 변수에 매핑된 행의 컬럼만 참조. price 가 아닌 UP.price 처럼 변수 범위 한정
SKIP TO [FIRST/LAST] var매치 후 특정 패턴 변수의 첫·마지막 행으로 점프
R010 MATCH_RECOGNIZE — 커뮤니티 합의 + 대형
FROM tbl MATCH_RECOGNIZE (...)독립 RPR 구문 도입. Match History 인프라 위에서 land 가능하지만 채택은 SQL 표준위/PG 커뮤니티 정책 결정 사안
ONE / ALL ROWS PER MATCH매치 단위(요약) / 매치 내 모든 행(상세) 선택, ALL ROWS 의 3 옵션(SHOW EMPTY · OMIT EMPTY · WITH UNMATCHED ROWS)
Anchors ^/$PATTERN 안에서 허용 (R020 는 금지)
{- -} exclusion매치는 인식하되 출력에서 제외 — R010 전용 기능
⚠️ 표준 거부 — R020 명시 금지
윈도우 프레임RANGE/GROUPS 금지 (ROWS only), 시작 CURRENT ROW, EXCLUDE NO OTHERS 외 금지
PATTERNanchors ^/$ 금지, 수량자 직접 중첩 (A+ +) 금지
Navigation 중첩2 레벨 이상, 같은 종류·매치 기준끼리 중첩 금지
DEFINEouter / range-qualified 컬럼 참조 금지, 변수 중복 정의 금지
Recursive query 안 RPR표준 명시 금지 (완화는 R010 정책 결정 대기)
{- -} exclusionR010 전용, R020 의미 없음
🔒 PG 정책 — 결정성·부작용 차단
DEFINE 내 volatile 함수표준은 navigation offset 만, PG 는 DEFINE 전체 확장 (커미터 정책)
DEFINE 내 sequence (nextval())부작용 차단

다음 단계 — 2026-07 PG20-1 CF 진입을 기점으로 리뷰 사이클 지속. 메인 라인 머지 시점에 도달하면,
독립 항목은 항목별 단독 패치로, 의존 항목은 Match History 인프라 패치를 먼저 land 한 뒤 조각 패치로 분할 진행.

NFA Simulator — 라이브 데모

사용법
컴파일된 NFA — 컬럼 참조
type
변수 이름, 또는
BEGIN / END (그룹 마커)
ALT (alternation 시작)
FIN (패턴 완료) 중 하나
d
그룹 중첩 깊이 (0 = 최상위)
quant
반복 범위 min..max
는 무한
nx
"next" — 이 요소를 소비한 후 진행할 요소 인덱스
(· = 없음)
jp
"jump" — 분기 대상
ALT는 대안 연결
BEGIN은 선택적 그룹 건너뜀
END는 본체로 loop back
flags
rel reluctant 수량자
elp empty-loopable 그룹 끝
rgn 흡수 가능 영역 안
abs absorption 비교 지점
markers
축약 뷰의 deparsed 패턴
수량자 뒤에 붙는 표식:
" absorption 비교 지점
' 흡수 가능 영역 안

정리 · Q&A — RPR 한 장 컨닝 페이퍼

Closing

WINDOW + RPR 골격

WINDOW w AS (
  PARTITION BY ... ORDER BY ...
  ROWS BETWEEN CURRENT ROW
        AND UNBOUNDED FOLLOWING
  AFTER MATCH SKIP PAST LAST ROW
  INITIAL PATTERN (A+ B+)
  DEFINE
    A AS price < PREV(price),
    B AS price > PREV(price))
  • 프레임 — ROWS only, 시작 CURRENT ROW 필수
  • EXCLUDE NO OTHERS 외 금지
  • 매 행이 매치 시작 후보

PATTERN 연산자 · 한정자

+1회 이상 *0회 이상 ?0 또는 1 |택일 ( )그룹 {n}정확히 n {n,}n 이상 {,m}최대 m {n,m}n~m +? *? ??reluctant

⚠️ 표준 금지 (PATTERN)

  • Anchors ^ $ — R020 명시 금지 (R010 만 허용)
  • 수량자 직접 중첩 A+ + — 그룹 강제 (A+)+
  • {- -} exclusion — R010 전용, R020 의미 없음
  • RANGE / GROUPS 프레임
  • Recursive query 안 RPR (parser 거부)

INITIAL / SEEK

  • INITIAL — 현재 행에서만 매치 시도 (R020 기본)
  • SEEK — R+1, R+2 ... 끝까지 진행해 첫 매치 찾기

DEFINE

  • 각 변수 = 부울 표현식. 행 단위 판정
  • PATTERN 에 있고 DEFINE 없는 변수 = 항상 참
  • DEFINE 만 있고 PATTERN 에 없으면 구문 오류

Navigation (DEFINE 내)

  • PREV(x, N) · NEXT(x, N) — 행 기준
  • FIRST(x, N) · LAST(x, N) — 매치 기준
  • 2번째 인자 = run-time constant (literal·var)
  • 경계 너머 = NULL (부울식에서 거짓)

Compound — 2단 합성 4개

  • PREV(FIRST(x)) · PREV(LAST(x))
  • NEXT(FIRST(x)) · NEXT(LAST(x))
  • 그 외 조합 (같은 종류 중첩 · 역방향 · 3단 · 중간 표현식) 모두 거부

❌ DEFINE 안 금지

  • ⚠️ outer / range-qualified ref (표준)
  • 🔒 volatile · nextval() (PG 정책)
  • A.price (match history 의존)
  • Subquery (PG 과잉 거부)
  • immutable/stable · 현재 행 컬럼 · nav · 상수

한눈에 — V-shape 매치

START UP ▲ UP ▲ DOWN ▼ DOWN ▼ ✓ MATCHED RANGE no match

AFTER MATCH SKIP

  • PAST LAST ROW — 기본, 매치 직후 점프, 겹침 없음
  • TO NEXT ROW — 매치 시작 다음 행, 겹침 허용, 흡수 무력
  • TO FIRST / LAST var — match history 의존

NFA 실행 — 3 단계 / 행

  • MATCH — 현재 행에 DEFINE 술어 일괄 평가, 1-slot tuple swap 으로 PREV/NEXT/FIRST/LAST 처리
  • ABSORB — 같은 NFA 위치 컨텍스트들 비교, 오래된 것이 새것의 미래를 덮으면 새것 폐기
  • ADVANCE — DFS 어휘 우선순위로 다음 요소 전이 / FIN 도달 → SKIP 정책 적용

Context Absorption

  • N 시작점 → 1 컨텍스트 · O(n²) → O(n)
  • 가능 — A+·A*·A{n,} · 고정길이 그룹 무제한 반복 (A B){n,}
  • 불가 — reluctant · 고정 한정자 · FIRST/offset LAST · SKIP TO NEXT ROW
  • nav 안전 — PREV/NEXT·LAST(no off) / LAST(off)·FIRST
  • 후속: PREFIX absorption (A B+ C)

가지치기 8 — 단계별

  • M 술어 실패 · 한정 프레임 컷오프
  • A Context absorption
  • V 상태 중복 제거 · 후보 매치 시 후순위 상태 폐기 · 매치 범위 내 후행 컨텍스트 폐기 · Cycle guard · Empty-loop fast-forward

EXPLAIN — 핵심 항목

  • Pattern: 최적화된 패턴 · 흡수 "=판정점 '=분기
  • Nav Mark Lookback/Lookahead — 상수/runtime/retain all, Lookahead 는 FIRST 사용 시만
  • NFA States/Contexts — peak/total/merged · peak/total/pruned
  • NFA: matched / mismatched (len min/max/avg)
  • NFA: absorbed / skipped — skipped = SKIP 폐기

진단 — mismatched 길이↑ 헛수고 · peak ∝ N 흡수 무효

R010 vs R020

  • R010 MATCH_RECOGNIZE — FROM 절 독립 (PG 미채택)
  • R020 윈도우 절 RPR (PG 채택) — reduced frame
  • 공통 — PATTERN·DEFINE·MEASURES·SUBSET·PERMUTE·CLASSIFIER·MATCH_NUMBER·nav·SKIP

후속 패치 — 우선순위

  • 독립 (항목별 단독) — SEEK · Empty pattern () · PREFIX absorption · PERMUTE · DEFINE subquery 조건부
  • 의존 (Match History 인프라 선행) — MEASURES · MATCH_NUMBER · CLASSIFIER · SUBSET · A.price · SKIP TO var
  • 대형 — R010 MATCH_RECOGNIZE (정책 결정 사안)
2026 · Vancouver
Simon Fraser University — Vancouver Campus

Row Pattern Recognition PostgreSQL 20을 향해

PostgreSQL 위에 구현된 ISO/IEC 19075-5 · SQL R020 — 무엇을 만들었고, 무엇이 아직 남아 있으며, 실제로 어떻게 동작하는지.

PGConf.dev 2026 포스터 세션의 사내 follow-up입니다. 2023년 Tatsuo Ishii 의 원작 제안 이후 매 CommitFest 마다 커밋을 향한 도전이 이어졌고, 올해부터 INZENT 가 합류해 NFA with Context Absorption·Navigation on 1-Slot Tuple Swap 같은 핵심 설계를 CommitFest 레디 상태까지 다듬은 뒤 PG20-1 CommitFest (2026-07) 에서 Ready for Committer 선언을 다시 노립니다.
石井 達夫 (Tatsuo Ishii) — SRA OSS K.K.
 ishii@postgresql.org
최현수 (Henson Choi) — INZENT CO., LTD.
 assam@inzent.com

§1. RPR 이란 무엇인가

1.1 시퀀스를 SQL 의 1급 표현으로

기존 SQL 은 행을 집합 으로 다룬다. WHERE 는 각 행을 독립적으로 평가하고, GROUP BY 는 행을 모아 요약한다. 이 모델은 "이번 분기 매출 합계" 같은 질문에는 강하지만, "주가가 두 번 연속 떨어진 뒤 한 번 오른 V 자 구간을 모두 찾아라" 같은 질문에는 약하다. 순서가 있는 시퀀스 자체가 SQL 의 1급 표현이 아니었기 때문이다.

SQL 표준 R020 (ISO/IEC 19075-5) 은 윈도우 절을 확장해 이 부족을 메운다. WINDOW 안에 PATTERNDEFINE 절을 더하면, 행의 시퀀스에 정규식을 선언할 수 있다.

SELECT ts, price,
  first_value(price)
    OVER w AS start_price
FROM ticks
WINDOW w AS (
  PARTITION BY symbol ORDER BY ts
  ROWS BETWEEN CURRENT ROW
        AND UNBOUNDED FOLLOWING
  PATTERN (DOWN+ UP+)
  DEFINE
    DOWN AS price < PREV(price),
    UP   AS price > PREV(price)
)

PATTERN형태 를, DEFINE 은 각 변수의 의미 를 선언한다. 위 쿼리는 "하락이 한 번 이상 이어진 뒤 상승이 한 번 이상 따라오는" V 자 구간을 찾는다. DEFINE 은 기존 WHERE 와 달리 PREV · NEXT 같은 네비게이션 함수로 행 간 비교가 가능하다.

매치가 확인되면 표준 용어로 "full window frame" 이 "reduced window frame" 으로 축소된다. 이후 first_value · last_value · sum · count 같은 기존 윈도우 함수가 그 축소된 프레임 위에서 평소처럼 동작한다. 새로운 실행 모델 없이 — 순서 있는 시퀀스가 SQL 표면에 새로운 차원으로 격상되는 것이다.

START UP UP DOWN DOWN ✓ MATCHED RANGE no match
V 자 구간(녹색 박스)이 매치되고, 이어 다시 오르기 시작하는 구간(붉은 박스)에서는 매치가 깨진다.

1.2 활용처

순서가 본질적인 데이터 분석은 도메인을 가리지 않는다. 금융·트레이딩에서는 캔들 패턴, V-shape, 이중바닥, 변동성 돌파 같은 시계열 형상을 찾는다. 관측·모니터링에서는 레이턴시 스파이크, 에러 클러스터, 메트릭 이상치의 연쇄를 추적한다. 사용자 행동 분석에서는 퍼널(전환 경로), 세션화, 이탈 직전 행동 패턴을 추출한다. 로그·보안에서는 login → action → logout 시퀀스나 침입·이상 트래픽 패턴을, IoT·산업에서는 센서 상태 전이와 설비 이상 징후를 잡아낸다.

공통점은 분명하다 — 단순 집합 연산이나 필터로는 표현이 어렵고, 자기 조인이나 LAG 누적으로 풀면 곧 복잡해지는 영역이다. RPR 은 이 영역을 SQL 의 일부로 끌어들인다.

1.3 만들어진 것 — 두 가지 핵심 설계

현 PostgreSQL 구현은 표준 R020 의 핵심 기능에 더해, 효율과 표현력을 받치는 두 가지 굵직한 설계를 담고 있다.

Context Absorption — O(n²) → O(n)

표준은 매처가 모든 행을 잠재적 매치 시작 후보로 고려할 것을 요구한다. 순진하게 구현하면 N 행마다 새 컨텍스트가 생겨 메모리가 O(n²) 로 증가한다. 핵심 관찰은 다음과 같다 — 무한 수량자(A+) 아래에서 같은 NFA 위치에 도달한 두 컨텍스트가 동일한 미래 를 가진다면, 오래된 쪽만 남기고 새 쪽은 안전하게 폐기할 수 있다. 결과적으로 N 개의 컨텍스트가 1 개로 압축된다.

Row data C1 (start=1) C2 (start=2) C3 (start=3) 1 A A: cnt=1 2 A A: cnt=2 A: cnt=1 absorbed 3 A A: cnt=3 A: cnt=1 absorbed 4 A A: cnt=4 5 B B → FIN OK Only C1 survives. N contexts → 1 context. Earlier start = higher count. Older context covers newer.
패턴 A+ 에 대해 매 행마다 새 컨텍스트가 생기지만, 모두 같은 미래로 수렴해 가장 오래된 C1 하나로 흡수된다.

흡수가 가능한 조건은 매치 시작 위치에 의존하지 않는 패턴 이다. 단순 A+, 고정 길이 그룹의 무제한 반복 (A B){n,} 같은 구조에서 적용된다. 반면 매치 시작 행을 참조하는 술어 — FIRST, offset 있는 LAST, 또는 inner 가 그 둘인 compound 형 — 가 있으면 컨텍스트마다 미래가 달라져 흡수할 수 없고, 이 경우 모든 컨텍스트가 살아남는다 (§4.1 의 흡수 안전성 표에서 자세히 다룬다).

Navigation on 1-Slot Tuple Swap

DEFINE 안에서 PREV(price) 는 이전 행의 값을 어떻게 가져오는가? 단순한 방법은 행마다 슬롯을 따로 두고 그쪽 컬럼을 평가하는 것이다. 그러나 PREV, NEXT, FIRST, LAST, 그리고 그 합성형까지 모두 별도 슬롯을 잡으면 메모리·캐시 비용이 커진다.

PostgreSQL 구현은 단일 슬롯(ecxt_outertuple) 만 운용한다. 네비게이션을 만나면 그 슬롯의 튜플을 임시로 대상 행의 튜플로 교체(NAV_SET)하고, 표현식을 평가한 뒤 원래 튜플로 복원(NAV_RESTORE)한다. 슬롯 한 칸으로 모든 행 참조를 처리하므로 메모리 오버헤드가 일정하고, JIT 컴파일까지 그대로 통과한다.

Trino 와의 비교

Trino 도 같은 표준 MATCH_RECOGNIZE 를 구현한다. 매치 성공 케이스에서는 양쪽 모두 단일 패스로 O(n) — 패턴이 한 번 잡히면 그 뒤로 SKIP 정책에 따라 진행하므로 비용이 선형이다. 그러나 매치 실패 케이스에서 차이가 드러난다.

케이스20k60k100k
매치 성공 (A+ B+ C+ D) ~16x~26x~33x
매치 실패 (A+ B+ C+ E) 21,000x 86,700x 217,000x

PostgreSQL 대비 Trino 의 실행 시간 비율. 매치 실패에서 격차가 극단으로 벌어진다.

성공·실패 양쪽 모두 단일 패스 O(n) 를 유지하는 비결이 바로 context absorption 이다. Trino 는 흡수 최적화가 없어 매치가 끝까지 못 가는 경로마다 컨텍스트 폭증이 일어나고, 결과적으로 O(n²⁺) 으로 폭발한다. 이 한 가지 사실이 현 구현의 핵심 — 표현력은 표준이 정의하고, 효율은 absorption 이 보장한다.

참고로 매치 성공 케이스의 16~33 배 격차는 알고리즘만의 차이는 아니다. Trino 는 JVM 위에서 분산 실행을 가정해 구동되는 반면 PostgreSQL 은 C 로 작성된 단일 프로세스 엔진이라 평탄한 데이터에서는 시동·인터프리트 오버헤드만으로도 한 자릿수 배 차이가 자연스럽게 난다. 알고리즘 격차의 본 모습은 매치 실패 케이스 — 같은 시동 비용 위에서 21,000 배 ~ 217,000 배로 벌어지는 — 에서 드러난다.

§2. SQL 표준 정리

2.1 두 가지 진입점 — R010 과 R020

SQL 표준의 Row Pattern Recognition 은 두 형태로 정의되어 있다. R010 MATCH_RECOGNIZEFROM 절에 들어가는 독립 구문이고, R020 은 기존 WINDOW 절을 확장하는 형태다. PostgreSQL 은 R020 부터 진입했다 — 기존 Tuple Store 인프라를 재활용해 구현이 간결하기 때문이다. R010 은 R020 완성 후 동일 인프라 위에 올릴 수 있다.

R010 — MATCH_RECOGNIZE (PG 미채택)
SELECT *
FROM ticks MATCH_RECOGNIZE (
  PARTITION BY symbol ORDER BY ts
  MEASURES CLASSIFIER() AS var
  ALL ROWS PER MATCH
  PATTERN (DOWN+ UP+)
  DEFINE ...
)

위치는 FROM 절의 derived table. 출력 모델은 매치 단위 행이며, ONE ROWS PER MATCH 또는 ALL ROWS PER MATCH 를 선택할 수 있다 (후자는 SHOW EMPTY / OMIT EMPTY / WITH UNMATCHED ROWS 3 옵션). 패턴 내 anchors ^/$ 를 허용하고, MEASURES 절을 통해 매치 단위 계산식을 출력 컬럼으로 노출한다.

R020 — 윈도우 절 RPR (PG 채택)
SELECT ts, price,
  first_value(price) OVER w
FROM ticks
WINDOW w AS (
  PARTITION BY symbol ORDER BY ts
  ROWS BETWEEN CURRENT ROW
        AND UNBOUNDED FOLLOWING
  PATTERN (DOWN+ UP+)
  DEFINE ...
)

기존 WINDOW 절 안에 들어간다. 각 행마다 reduced window frame 이 만들어지고, 윈도우 함수가 이를 소비한다. 매치 시작은 항상 CURRENT ROW 이며, 프레임 단위는 ROWS 만 허용된다 (RANGE / GROUPS 금지). 시작 모드는 INITIAL (기본) 또는 SEEK 이다. 패턴 내 anchors ^/$ 는 표준이 명시적으로 금지한다.

두 형태는 같은 정규식 어휘와 같은 네비게이션 의미론을 공유한다 — PATTERN, DEFINE, MEASURES, SUBSET, PERMUTE, {- -} exclusion, CLASSIFIER(), MATCH_NUMBER(), PREV/NEXT/FIRST/LAST (+ compound), 정규식 한정자 + * ? {n,m}, reluctant 한정자, alternation, grouping, AFTER MATCH SKIP 의 모든 옵션이 공통이다. 다른 점은 위치(FROM 절 vs WINDOW 절)와 출력 모델뿐이다.

2.2 정규식 엔진 선택 — Traditional NFA

표준은 정규식 엔진으로 DFA · Traditional NFA · POSIX NFA 세 가지를 검토한 끝에 POSIX 를 거부했다. POSIX 는 "leftmost longest" 규칙을 강제해 모든 매치를 다 찾은 뒤 최장을 선택해야 하므로 orders of magnitude 느릴 수 있고, 결정적으로 reluctant 한정자 자체를 정의할 수 없다. 표준은 결국 "Perl was adopted as the design target for pattern matching rules" 로 정리했다 — 매치는 가장 이른 행에서 시작하고 발견 즉시 exit, 우선순위는 lexicographic 순서와 greedy/reluctant 선택을 따른다.

PostgreSQL 도 v37 까지는 기존 regex 인프라 위에 구현했으나 패턴 변수 한계와 상태 폭발 문제에 막혀 v38 부터 자체 NFA 엔진으로 전환했다. 자체 NFA 는 표준의 "Perl as design target" 과 정확히 일치하는 traditional NFA 다. 일반 정규식과 다른 점은 알파벳에 ^ $ ( ) - [ ] 같은 특수기호가 함께 포함되는 parenthesized language 로 NFA 상태를 모델링한다는 것 — 그룹 진입/이탈, 대안 선택, 제외 구간을 모두 식별 가능한 형태로 추적한다.

2.3 PATTERN 의 어휘

현재 PostgreSQL 이 받아들이는 PATTERN 구문은 다음과 같다.

구문의미
+ * ?각각 1회 이상 / 0회 이상 / 0~1회 반복
{n} {n,} {,m} {n,m}정확한 반복 횟수 또는 범위
+? *? ?? {n,m}?Reluctant — greedy 의 반대, 가능한 짧게
|대안 (alternation)
( )그룹화
INITIAL매치를 파티션 시작 행에서만 시도 (R020 기본값)

표준이 정의하지만 PostgreSQL 이 거부하는 항목도 명확하다. R020 윈도우 절 RPR 에서 anchors ^ / $ 는 표준이 "precisely the same as in MATCH_RECOGNIZE, except that the anchors (^ and $) are not permitted with row pattern matching in windows" 로 명시 금지하므로 parser 가 차단한다. 수량자 직접 중첩 A+ + 도 모호성 때문에 표준이 그룹을 강제((A+)+) 하므로 동일하게 거부된다. 빈 패턴 ()PERMUTE(A,B,C) 는 표준이 정의하지만 현재 구현이 아직 받아들이지 않는다 — 둘 다 후속 패치에서 추가 가능한 항목이다 (PERMUTE 는 NFA 상태 머신 확장과 상태 폭발 차단이 필요해 별도 시리즈로 분리되어 있다).

구현 측 제약 한 가지 — PATTERN 안에 등장할 수 있는 서로 다른 변수의 개수는 252 개 (RPR_VARID_MAX) 로 제한된다. 실용 한도는 한참 위이지만 NFA 요소의 varId 필드가 1 바이트라 BEGIN·END·ALT·FIN 같은 예약 ID 를 빼고 남는 슬롯 수가 그 값이다. 초과 시 too many pattern variables 오류로 거부된다.

2.4 DEFINE · Navigation · 출력 · SKIP

PATTERN 외 네 카테고리의 인벤토리는 구현 상태가 갈린다.

DEFINE 과 Navigation

DEFINE 은 변수마다 부울 조건식을 매긴다 (UP AS price > PREV(price)). PATTERN 에 있지만 DEFINE 이 없는 변수는 항상 참으로 간주된다. 네비게이션 함수로는 PREV / NEXT (이전·다음 행 참조, 1-slot 운용) 와 FIRST / LAST (매치 내 시작·끝 행 참조) 가 있다. 이 둘의 합성은 외부 행 기준 · 내부 매치 기준 조합인 PREV(FIRST()) · PREV(LAST()) · NEXT(FIRST()) · NEXT(LAST()) 4 가지로 제한된다. 그 외 조합 — 같은 종류 중첩 (PREV(NEXT()), FIRST(LAST()) 등), 역방향 (외부 매치 기준 · 내부 행 기준, 예: FIRST(PREV())), 3 단 이상 중첩, 합성 내부에 표현식 삽입 — 은 parser 가 모두 거부한다.

AFTER MATCH SKIP

표준이 정의하는 4 모드 중 PostgreSQL 은 두 개를 구현한다. PAST LAST ROW (기본, 매치 직후로 점프) 와 TO NEXT ROW (매치 시작 다음 행, 겹침 허용). 나머지 두 모드 SKIP TO FIRST varSKIP TO LAST var 는 후속 패치 대상이다 — 매치에 참여한 특정 패턴 변수의 첫·마지막 행으로 점프하는 모드인데, 어느 행이 어느 변수에 매핑됐는지를 알아야 하므로 뒤에서 설명할 match history 인프라에 의존한다.

출력 모델

R020 의 MEASURES · ONE/ALL ROWS PER MATCH · CLASSIFIER() · MATCH_NUMBER() · SUBSET · RUNNING/FINAL semantics 는 모두 표준의 일부지만 현 PostgreSQL 구현에는 포함되어 있지 않다. MEASURES 없이도 일반 윈도우 함수(first_value, count(*), sum 등) 로 매치 정보를 충분히 추출할 수 있다는 점이 R020 의 강점이지만, 표준이 정의하는 출력 옵션 전체를 지원하려면 별도 인프라 작업이 필요하다.

DEFINE 안의 거부 항목

DEFINE 표현식 안에서 거부되는 항목은 출처가 셋으로 갈린다. (a) 표준 명시 금지 로는 outer / range-qualified 컬럼 참조 ("not allowed in DEFINE clause") 가 있고, WITH RECURSIVE 안에서의 RPR 사용도 같은 부류다. (b) PostgreSQL 정책 으로 거부하는 것은 volatile 함수와 sequence (nextval()) — 결정성과 부작용 차단을 위해 커미터 정책으로 정한 항목이다. (c) 미구현 으로 거부하는 것은 pattern-qualified reference (A.price) 와 subquery 다. 전자는 match history 인프라가, 후자는 정적 검사 두 개 추가가 land 의 전제다 (자세한 풀이는 §3.4 · §5.5).

패턴 확장

{- ... -} exclusion 은 R010 전용이라 R020 에서는 의미가 없다. 그 외 PATTERN 어휘 확장 후보 (PERMUTE, empty pattern ()) 는 §2.3 에서 다뤘다.

2.5 match history — 후속 패치의 단일 의존성

위 미구현 항목 대부분은 단 하나의 인프라에 묶여 있다 — match history, 즉 "어느 행이 어느 패턴 변수에 매핑됐는가" 를 매치 단위로 인덱싱 가능한 형태로 보존하는 자료구조다. 현 NFA 실행 상태는 루프 카운터의 깊이(현재 어떤 변수에서 몇 번째 반복인지)만 보존하고, 매치가 확정되면 시작·끝 행 위치와 카운터만 남는다 — 행→변수 매핑은 명시적으로 기록되지 않는다.

이 정보가 없으면 매치 단위 계산을 할 수 없다. MEASURES 는 어느 행이 어느 변수인지 알지 못하고, CLASSIFIER() 는 행→변수 매핑 자체를 반환할 수 없으며, MATCH_NUMBER() 는 매치 식별 번호를 매길 수 없다. SUBSETA.price 같은 qualified reference 는 평가 대상 행 집합을 정할 수 없고, SKIP TO FIRST/LAST var 도 특정 변수의 시작·끝 위치를 모른다. 그래서 match history 인프라 한 번을 land 하면 다수의 미구현 기능이 한꺼번에 풀린다 — 후속 패치 시리즈의 우선순위 최상위에 놓이는 이유다.

§3. 사용법

3.1 윈도우 절에 RPR 끼워넣기

R020 의 사용은 의외로 단순하다. 기존 윈도우 절 안에 PATTERNDEFINE 두 키워드만 추가하면 된다. 다른 절은 모두 평소처럼 동작한다 — 출력 컬럼은 SELECT 에, 데이터 소스는 FROM 에, 윈도우 함수는 OVER w 에 적는다.

SELECT ts, price,
  count(*)
    OVER w AS match_len,
  first_value(price)
    OVER w AS start_price,
  last_value(price)
    OVER w AS end_price,
  sum(volume)
    OVER w AS total_vol
FROM ticks
WINDOW w AS (
  PARTITION BY symbol
  ORDER BY ts
  ROWS BETWEEN CURRENT ROW
        AND UNBOUNDED FOLLOWING
  PATTERN (DOWN+ UP+)
  DEFINE
    DOWN AS price < PREV(price),
    UP   AS price > PREV(price)
)

윈도우 프레임은 반드시 ROWS BETWEEN CURRENT ROW AND ... 형식이어야 한다. RANGEGROUPS 는 RPR 과 함께 쓸 수 없고, 시작점도 CURRENT ROW 가 아닌 다른 값은 허용되지 않는다. EXCLUDE 절은 NO OTHERS 외 다른 값을 받지 않는다 — 표준이 그렇게 못 박았기 때문이다.

매치는 각 행을 매치 시작 후보 로 보고 시도된다. 어떤 행 R 에서 PATTERN 이 성립하면 그 행이 매치의 시작점이 되고, 윈도우 함수는 축소된 프레임에서 의미 있는 값을 반환한다. 매치되지 않은 행에서는 count(*) OVER w 가 0 이고 first_value 등은 NULL 이다 — 기본 AFTER MATCH SKIP PAST LAST ROW 정책의 자연스러운 결과다.

3.2 PATTERN — 정규식 어휘

PATTERN 은 자주 만나는 정규식과 거의 같은 어휘를 쓴다. + 는 1회 이상, * 는 0회 이상, ? 는 0 또는 1회를 의미한다. 정확한 횟수가 필요하면 {n}, 범위는 {n,m}, 한쪽이 열려 있으면 {n,} 또는 {,m} 으로 적는다.

-- 하락 → (잠시 평이) → 상승
PATTERN ( DOWN+ FLAT* UP+ )

-- UP 이 3~5 행 연속
PATTERN ( UP{3,5} )

-- 상승 또는 하락 연속
PATTERN ( (UP | DOWN)+ )

-- UP 뒤 DOWN 쌍이 2~4 번
PATTERN ( (UP DOWN){2,4} )

한정자 직접 중첩(A+ +)은 표준이 금지한다. 같은 효과를 원하면 그룹을 거쳐야 한다 ((A+)+). 패턴 내 anchors ^$ 도 R020 에서는 금지되는데, INITIAL 이 시작 위치를 정하는 역할을 대체한다.

각 한정자에는 reluctant 변형 (+?, *?, ??, {n,m}?) 이 있다. 기본 greedy 는 매치를 가능한 길게 잡지만, reluctant 는 가능한 짧게 잡는다 — 같은 파티션에서 짧은 매치 여러 개를 끊어내고 싶을 때 유용하다. 예를 들어 PATTERN (A+) 로 5 개 행을 받으면 첫 행에서 길이 5 의 매치 하나가 나오지만, PATTERN (A+?) 로 받으면 5 개 행 각각이 길이 1 의 매치가 된다.

3.3 INITIAL — 매치 시작 모드

R020 의 기본 모드 INITIAL 은 각 평가 행 R 마다 "R 이 매치의 첫 행이 되는 매치만 시도" 한다. 즉 R 에서 시작하지 않는 매치는 무시된다. "각 시점에서 그 행을 시작점으로 한 시퀀스의 길이" 같은 분석에 자연스럽게 맞는다.

대안인 SEEK 모드는 — R 에서 시작하지 않더라도 R+1, R+2 ... 끝까지 진행하며 첫 매치를 찾는다 — 표준에 정의되어 있으나 현 PostgreSQL 구현은 parser 단계에서 거부한다 (ERROR: SEEK is not supported / HINT: Use INITIAL instead.). SEEK 가 동작하면 "이 행 이후 다음 V 자 패턴이 어디서 일어나는가" 같은 질문을 자연스럽게 던질 수 있다 — 후속 패치의 후보다.

3.4 DEFINE — 변수의 의미

DEFINE 은 각 패턴 변수에 부울 표현식을 매긴다. 현재 행이 그 변수에 해당하는지 판단하는 술어다. 표현식 안에는 일반 SQL 의 비교·논리 연산자, immutable/stable 함수, 그리고 PREV·NEXT·FIRST·LAST 네비게이션을 자유롭게 쓸 수 있다.

DEFINE 이 없는 변수도 합법이다 — PATTERN 에 등장하지만 DEFINE 에 매기지 않은 변수는 항상 참 으로 평가된다. 반대로 DEFINE 에는 있는데 PATTERN 에 등장하지 않는 변수는 구문 오류(DEFINE variable "..." is not used in PATTERN) 다.

DEFINE 안에서 금지되는 항목은 몇 가지로 나뉜다.

안전하게 사용할 수 있는 것은 immutable/stable 함수, 현재 행 컬럼, PREV/NEXT/FIRST/LAST, 리터럴·상수 다.

3.5 Navigation

네비게이션 함수는 DEFINE 표현식에서 다른 행을 참조하기 위해 쓴다. 두 가지 기준이 있다 — 행 기준의 PREV/NEXT 와 매치 기준의 FIRST/LAST.

DEFINE
  -- 행 기준 (직전 행)
  DOWN AS price < PREV(price),
  -- 행 기준 (3 행 전)
  BIG  AS price > PREV(price, 3),

  -- 매치 첫 행 대비
  UP   AS price > FIRST(price),
  -- 매치 직전 행 대비
  PEAK AS price > LAST(price)

두 번째 인자는 거리/오프셋이다 — PREV(x, N) 는 N 행 전, FIRST(x, N) 은 매치 시작점에서 N 행 뒤를 가리킨다. 표준은 이 인자로 run-time constant 만 허용한다 (literal, embedded variable 가능, column 참조나 subquery 불가). 파티션 경계 너머나 매치 범위 밖을 가리키면 NULL 이 반환되고, DEFINE 부울식에서는 거짓으로 평가된다.

두 단계 합성도 가능하다 — PREV(FIRST(x)), PREV(LAST(x)), NEXT(FIRST(x)), NEXT(LAST(x)) 네 가지 형태로, 외부는 행 기준이고 내부는 매치 기준이다. 같은 종류끼리(PREV(PREV()), FIRST(LAST())) 또는 역방향 (FIRST(PREV())) 중첩은 거부된다. 중간에 연산자가 끼는 형태 (PREV(FIRST(x) + 1)) 도 마찬가지다.

3.6 AFTER MATCH SKIP

매치 하나가 성립한 뒤 다음 매치를 어디서 시도할지를 정하는 절이다. 표준은 4 가지 모드를 정의하고, PostgreSQL 은 이 중 두 개를 지원한다.

SKIP PAST LAST ROW (기본)

매치된 마지막 행의 바로 다음 행에서 재시도한다. 매치들이 겹치지 않고, 가장 일반적인 정책이다. 5 행 데이터에서 PATTERN (A+) 를 평가하면 첫 행에서 길이 5 의 매치 하나가 잡히고, 나머지 행은 모두 skip 되어 count(*) OVER w 가 0 으로 나온다.

SKIP TO NEXT ROW

매치 시작 행의 바로 다음 행에서 재시도한다. 같은 행이 여러 매치에 중복으로 참여할 수 있다. 같은 5 행에 PATTERN (A+) 를 적용하면 각 행이 자기 자신부터 시작하는 매치를 만들어 카운트가 5, 4, 3, 2, 1 로 줄어드는 분포가 나온다 — "이 시점에서 시작하면 얼마나 멀리 갈 수 있는가" 를 보고 싶을 때 유용하다.

성능 측면에서는 두 모드가 크게 다르다. PAST LAST ROW 는 한 매치 안에서 context absorption 이 그대로 작동해 전체 비용이 O(n) 으로 유지되지만, TO NEXT ROW 는 매 시작점마다 매치를 다시 시도하기 때문에 흡수 효과가 사라지고 누적 비용이 시작점 수에 비례해 증가한다. 결과적으로 한쪽은 선형, 다른 쪽은 초선형 — 파티션이 커질수록 두 모드 간 격차가 빠르게 벌어진다.

남은 두 모드 SKIP TO FIRST varSKIP TO LAST var 는 매치에 참여한 특정 패턴 변수의 첫·마지막 행으로 점프하는 모드다 — 매치 안의 부분 구조를 골라 재시도 위치를 정밀하게 제어할 수 있지만, 행→변수 매핑이 필요하므로 match history 인프라 의존이다.

§4. NFA 내부 · 진단

4.1 Context Absorption — 흡수의 원리

§1 에서 흡수가 N 개의 컨텍스트를 1 개로 압축한다고 언급했다. 좀 더 정밀하게 보면, 흡수가 적용되는 조건은 단순하다 — 같은 NFA 위치에 있는 두 컨텍스트가 무한 수량자 아래에서 동일한 미래 를 가진다면, 오래된(=카운트가 더 높은) 쪽이 새 쪽의 모든 가능한 결과를 이미 포함한다. 따라서 새 컨텍스트는 폐기해도 결과에 영향이 없다.

"동일한 미래" 조건이 깨지는 두 가지 부류가 있다. 첫째는 매치 시작에 의존하는 술어FIRST 와 offset 있는 LAST 는 매치 시작 행의 위치를 직접 참조하므로 컨텍스트마다 결과가 달라진다. 둘째는 겹침 허용 SKIPSKIP TO NEXT ROW 는 출력 측에서 모든 매치 보고를 요구하므로 흡수해버리면 결과를 잃는다.

흡수 적용 가능 여부는 패턴 구조와 DEFINE 사용에 따라 달라진다.

구조흡수예시
단순 무제한 반복A+ · A* · A{n,}
고정 길이 그룹의 무제한 반복(A B){n,} · (A (B{2}){3} C)+
접두사 + 무제한 반복 + 접미사📋 후속A B+ C — PREFIX absorption
Reluctant 한정자A+? · A*?
고정 한정자A{3} · A{3,5}
매치 시작 의존 DEFINEUP AS price > FIRST(price)
겹침 허용 SKIPAFTER MATCH SKIP TO NEXT ROW

대안(A | B)이 섞인 패턴은 각 대안이 별개 NFA 분기로 전개되고, 분기마다 자체 absorbability 가 판정된다. 한 분기에서 흡수가 가능하면 그 분기 안에서만 컨텍스트가 압축된다.

4.2 플래너 단계의 패턴 최적화

플래너는 PATTERN AST 를 NFA 로 컴파일하기 전에 몇 가지 정규화·축약을 적용한다. 이 단계에서 흡수 가능 여부와 네비게이션의 lookback/lookahead 거리도 함께 사전 판정된다. 적용되는 변환은:

플래너의 결과는 평탄한 NFA 배열로 컴파일되고, 각 요소의 흡수 가능 여부, DEFINE 의 매치 시작 의존성, 네비게이션의 lookback/lookahead 거리가 사전 판정되어 실행기에 전달된다. EXPLAIN 에 출력되는 Pattern: 라인이 이 최적화된 결과다.

4.3 실행의 세 단계 — Match · Absorb · Advance

매 행마다 NFA 는 세 단계 사이클을 돈다. 슬라이드와 deepdive 양쪽 모두 강조하는 골격이다.

MATCH — 현재 행 소비

현재 행에 대해 DEFINE 의 모든 변수 술어를 한 번에 평가한다. 결과는 살아있는 모든 상태가 공유 참조한다. 행 참조 (PREV·NEXT·FIRST·LAST) 는 단일 슬롯에서 대상 행으로 임시 교체 후 복원하는 1-slot tuple swap 방식으로 처리된다 (§1.3 참조). 매치 시작에 의존하는 변수(FIRST, offset 있는 LAST) 는 컨텍스트마다 시작점이 달라 별도 재평가가 필요하다.

ABSORB — 중복 컨텍스트 통합

Match 직후, 살아있는 컨텍스트들을 NFA 위치별로 비교한다. 같은 위치에 있고 더 오래된(=카운트가 더 높은) 컨텍스트가 새 컨텍스트의 미래를 덮으면 새 컨텍스트는 안전하게 폐기된다. 플래너가 사전 판정한 자격에 따라 활성화되며, 매치 시작 의존 술어나 겹침 허용 SKIP 이 있으면 비활성화된다. O(n²) → O(n) 효과는 이 단계에서 만들어진다.

ADVANCE — 다음 행 준비

살아남은 상태들을 깊이 우선으로 진행한다. 다음 패턴 요소로 전이하고, 그룹을 종료하고, 대안 분기를 탐색하고, FIN(매치 후보)에 도달한다. 분기 순서는 어휘적 우선순위로 정해진다. 같은 분기 안에서 같은 패턴 요소를 다시 방문하려는 시도는 차단되고, 정당한 빈 반복은 그룹을 즉시 종료해 우회한다. FIN 도달 시 매치 후보가 확정되고, AFTER MATCH SKIP 정책에 따라 컨텍스트 내 후순위 상태와 매치 범위 내 다른 시작점의 컨텍스트가 정리된다.

4.4 가지치기 8 — 단계별 정리

세 단계 사이클은 각 단계에서 의미 없는 상태와 컨텍스트를 잘라낸다. 총 8 가지 가지치기가 작동한다 (Match 단계에서 카운트가 찬 상태를 그룹 end 로 즉시 진행시키는 inline end-chain advance 와, FIN 도달 시점의 컨텍스트 간 정리는 각 단계의 일부로 묶었다).

▸ Match 단계 (2)

▸ Absorb 단계 (1)

▸ Advance 단계 (5)

Cycle guard 와 Empty-loop fast-forward 는 짝으로 작동한다 — 전자는 무한 루프를 막고, 후자는 그 과정에서 죽어버리는 정당한 빈 매치를 살린다.

4.5 EXPLAIN 으로 보기

RPR 은 별도 plan node 가 아니라 WindowAgg 노드의 속성으로 동작한다. EXPLAIN ANALYZE 가 NFA 레벨의 통계를 함께 노출하므로 진단에 활용할 수 있다.

WindowAgg
  Output: ts, count(*) OVER w
  Window: w AS (ROWS BETWEEN CURRENT ROW
                AND UNBOUNDED FOLLOWING)
  Pattern: down+" up+          -- " = 흡수 판정점
  Nav Mark Lookback: 1            -- PREV(price) → 1 행 후방
  NFA States: 4 peak, 312 total, 87 merged
  NFA Contexts: 3 peak, 101 total, 47 pruned
  NFA: 12 matched (len 4/18/7.3), 35 mismatched (len 1/3/1.4)
  NFA: 21 absorbed (len 2/15/4.2), 12 skipped (len 0/0/0)
  -> Sort
       Sort Key: ticks.ts
       -> Seq Scan on public.ticks

각 항목의 의미는 다음과 같다.

항목의미
Pattern플래너 AST 최적화 후의 패턴이 그대로 출력된다. 식별자는 소문자. 흡수 표기는 수량자 뒤의 " (흡수 판정점) 와 ' (흡수 분기 영역) 두 종류다.
Nav Mark LookbackPREV 와 offset 있는 LAST 의 최대 후방 도달 거리. tuplestore 가 보관할 행 수를 결정한다. RPR 이 있으면 항상 출력 — PREV·offset LAST 미사용 시 0, 상수 offset 이면 정수, 비상수 표현식이면 runtime, 범위 미상이면 retain all 로 표시.
Nav Mark LookaheadFIRST 와 compound PREV/NEXT(FIRST) 의 최대 전방 도달 거리. FIRST 가 쿼리에 사용되었을 때만 출력된다.
NFA Statespeak (동시 살아있던 상태의 최댓값), total (쿼리 수명 동안 생성된 누적), merged (중복 제거로 병합되어 사라진 수).
NFA Contextspeak / total / pruned. pruned 는 가지치기(후순위 상태·매치 범위 후행 컨텍스트 제거)로 사라진 컨텍스트 수.
matched / mismatched성공한 매치 / 실패한 매치 시도 카운트와 길이 분포(min/max/avg). 실패 길이가 길면 NFA 가 헛수고를 많이 한다는 신호.
absorbed / skipped흡수로 통합된 컨텍스트 / AFTER MATCH SKIP 으로 평가 없이 폐기된 컨텍스트와 길이 분포.

성능 진단의 핵심은 단순하다 — matchedabsorbed 의 비중이 크면 NFA 가 효율적으로 동작 중이다. mismatched 의 카운트나 길이가 비정상적으로 크면 매치되지 않는 경로를 길게 추적하고 있다는 뜻이다. States/Contexts 의 peak 가 데이터 크기에 비례해 증가하면 흡수가 작동하지 않는 패턴일 가능성이 크다 — 매치 시작 의존 술어나 reluctant 한정자, 고정 한정자가 흡수를 막고 있는지 확인해야 한다.

§5. 로드맵

현 구현의 R020 핵심 기능을 land 한 뒤에 남는 작업은 크게 세 갈래로 나뉜다 — 단독 패치로 진행 가능한 독립 항목, match history 인프라 선행이 필요한 의존 항목, 그리고 R010 MATCH_RECOGNIZE 본체다. 그 옆에 표준 자체가 금지하는 항목과 PostgreSQL 정책으로 거부하기로 한 항목이 별도로 있다.

5.1 Match History 독립 항목

이 그룹은 현재 NFA 위에 바로 land 가능한 기능이다. 각 항목은 독립 패치로 진행할 수 있고, 머지 시점도 서로 묶이지 않는다.

항목설명
SEEK현재 행 R 에서 매치가 시작하지 않더라도 R+1, R+2 … 끝까지 진행해 첫 매치를 찾는다. 각 시점에서 "다음 패턴이 어디서 일어나는가" 측정에 유용. NFA 엔진 자체는 그대로 재사용 가능하지만, 윈도우 함수의 reduced frame 결정 로직이 "R 에서 시작" 대신 "R 이후 첫 매치" 의미를 반영하도록 추가 작업이 필요하다.
Empty pattern ()0 개 행에 매치하는 빈 패턴. 표준 정의에 포함되어 있고, 빈 매치 무한 루프 방지 로직만 추가하면 현 NFA 위에서 동작.
PREFIX absorption접두사 + 무제한 반복 + 접미사 형태(A B+ C)에서 앞부분까지 흡수 확장. 고정 길이 접두를 shadow path 로 추적하면서 무한 본체에 흡수 규칙을 적용한다.
PERMUTEPERMUTE(A, B, C) = 변수들의 모든 순열에 매치. NFA 상태 머신 확장 + 상태 폭발 차단 필요. 별도 시리즈로 분리되어 있다.
DEFINE subquery 조건부 허용표준은 (a) 중첩 RPR 없음, (b) outer pattern variable 미참조 두 조건 만족 시 subquery 를 허용한다. 현 PG 는 일괄 거부하는데, 정적 검사 두 항목을 추가하면 표준대로 완화된다.

5.2 Match History 의존 항목

§2.5 에서 설명한 match history 인프라 — "어느 행이 어느 패턴 변수에 매핑됐는가" 를 매치 단위로 보존하는 자료구조 — 가 선행되어야 land 가능한 기능들이다. 인프라 패치 한 번을 land 하면 아래 항목 모두가 조각 패치로 분할되어 빠르게 따라온다.

항목설명
MEASURES매치당 계산식을 윈도우 함수 형태로 노출. 매치 내 변수별 집계·내비게이션 결과를 직접 조회할 수 있다.
MATCH_NUMBER()파티션 내 순차 매치 번호 반환. 같은 행이 여러 매치에 참여할 때 매치를 식별하거나 매치 카운트를 집계할 때 쓴다.
CLASSIFIER()현재 행이 어느 패턴 변수에 매핑됐는지 변수명을 반환.
SUBSETprimary 변수들의 union 을 별도 식별자로 선언 (예: SUBSET U = (A, B, C)). MEASURES · DEFINE 에서 묶음 단위로 참조 가능.
패턴 한정 참조 A.priceDEFINE / MEASURES 안에서 특정 패턴 변수에 매핑된 행의 컬럼만 참조.
SKIP TO [FIRST/LAST] var매치 후 특정 패턴 변수의 첫 · 마지막 행으로 점프하는 SKIP 모드.

5.3 R010 — MATCH_RECOGNIZE 본체

R010 MATCH_RECOGNIZE 는 R020 와 동일한 정규식·네비게이션 엔진 위에 올라간다. match history 인프라가 갖춰진 뒤 land 자체는 가능하지만, 채택 여부는 PostgreSQL 커뮤니티의 정책 결정 사안이다 — 새 구문 도입의 크기, SQL 표준위원회 동향, R020 만으로 충분한지의 판단이 함께 걸린다.

R010 이 도입되면 R020 에 없는 몇 가지 기능이 따라온다 — ONE / ALL ROWS PER MATCH 와 그 3 옵션(SHOW EMPTY · OMIT EMPTY · WITH UNMATCHED ROWS), PATTERN 내 anchors ^ / $ 허용, {- ... -} exclusion (매치는 인식하되 출력에서 제외) 등이다.

5.4 표준이 금지하는 것

"거부" 라고 부르지만 실제로는 표준이 명시 금지한 항목이고, parser 단계에서 차단되는 것이다. 표준이 그렇게 못 박은 이유는 모호성을 피하거나 R020 의 window-frame 의미론에 맞지 않기 때문이다.

5.5 PG 정책으로 거부하는 것

표준이 명시 금지하지는 않지만, PostgreSQL 의 결정성·부작용 원칙에 따라 거부하기로 한 항목이다. 향후 정책 재검토는 열려 있지만 현 구현은 다음을 막는다.

5.6 다음 단계

2026-07 PG20-1 CommitFest 진입을 기점으로 리뷰 사이클이 다시 돌기 시작한다. 메인 라인 머지 시점에 도달하면, 위 분류대로 독립 항목은 항목별 단독 패치로, 의존 항목은 match history 인프라 패치를 먼저 land 한 뒤 조각 패치로 분할되어 진행된다. R010 본체는 그 다음 단계의 정책 결정 사안이다.