# Postgres 19의 프로퍼티 그래프 이해하기

> Clean Markdown view of GeekNews topic #31383. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=31383](https://news.hada.io/topic?id=31383)
- GeekNews Markdown: [https://news.hada.io/topic/31383.md](https://news.hada.io/topic/31383.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-07-13T10:36:02+09:00
- Updated: 2026-07-13T10:36:02+09:00
- Original source: [neovintage.org](https://neovintage.org/posts/postgres-property-graphs/)
- Points: 2
- Comments: 0

## Topic Body

- Postgres 19의 **프로퍼티 그래프**는 기존 테이블을 정점과 간선으로 선언하고 `MATCH`로 고정된 관계 패턴을 검색하는 SQL/PGQ 기능으로, 데이터를 복사하거나 별도 그래프 실행 엔진을 만들지 않음  
- 그래프 패턴은 **관계형 조인으로 컴파일**되어 기존 옵티마이저·인덱스·통계를 그대로 사용하므로, 느린 순회도 일반 조인과 같은 방식으로 분석하고 최적화할 수 있음  
- 차원 테이블은 정점, 순수 조인 테이블은 간선으로 자연스럽게 대응하지만, 여러 외래 키와 자체 속성을 가진 **팩트 테이블**은 이벤트를 나타내는 허브 정점으로 모델링하는 편이 적합함  
- 하나의 테이블을 서로 다른 별칭으로 정점과 여러 간선에 동시에 선언할 수 있어, `results`의 기존 외래 키를 이용한 **별도 간선 테이블을 생성·저장할 필요가 없음**  
- Postgres 19는 **가변 길이 경로를 지원하지 않으므로** 최단 경로·N홉 도달성·PageRank에는 맞지 않으며, 관계 구조를 미리 아는 고정 길이 탐색에 적합함  
  
---  
  
### 관계형 스키마를 그래프로 읽는 방식  
- 정규화된 관계형 스키마에서 `drivers`, `constructors`, `circuits` 같은 차원 테이블은 엔터티를 담고, 팩트 테이블은 경기 결과나 예선 세션 같은 이벤트를 기록함  
- 각 행은 잠재적인 **정점**이고, 외래 키는 다른 행을 가리키는 잠재적인 **간선**임  
  - ER 다이어그램은 스키마 수준의 그래프에 해당함  
  - 실제 행과 외래 키 관계는 인스턴스 수준의 그래프에 해당함  
- “각 결과에 해당하는 constructor를 찾는다”는 요청은 관계형 SQL에서 `results JOIN constructors ON ...`로 작성하는 그래프 순회임  
- SQL/PGQ는 그래프 자체를 새로 추가하지 않고, 조인을 직접 나열하는 대신 **그래프 형태로 질문을 작성**하게 해줌  
- [RelBench](https://neovintage.org/posts/relational-deep-learning) 방식은 테이블을 pandas로 가져와 메모리에서 PyTorch 그래프로 구성하지만, Postgres 프로퍼티 그래프는 데이터베이스 안의 기존 테이블 관계를 그대로 사용함  
- [PyG](https://pytorch-geometric.readthedocs.io/en/latest/)의 예제도 주로 평면 파일이나 메모리 내 그래프를 사용함  
- Formula 1 데이터셋으로 실험한 코드는 [GitHub 저장소](https://github.com/neovintage/relational-deep-learning-with-pg19)에서 확인할 수 있음  
  
### 프로퍼티 그래프의 구성  
- `CREATE PROPERTY GRAPH`는 기존 테이블 위에 만드는 **이름 있는 선언 객체**임  
  - `VERTEX TABLES`는 행을 정점으로 읽을 테이블을 지정함  
  - `EDGE TABLES`는 행을 연결 관계로 읽을 테이블을 지정함  
- 각 정점 테이블에는 다음 요소를 정의함  
  - `KEY`: 정점의 식별자로, 대부분 기본 키를 사용함  
  - `LABEL`: `MATCH`에서 사용할 정점 유형 이름임  
  - `PROPERTIES`: 그래프 질의에서 접근할 수 있는 열 목록임  
- 각 간선 테이블에는 `SOURCE`와 `DESTINATION`을 지정하며, 두 값은 각각 정점의 키를 참조함  
- `CREATE PROPERTY GRAPH`는 **데이터를 이동하거나 복사하지 않음**  
  - 행은 원래 테이블에 그대로 남아 있음  
  - 선언은 기존 외래 키 구조를 그래프로 읽는 방법만 정의함  
- 프로퍼티 그래프의 구조적 요소는 **정점과 간선 두 종류뿐**임  
  - 레이블과 프로퍼티는 정점 또는 간선에 속하는 속성임  
  - `psql`에서 각 요소의 `Element Kind`는 `vertex` 또는 `edge` 중 하나임  
  
### `MATCH`로 고정된 패턴 질의하기  
- 그래프 질의는 `GRAPH_TABLE(...)` 안에 `MATCH` 패턴을 작성하는 방식임  
- 다음 패턴은 driver에서 result를 거쳐 race로 이어지는 관계를 나타냄  
  
```sql  
MATCH (d IS driver)<-[IS of_driver]-(res IS result)-[IS in_race]->(ra IS race)  
```  
  
- `COLUMNS` 절은 그래프 질의가 외부로 반환할 열을 지정함  
- 바깥쪽 `SELECT`에서는 `GRAPH_TABLE(...)`의 결과를 일반 테이블처럼 조회할 수 있음  
- 패턴에 방향과 레이블이 포함되어 관계를 문장처럼 읽을 수 있으므로, 동일한 다중 조인보다 구조를 파악하기 쉬움  
  
### 실행 시 관계형 조인으로 변환되는 구조  
- `MATCH`는 Postgres에 별도로 덧붙인 그래프 실행 엔진이 아니라 **관계형 조인으로 컴파일되는 문법**임  
- driver–result–race 패턴에 `EXPLAIN`을 실행하면 기반 테이블을 대상으로 한 4개의 해시 조인이 나타남  
- 직접 작성한 조인과 마찬가지로 다음 요소를 사용함  
  - 기존 Postgres 옵티마이저  
  - 기존 인덱스  
  - 기존 통계  
- 그래프 순회가 느리다면 원인과 최적화 방법도 일반 조인이 느린 경우와 같음  
- `psql`에서도 테이블과 유사한 명령으로 그래프를 조사할 수 있음  
  - `\dG`: 프로퍼티 그래프 목록을 표시함  
  - `\d f1`: 각 정점과 간선, 기반 테이블, 요소 종류, 간선의 출발·도착 정점을 보여줌  
  - `\d+ f1`: 전체 `CREATE PROPERTY GRAPH` 문을 재구성하며, 간선 테이블의 기본 키에서 추론한 간선 키도 포함함  
  
### 키와 프로퍼티의 차이  
- 정점의 `KEY`로 지정한 열이 자동으로 **프로퍼티가 되지는 않음**  
- `driver_id`를 정점 키로만 지정한 뒤 `d.driver_id = 1`로 필터링하면, 해당 열이 질의 가능한 프로퍼티가 아니므로 실패함  
- 키는 정점을 식별하지만 질의에 노출하지는 않음  
- ID 열을 필터링하거나 반환하려면 `PROPERTIES` 목록에 명시적으로 추가해야 함  
  
### Postgres 19의 가변 길이 경로 제약  
- Postgres 19는 간선을 1~3회 따라가는 식의 **요소 패턴 수량자**를 지원하지 않음  
  
```text  
ERROR: element pattern quantifier is not supported  
```  
  
- 두 홉을 탐색하려면 `MATCH`에 간선 패턴 두 개를 명시해야 함  
- 임의 길이의 경로는 프로퍼티 그래프 문법으로 표현할 수 없음  
- 깊이가 열려 있는 탐색은 기반 테이블에 대한 **재귀 CTE**로 처리해야 하며, 이 경우 프로퍼티 그래프 문법을 벗어남  
  
### 기존 테이블을 정점과 간선으로 매핑하기  
- ## 차원 테이블은 정점  
  - `drivers`, `constructors`, `circuits`처럼 안정적인 기본 키와 속성을 가진 엔터티 테이블은 정점으로 직접 대응함  
  - 기본 키를 정점 키로 사용하고 필요한 열을 프로퍼티로 노출하면 됨  
- ## 순수 조인 테이블은 간선  
  - `student_courses(student_id, course_id)` 같은 다대다 브리지 테이블은 두 엔터티를 연결하는 것이 본래 역할이므로 **간선으로 자연스럽게 대응**함  
  - 한쪽 외래 키를 출발 정점, 다른 쪽 외래 키를 도착 정점으로 선언함  
  - 조인 테이블의 각 행 자체가 하나의 관계이므로 별도의 그래프용 데이터 변환이 필요하지 않음  
  - 이 구조에서는 `students`와 `courses`가 정점이고, `student_courses`가 `enrolled_in` 간선이 됨  
- ## 팩트 테이블은 이벤트 정점  
  - `results` 행은 driver, race, constructor라는 세 엔터티를 가리키면서 `grid`, `position`, `points`, `status` 같은 자체 데이터도 가짐  
  - SQL/PGQ 간선은 출발점 하나와 도착점 하나를 가진 **이항 관계**이므로, 세 개의 외래 키를 가진 행 전체를 단일 간선으로 만들 수 없음  
  - 팩트 행 자체가 분석 대상이라면 해당 테이블을 정점으로 선언하는 편이 적합함  
  - `results` 정점은 이벤트와 그 속성을 보유함  
  - `results_driver`, `results_race`, `results_constructor` 같은 좁은 간선이 외부 엔터티와 연결함  
  - 이 모델은 `driver <- result -> race` 같은 **허브 구조**를 만들며, result 정점에서 멈춰 필터링하거나 속성을 조회할 수 있음  
  - 관계 자체가 관심 대상이면 간선으로, 고유 속성을 가진 특정 행이나 이벤트가 관심 대상이면 정점으로 모델링함  
  - 조인 테이블은 관계를 나타내고, 팩트 테이블은 이벤트를 나타냄  
  
### 하나의 테이블을 정점과 간선으로 동시에 사용하기  
- “정점 또는 간선”이라는 구분은 기반 테이블이 아니라 그래프의 각 **요소 선언**에 적용됨  
- 하나의 테이블을 서로 다른 별칭으로 `VERTEX TABLES`와 `EDGE TABLES` 양쪽에 선언할 수 있음  
- `results`를 result 정점으로 사용하면서 같은 테이블을 다음 간선 별칭으로 재사용할 수 있음  
  - `results AS res_driver`: result에서 driver로 연결함  
  - `results AS res_race`: result에서 race로 연결함  
  - `results AS res_constr`: result에서 constructor로 연결함  
- 각 간선 별칭은 `results`에 이미 존재하는 기본 키와 외래 키 열을 사용함  
- 별도의 `results_driver`, `results_race`, `results_constructor` 테이블을 만들 필요가 없으며, **별칭은 데이터를 저장하지 않음**  
- 외래 키가 세 개인 팩트 테이블은 하나의 다중 종점 간선이 아니라, 각각 출발점과 도착점이 하나인 간선 별칭 세 개로 선언해야 함  
- 이 방식은 세 개의 물리 테이블이나 뷰 대신 하나의 기반 테이블에 대한 세 개의 선언적 별칭을 사용함  
  
### 프로퍼티 이름과 타입 충돌  
- `PROPERTIES`를 생략하면 SQL/PGQ는 테이블의 **모든 열을 프로퍼티로 노출**함  
- `results`와 `qualifying`에 모두 `number` 열이 있지만 각각 `double precision`과 `bigint`라면 다음 오류가 발생함  
  
```text  
ERROR: property "number" data type mismatch: double precision vs. bigint  
```  
  
- 그래프 전체에서 같은 이름의 프로퍼티는 하나의 타입을 가져야 하므로, 서로 다른 타입의 동명 열이 충돌함  
- 필요한 열만 `PROPERTIES`에 명시하면 충돌하는 열을 그래프에서 제외할 수 있음  
- 키가 자동으로 프로퍼티가 되지 않는 문제와 타입 충돌은 모두 **명시적인 프로퍼티 허용 목록**으로 해결 가능함  
  
### 적합한 질의와 부적합한 질의  
- ## 고정된 관계 패턴  
  - 프로퍼티 그래프는 “X와 이 특정 패턴으로 연결된 대상을 찾아라”라는 질문에 적합함  
  - Formula 1 그래프에서는 다음과 같은 질의를 작성할 수 있음  
    1. 특정 driver가 어느 constructor에서 경주했는지 탐색함  
    2. driver에서 result와 race를 거쳐 다른 result와 driver로 이어지는 패턴으로 경쟁자를 찾음  
    3. 출발 순위가 10위 밖이고 driver가 Italian인 결과처럼 구조와 속성 조건을 함께 필터링함  
  - 관계의 모양을 미리 알고 그 구조가 **고정되고 유한할 때** 검색·필터링·집계 문법의 가독성이 높아짐  
  - 여러 번의 자기 조인이 필요한 질의도 하나의 `MATCH` 패턴으로 읽기 쉽게 표현할 수 있음  
- ## 경로 자체가 미지수인 문제  
  - 다음 문제는 Postgres 19 프로퍼티 그래프에 맞지 않음  
    - 두 driver 사이의 최단 경로  
    - N홉 안에서 도달할 수 있는 모든 대상  
    - 깊이를 미리 알 수 없는 연결 탐색  
  - 이런 질의는 가변 길이 순회가 필요하므로 기반 테이블에 대한 재귀 CTE를 사용해야 함  
  - **PageRank**, 커뮤니티 탐지, 중심성 계산 같은 그래프 알고리듬도 패턴 매칭과 다른 문제이므로 이 기능의 범위에 속하지 않음  
  - 프로퍼티 그래프는 경로 구조를 알고 있을 때 연결 대상을 찾지만, X와 Y가 어떤 경로로 연결됐는지 모르는 상태에서 경로를 발견하거나 그래프의 구조적 중요도를 계산하지는 못함  
  
### 도입 전 판단할 점  
- SQL/PGQ는 기존 외래 키 구조 위에 놓이는 **선언적 오버레이**이며, 별도로 선택하지 않는 한 추가 데이터를 저장하지 않음  
- 주요 이점은 두 가지임  
  - 고정된 형태의 순회를 관계형 조인보다 읽기 쉽게 작성할 수 있음  
  - 스키마를 그래프로 문서화하는 이름 있는 객체를 제공함  
- 가장 큰 제약은 가변 길이 경로가 없어 깊거나 개방된 탐색을 재귀 SQL로 처리해야 한다는 점임  
- 테이블 자체가 본질적으로 정점이나 간선인 것은 아니며, 각 그래프에서 그 행을 정점·간선 또는 양쪽으로 읽을지 결정할 수 있음  
- Postgres 19에서 기존 스키마에 고정된 그래프 패턴을 질의하려는 경우, 별도 그래프 데이터베이스로 데이터를 옮기기 전에 `CREATE PROPERTY GRAPH`와 `MATCH`를 검토할 수 있음  
- 알 수 없는 깊이를 탐색하려면 재귀 CTE가 필요하지만, 어느 방식을 사용하든 데이터가 Postgres를 떠날 필요는 없음  
- 실제 도입 전에는 필요한 성능이 나오는지 직접 테스트해야 함

## Comments



_No public comments on this page._
