- PostgreSQL 확장 프록시인 PgDog가 SQL 파싱 성능을 높이기 위해 Protobuf 직렬화 대신 Rust 직접 바인딩을 도입
- 기존 Protobuf 기반 구조를 C–Rust 직접 변환(bindgen + Claude 생성 래퍼) 으로 교체해 파싱 5.45배, 디파싱 9.64배 속도 향상
- 성능 병목은 pg_query_parse_protobuf 함수에서 발견되었으며, 캐싱 시도 후에도 근본적 개선을 위해 구조 변경 수행
- Claude LLM을 활용해 6,000줄의 Rust–C 변환 코드를 자동 생성하고, parse, deparse, fingerprint, scan 등 주요 함수에 적용
- 이 최적화로 PgDog의 CPU 사용량과 지연시간이 감소, PostgreSQL 수평 확장 프록시로서의 효율성이 크게 향상됨
PgDog과 Protobuf의 한계
-
PgDog은 PostgreSQL을 확장하기 위한 프록시로, 내부적으로 libpg_query를 사용해 SQL 쿼리를 파싱함
- Rust로 작성되어 있으며, 기존에는 Protobuf 직렬화/역직렬화를 통해 C 라이브러리와 통신
- Protobuf는 빠르지만, 직접 바인딩 방식이 더 빠름
- PgDog 팀은 pg_query.rs를 포크해 Protobuf를 제거하고 C–Rust 직접 바인딩을 구현
- 결과적으로 쿼리 파싱은 5.45배, 디파싱은 9.64배 빨라짐
벤치마크 결과
- 벤치마크는 PgDog의 포크 저장소에서 재현 가능
-
pg_query::parse (Protobuf): 613 QPS
-
pg_query::parse_raw (직접 C–Rust): 3357 QPS
-
pg_query::deparse (Protobuf): 759 QPS
-
pg_query::deparse_raw (직접 Rust–C): 7319 QPS
성능 병목 분석과 캐싱 시도
-
samply 프로파일러를 사용해 CPU 사용 시간을 분석한 결과, pg_query_parse_protobuf 함수가 병목으로 확인됨
- 캐싱을 통해 일부 개선을 시도
-
LRU 알고리듬 기반 해시맵 캐시를 사용, 쿼리 텍스트를 키로 AST를 저장
- 준비된 문장을 사용하는 경우 재사용 가능
- 그러나 일부 ORM이 수천 개의 고유 쿼리를 생성하거나, 오래된 PostgreSQL 드라이버가 준비된 문장을 지원하지 않아 캐시 효율이 낮았음
LLM을 활용한 Protobuf 제거
- PgDog 팀은 Claude LLM을 활용해 Protobuf를 제거한 Rust 바인딩을 생성
- 명확하고 검증 가능한 작업 범위 내에서 AI가 효과적으로 작동
- Claude는 libpg_query의 Protobuf 스펙을 기반으로 C 구조체를 Rust 구조체로 매핑
- 2일간의 반복 작업 끝에 6,000줄의 재귀적 Rust 코드 완성
-
parse, deparse, fingerprint, scan 함수에 적용해 pgbench 기준 25% 성능 향상 확인
구현 세부 구조
- Rust와 C 간 변환은 unsafe 함수를 사용해 구조체를 직접 매핑
- C 구조체를 Postgres API에 전달해 AST를 생성하고, Rust로 재귀 변환
- 각 AST 노드는 convert_node 함수로 처리되며, SQL 문법의 수백 개 토큰을 매핑
- SELECT, INSERT 등 각 노드 타입별로 별도 변환 함수 존재
- 변환 결과는 기존 Protobuf 구조체(protobuf::ParseResult)를 재활용해 테스트 시 byte 단위 비교 검증 가능
- 재귀 알고리듬은 메모리 할당이 적고 CPU 캐시 효율이 높아 반복문 기반 구현보다 빠름
- 반복문 기반 구현은 불필요한 메모리 할당과 해시맵 조회로 인해 오히려 느림
결론
- Postgres 파서의 오버헤드를 줄여 PgDog의 지연시간, 메모리, CPU 사용량을 모두 절감
- 이 최적화로 PgDog은 더 빠르고 저비용으로 운영 가능한 PostgreSQL 확장 프록시로 발전
- PgDog은 PostgreSQL의 수평 확장(next iteration) 을 함께 구축할 엔지니어를 모집 중임