사람이 코드를 안 짰는데 70배 빨라졌다? PostHog의 AI 기반 SQL 파서 재작성기

핵심 요약
PostHog가 ANTLR 기반 C++ SQL 파서를 여러 개의 Claude Code 세션을 활용해 Rust로 완전히 재작성했습니다. 결과물은 파서 코드 약 16K줄, 도구 5K줄, 수천 줄의 테스트였고, 성능은 노트북 벤치마크 기준 약 70배, 프로덕션 평균 454배 빨라졌어요. 더 놀라운 건 개발자가 코드를 거의 직접 보지 않았다는 점입니다. 대신 기존 파서를 '기준 구현(오라클)'으로 삼고, 속성 기반 테스트와 퍼징으로 두 파서의 동등성을 기계적으로 검증하는 방식을 택했죠.
왜 SQL 파서가 필요했을까?
PostHog는 사용자가 입력한 SQL을 원시 ClickHouse SQL로 변환해 실행합니다. 이 과정에서 다음과 같은 일을 하죠.
- 데이터베이스의 물리적 배치와 독립된 논리적 데이터 뷰 제공
- DB 계층이 바뀌어도 기존 쿼리가 깨지지 않도록 보호
- 변환 과정에 성능 최적화와 접근 제어 추가
SQL을 변환하려면 먼저 추상 구문 트리(AST)로 바꿔야 하는데, 이 역할을 하는 파서는 신뢰할 수 없는 입력을 가장 먼저 처리하는 구성 요소입니다. 이후의 접근 제어와 최적화가 전부 파서의 출력에 의존하기 때문에, 절대 틀리면 안 되는 부품이에요.
ANTLR은 왜 느렸나
기존 파서는 파서 생성기 ANTLR이 만든 C++ 코드였습니다. 즉, 이번 성능 향상은 단순히 'C++을 Rust로 바꿔서' 얻은 게 아닙니다.
ANTLR은 문법을 증강 전이망(ATN)이라는 그래프 형태로 컴파일하고, 런타임에 범용 인터프리터가 이 그래프를 순회합니다. 직접 작성한 parseExpression() 같은 함수 대신 추상화와 간접 호출 계층을 거치고, 대안이 여러 개면 하나만 남을 때까지 모든 해석을 동시에 시뮬레이션하죠. 유연하지만, 구조적으로 손으로 짠 재귀 하강 파서를 이기기 어렵습니다.
AI에게 그냥 "파서 짜줘"는 통하지 않았다
Claude에게 오류 없는 Rust 파서를 요청하는 것만으로는 부족했습니다. Claude는 실수를 많이 했고, 재작성 가능성 자체를 반복해서 의심했으며, 매 라운드마다 작업을 서둘러 마치려는 경향을 보였다고 해요. 그래서 PostHog는 접근 방식을 바꿨습니다.
1. 두 가지 구현을 병렬로 실험
- 성능 중심: 재귀 하강 + Pratt 표현식 루프, 필요한 곳에만 미리보기와 백트래킹
- 성공 가능성 중심: ANTLR의 동작을 최대한 따르되, 그래프 순회를 명시적 코드로 구현
2. 기존 파서를 '오라클'로 삼은 테스트 주도 개발
기존 C++ 파서를 기준 구현으로 두고, 두 파서가 다르게 처리하는 SQL을 찾아 수정하고 다시 비교하는 루프를 돌렸습니다. SELECT SELECT FROM FROM WHERE WHERE AND AND처럼 이상해 보이지만 유효한 SQL까지 테스트 대상에 포함했어요.
3. 속성 기반 테스트(PBT)와 퍼징으로 불일치 사냥
- Hypothesis로 "새 파서가 기준 구현과 일치한다"는 속성을 검증
- ANTLR 문법 파일(.g4)에서 SQL 생성기를 코드 생성하는 도구까지 제작
- 실패 사례는 회귀 테스트, PBT 생성 SQL, 익명화된 프로덕션 쿼리, 경계 사례 탐색 등 여러 경로에서 수집
- 외부 SQL 축소에는 ShrinkRay, 이후에는 코드 커버리지 유도 생성까지 추가
4. 취약한 수정을 막는 프롬프트 설계
Claude가 컨텍스트 압축으로 문법을 잊은 채 임시방편 수정을 반복하는 문제가 있었는데, 불일치를 고치기 직전에 관련 문법 파일과 C++ 소스를 컨텍스트로 다시 불러오게 지시해 완화했습니다. PBT는 백그라운드에서 계속 돌며 실패 사례를 파일에 쌓고, Claude는 한가할 때 그 사례를 집어가는 구조로 CPU와 AI를 모두 쉬지 않게 만들었죠.
프로덕션 검증: 수백만 번 파싱, 불일치 0건
사전 비교에서 약 5만 개의 프로덕션 쿼리를 테스트한 뒤, 섀도 모드로 기존 파서와 나란히 실행했습니다. 수백만 번의 파싱에서 불일치가 단 한 건도 없었고, 원래 며칠 유지하려던 섀도 모드를 몇 시간 만에 끝내고 트래픽을 전환했습니다. 전환 후에도 0.1% 역방향 섀도를 유지하고 있고, 새 파서는 AST뿐 아니라 소스 위치 정보까지 기존 파서와 동일하게 출력합니다.
프로덕션에서 454배라는 더 큰 차이가 난 이유는, 실서비스에서는 파서 캐시에 적중하지 않는 더 긴 SQL을 주로 처리하기 때문이에요.
이 사례가 말해주는 것
전체 파서는 2026년 5월 Claude Opus 4.7이 Rust로 작성했고, 사람은 코드를 직접 손으로 쓰지 않았습니다. 하지만 이건 단순한 '바이브 코딩'이 아닙니다. 문법으로 입력을 생성하고 커버리지로 생성을 유도하는 구성은 파서 퍼징의 최신 기법에 가까운, 잘 설계된 검증 파이프라인이었죠.
앞으로는 이런 패턴이 일반화될지도 모릅니다.
- 파서 생성기(ANTLR 등)가 문법과 기준 구현을 제공하고
- LLM이 PBT와 퍼징으로 동등성을 검증하면서
- 더 빠른 직접 구현 파서를 작성하는 방식
파서 전문 지식이 있는 개발자에게도 수개월 걸릴 작업이 며칠로 줄어든 셈입니다. AI 시대의 고성능 시스템 소프트웨어 개발이 어떤 모습일지 보여주는 흥미로운 사례네요.