들어가며
PostgreSQL 19는 SQL/PGQ(SQL Property Graph Queries) 를 코어에 들였다. ISO/IEC 9075-16:2023, 즉 SQL:2023 Part 16으로 표준화된 기능이다.
그래프 워크로드를 다루려면 Neo4j나 ArangoDB 같은 전용 DB를 띄우거나, PostgreSQL 위에 Apache AGE 확장을 올리는 길이 대표적이었다. 데이터는 멀쩡히 관계형 테이블에 들어 있는데 권한·조직도·추천 같은 일부 쿼리만 그래프 모양으로 풀고 싶을 때는, 그 절반쯤만 필요한 인프라를 들이는 셈이라 늘 어색했다.
SQL/PGQ는 그 회색 지대를 노린다. 기존 테이블 위에 그래프 뷰를 얹고, 표준 SQL 안에서 그래프 패턴 매칭을 그대로 쓴다.
이 글은 PostgreSQL 19 베타 직전 기준으로 작성되었으며, 정식 출시 전까지 일부 문법이 조정될 수 있다. 앞선 PostgreSQL 19 새 기능 총정리의 후속편으로 읽으면 좋다.
SQL/PGQ가 푸는 문제
“아이유를 팔로우하는 사용자가 좋아요를 누른 게시물의 작성자"를 표준 SQL로 풀려면 어떻게 써야 할까. users, follows, liked, posts, authored 다섯 테이블을 차례로 조인해야 한다.
SELECT DISTINCT author.handle, p.content
FROM users iu
JOIN follows f ON f.followee_id = iu.id
JOIN users follower ON follower.id = f.follower_id
JOIN liked l ON l.user_id = follower.id
JOIN posts p ON p.id = l.post_id
JOIN authored a ON a.post_id = p.id
JOIN users author ON author.id = a.user_id
WHERE iu.handle = '아이유';
동작은 한다. 그러나 한눈에 들어오지 않는다. 어느 컬럼이 출발이고 어느 컬럼이 도착인지, 팔로우의 방향이 어디로 향하는지 매번 머릿속에서 다시 짚어야 한다. 한 홉만 더 늘려도 조인이 두 줄씩 붙는다.
SQL/PGQ는 이 코드를 시각적 패턴으로 옮긴다. 정점은 괄호 (), 간선은 꺾쇠 -[]->. 의도가 그대로 보인다.
다만 PostgreSQL 19의 첫 구현은 고정 깊이 패턴까지다. 정량 패턴(+, *, {2,5})과 가변 길이 경로는 다음 릴리스로 미뤄졌다. 이 글에서 다루는 범위는 그 첫 구현이다.
CREATE PROPERTY GRAPH — 그래프 뷰 정의
먼저 그래프를 선언한다. 데이터는 옮기지 않는다. 기존 정점 테이블과 간선 테이블을 그래프로 어떻게 볼지를 카탈로그에 기록할 뿐이다.
아래는 소셜 서비스 도메인을 그래프로 노출하는 예시다. 사용자와 게시물을 정점으로, 팔로우·작성·좋아요를 간선으로 잡는다.
CREATE PROPERTY GRAPH social
VERTEX TABLES (
users LABEL person PROPERTIES (id, handle, joined_at),
posts LABEL post PROPERTIES (id, content, posted_at)
)
EDGE TABLES (
follows
SOURCE KEY (follower_id) REFERENCES users (id)
DESTINATION KEY (followee_id) REFERENCES users (id)
LABEL follows PROPERTIES (followed_at),
authored
SOURCE KEY (user_id) REFERENCES users (id)
DESTINATION KEY (post_id) REFERENCES posts (id)
LABEL authored,
liked
SOURCE KEY (user_id) REFERENCES users (id)
DESTINATION KEY (post_id) REFERENCES posts (id)
LABEL liked PROPERTIES (liked_at)
);
세 가지만 짚으면 충분하다.
- VERTEX TABLES — 정점 데이터를 들고 있는 실제 테이블.
LABEL로 그래프상 노드 이름을 지정한다. 정점 테이블은 여러 개 둘 수 있다 — 여기서는users와posts두 종류다. - EDGE TABLES — 간선 데이터를 들고 있는 실제 테이블.
SOURCE KEY와DESTINATION KEY가 화살표의 출발과 도착을 결정한다. 같은 정점 간 간선(follows)과 서로 다른 정점 간 간선(authored,liked)이 한 그래프 안에 공존한다. - PROPERTIES — 그래프 패턴에서 노출할 컬럼을 골라 노출한다. 모든 컬럼을 자동으로 끌어오지 않는다.
CREATE PROPERTY GRAPH는 새 테이블을 만들지 않는다. users, posts, follows, authored, liked는 그대로 남고, 그 위에 social이라는 그래프 객체 하나가 더 생긴다. DROP PROPERTY GRAPH social로 언제든 떼어낼 수 있다.
GRAPH_TABLE & MATCH — 패턴 매칭 쿼리
쿼리 쪽 핵심은 GRAPH_TABLE이다. 그래프 패턴을 받아 관계형 행 집합으로 돌려준다. 즉 결과를 다시 일반 SQL의 FROM 절에서 받아 쓸 수 있다.
1홉: 아이유를 팔로우하는 사용자
SELECT handle
FROM GRAPH_TABLE (social
MATCH (iu IS person WHERE iu.handle = '아이유')
<-[f IS follows]-
(follower IS person)
COLUMNS (follower.handle AS handle)
);
화살표가 왼쪽으로 향한다. “아이유를 팔로우하는 사람들"을 찾는다는 뜻이다. 같은 의도를 오른쪽 방향으로 쓰려면 패턴의 시작점을 팔로워로 바꾸면 된다. 의미는 같지만 가독성은 사람에 따라 갈린다.
COLUMNS (...) 절은 패턴 안에서 잡은 값을 일반 컬럼으로 투영한다. 이름이 곧 결과 컬럼명이 된다.
3홉: 팔로워가 좋아요 누른 게시물의 작성자
SELECT DISTINCT author.handle, p.content
FROM GRAPH_TABLE (social
MATCH (iu IS person WHERE iu.handle = '아이유')
<-[IS follows]-
(follower IS person)
-[IS liked]->
(p IS post)
<-[IS authored]-
(author IS person)
COLUMNS (author.handle, p.content)
);
앞서 일곱 번 조인으로 풀던 쿼리가 패턴 한 덩어리로 줄었다. 화살표 방향이 의도와 그대로 일치한다.
간선 변수 이름은 본문에서 쓰지 않으면 생략해 -[IS liked]->처럼 적을 수 있다. 간선 속성을 다시 꺼내야 할 때만 -[l IS liked]->처럼 이름을 붙이면 된다.
양방향 매칭은 <-[e]->로 쓴다. 다만 PostgreSQL 19에서는 양방향 자유 매칭이 일부 제한되며, 단방향 두 번으로 풀어쓰는 편이 안전하다.
내부 동작 — rewriter가 하는 일
SQL/PGQ는 새 실행 엔진을 들고 오지 않는다. 파서가 GRAPH_TABLE을 만나면 rewriter가 그래프 패턴을 표준 관계형 트리로 풀어 쓴다. 정점은 RangeTblEntry가 되고, 간선은 출발 테이블·간선 테이블·도착 테이블 사이의 3-way 조인이 된다. 그 결과 트리는 일반 플래너로 그대로 흘러간다.
flowchart TD
A[GRAPH_TABLE / MATCH] --> B[Parse Tree]
B --> C[PGQ Rewriter]
C --> D[관계형 조인 트리]
D --> E[일반 플래너]
E --> F[실행기]
style C fill:#fde68a,stroke:#b45309
style D fill:#bbf7d0,stroke:#15803d
이 설계가 가져오는 결과는 셋이다.
- 기존 인덱스를 그대로 쓴다. 간선 테이블의
(follower_id, followee_id),(user_id, post_id)같은 복합 인덱스가 그래프 쿼리에서도 그대로 활용된다. - MVCC와 트랜잭션이 자동으로 따라온다. 별도 그래프 엔진이라면 따로 신경 써야 했을 부분이 사라진다.
EXPLAIN이 그대로 의미가 있다. 결국 보이는 건 일반 조인 플랜이고, 튜닝 도구가 다 통한다.
대신 그래프 쿼리의 성능은 결국 조인 최적화에 종속된다. 간선이 많고 선택도가 낮은 패턴은 대량 조인이 될 수 있다. 인덱스와 통계 정보가 평소처럼 중요하다.
PostgreSQL 19에 들어간 것 vs 빠진 것
| 항목 | PostgreSQL 19 |
|---|---|
| 고정 깊이 패턴 매칭 | 지원 |
레이블 필터 (v:Person) | 지원 |
속성 투영 COLUMNS (...) | 지원 |
| 단방향/다방향 다홉 조합 | 지원 |
가변 길이 경로 -[e*1..3]-> | 미지원 |
정량 패턴 +, *, {2,5} | 미지원 |
| 양방향 자유 매칭 일부 | 미지원 |
| BFS/DFS 탐색 순서 지정 | 미지원 |
CREATE PROPERTY GRAPH DDL | 지원 |
핵심 한 줄로 줄이면 다음과 같다. 모양이 정해진 그래프 질의는 PostgreSQL 19에서 곧장 풀 수 있고, 모양이 가변인 그래프 질의는 여전히 재귀 CTE의 영역에 남는다.
가변 길이 지원은 다음 메이저 릴리스의 1차 후보로 거론된다. rewriter가 재귀 CTE 또는 자기조인 반복으로 펴는 형태가 유력하다.
다른 옵션과 비교
선택지가 늘었으니 정리해 둘 만하다.
| 항목 | SQL/PGQ (PG19) | Apache AGE | Neo4j |
|---|---|---|---|
| 설치 방식 | PostgreSQL 코어 | PostgreSQL 확장 | 별도 DB |
| 쿼리 언어 | SQL + GRAPH_TABLE | openCypher | Cypher |
| 데이터 저장 | 기존 관계형 테이블 | AGE 전용 그래프 저장소 | 네이티브 그래프 |
| 트랜잭션 | PostgreSQL MVCC | PostgreSQL MVCC | 자체 |
| 가변 길이 경로 | 미지원 (PG19 한정) | 지원 | 지원 |
| 그래프 알고리즘 | — | 일부 | 풍부 |
| 성숙도 | 신규 (코어 첫 진입) | 중간 | 높음 |
이미 PostgreSQL에 든 데이터를 가볍게 그래프로 보고 싶다면 SQL/PGQ가 첫 후보다. 그래프 자체가 1급 워크로드라면 여전히 Apache AGE나 Neo4j가 더 멀리 간다.
언제 쓰면 좋은가
쓰기 좋은 시나리오부터 짚는다.
- 권한·조직도 탐색 — “이 사용자의 상위 N단계 관리자” 같은 고정 깊이 질의
- 추천 시나리오 lite — “내가 산 상품을 산 사람이 산 다른 상품”
- 소셜 그래프 lite — 친구의 친구 정도의 얕은 깊이
- 분석 워크로드 일부 — 기존 BI 쿼리에 그래프 패턴을 끼워 넣을 때
반대로 적합하지 않은 경우는 분명하다.
- 깊이가 가변인 traversal — 트리 루트까지 N단계, 도달 가능한 모든 노드 등
- 그래프 알고리즘 본격 사용 — PageRank, 중심성, 커뮤니티 탐지
- 수억 노드급 그래프 — 네이티브 그래프 저장소의 인덱스/캐시 구조가 더 유리
선택 기준은 단순하다. 그래프 모양 질의가 늘면 SQL/PGQ로 옮기고, 그래프 워크로드가 본진이 되는 순간 전용 엔진으로 간다.
마무리
PostgreSQL 19의 SQL/PGQ는 화려한 신기능이라기보다 관계형 DB가 그래프 워크로드를 흡수하는 첫 발판에 가깝다. 첫 릴리스는 의도적으로 좁다. 고정 깊이부터 안정적으로 굳히고, 가변 길이는 다음으로 미루는 그림이다.
개인적으로 주목하는 지점:
- 표준이 코어로 들어왔다는 것 — Apache AGE와 SQL/PGQ가 한 데이터베이스 안에서 공존할 수 있게 됐다.
- rewriter 기반 설계 — 새 엔진을 들이지 않고 기존 플래너에 얹은 선택. 운영 부담이 최소다.
- 첫 릴리스의 절제 — 가변 길이를 일단 빼고 출시한 점. 신기능 폭주 대신 표준 추종을 우선한 결정으로 읽힌다.
9월 정식 출시 전 베타 단계에서 한 번쯤 만져볼 만한 기능이다. 같은 시리즈의 PostgreSQL 19 새 기능 총정리, EXPLAIN ANALYZE RDTSC, 파티션 MERGE/SPLIT도 함께 참고할 수 있다.