लगभग बिना कोड देखे बनाया गया 70 गुना तेज़ SQL parser
(posthog.com)- PostHog ने ANTLR-आधारित C++ SQL parser को कई Claude Code sessions की मदद से फिर से लिखा और 16K lines का Rust parser, 5K lines के tools, और हजारों lines के tests बनाए; laptop पर लगभग 70 गुना speedup मिला
- नया implementation predictive recursive descent parser और Pratt expression core पर केंद्रित है, और केवल ज़रूरी जगहों पर सीमित lookahead तथा speculative backtracking लागू करता है, जिससे ANTLR की general-purpose graph traversal cost हट गई
- मौजूदा C++ parser को reference implementation मानकर property-based testing, anonymized real queries, regression tests, code-coverage-guided generation, और ShrinkRay minimization को मिलाकर दोनों parsers के बीच के mismatches को बार-बार हटाया गया
- production shadow mode में लाखों parsing results एक बार भी पुराने parser से अलग नहीं निकले, इसलिए कुछ ही घंटों में traffic switch कर दिया गया; production average 454x speedup दर्ज हुआ
- parser generator grammar और reference implementation दे, और LLM fuzzing से equivalence verify करे — यह तरीका दिखाता है कि high-performance parser development, जो पहले specialist expertise के साथ कई महीने ले सकता था, अब कुछ दिनों में सिमट सकता है
PostHog को SQL parser की ज़रूरत क्यों है
- PostHog user input SQL को raw ClickHouse SQL में बदलता है ताकि users SQL के ज़रिए data को सीधे access कर सकें
- यह database की physical layout से स्वतंत्र logical data view देता है
- database layer बदलने पर भी existing queries टूटें नहीं, इसकी सुरक्षा करता है
- transformation process में performance optimization और access control जोड़ता है
- product analytics, session replay, error tracking जैसे PostHog के ज़्यादातर tools भी SQL में लिखी queries को इसी transformation process से गुज़ारते हैं
- SQL को transform करने के लिए पहले उसे abstract syntax tree(AST) में बदलना पड़ता है, और फिर इसी tree को दोबारा ClickHouse SQL में transform किया जाता है
- parser वह component है जो सबसे पहले untrusted query input को handle करता है
- बाद के access control और optimization भी parser द्वारा बनाए गए tree पर ही निर्भर करते हैं
मौजूदा ANTLR parser की संरचना और उसकी लागत
- AI coding से पहले parser को सीधे लिखना और maintain करना बहुत कठिन था, इसलिए PostHog ने open source parser generator ANTLR का उपयोग किया
- grammar को declarative .g4 file के रूप में देने पर ANTLR parser code का अधिकांश हिस्सा generate कर देता है
- पुराना parser भी पहले से C++ में generated था, इसलिए यह performance gain सिर्फ implementation language को Rust में बदलने का परिणाम नहीं है
- ANTLR शक्तिशाली और flexible है, लेकिन हर token को process करते समय उसे अधिक काम करना पड़ता है
- यह grammar को Augmented Transition Network(ATN), यानी stack के साथ Nondeterministic Finite Automaton(NFA) जैसी संरचना में compile करता है
- runtime में एक general-purpose interpreter इस graph को traverse करता है
- सीधे लिखे गए
parseExpression()जैसे function की जगह अतिरिक्त abstraction और indirect calls की layers आती हैं
- यह arbitrary dynamic lookahead भी support करता है, इसलिए कई alternatives होने पर जब तक केवल एक valid option न बचे, तब तक सभी interpretations को साथ में simulate करता है
- चाहे ANTLR कितना भी optimized हो, graph-traversal interpreter architecture के कारण उसका hand-written recursive descent parser से तेज़ होना मुश्किल है
दो parser implementations पर parallel experiment
- AI की मदद से hand-written parser बनाना और maintain करना आसान हो सकता है, लेकिन Claude से सिर्फ error-free Rust parser माँगना पर्याप्त नहीं था
- Claude ने बहुत सी गलतियाँ कीं
- उसने बार-बार इस rewrite की feasibility पर संदेह किया
- हर coding round के अंत में वह काम समाप्त मान लेने की प्रवृत्ति दिखाता था
- कई long-running Claude Code sessions में दो approaches को parallel में आज़माया गया
- performance-first approach ने recursive descent parser और Pratt expression loop का उपयोग किया, और केवल ज़रूरत वाली जगहों पर lookahead और backtracking जोड़ा
- success-probability-first approach ने ANTLR के behavior का अधिकतम पालन किया, लेकिन general-purpose graph traversal की जगह transitions को explicit code में implement किया
- दोनों approaches अंततः लगभग समान स्तर पर काम करने लगे, लेकिन यह पता करने में कई दिन लगे
- लक्ष्य था कि वास्तविक दुनिया की सभी queries पर यह पुराने C++ parser से पूरी तरह मेल खाए, और artificial queries पर भी जितना संभव हो उतना नज़दीकी result दे
SELECT SELECT FROM FROM WHERE WHERE AND ANDजैसी अजीब दिखने वाली लेकिन valid SQL भी test target में शामिल थी
- पुराने C++ parser को reference implementation(or oracle) मानकर, अलग तरीके से handle होने वाली SQL को ढूँढना, नए parser को ठीक करना, और फिर दोबारा compare करना — इसी test-driven तरीके से development हुआ
property-based testing से mismatches बनाना
- पुराने parser development के दौरान जमा हुए regression tests को पहले pass कराया गया, फिर नए mismatches को व्यवस्थित रूप से generate किया गया
- Hypothesis आधारित property-based testing(PBT) में verification property यह रखी गई कि “नया parser reference implementation से मेल खाए”
- input value SQL query थी
- Hypothesis उन queries को खोजता था जहाँ दोनों parsers के results मेल नहीं खाते
- रोचक SQL generate करने के लिए Claude के साथ ANTLR grammar file से SQL generator code generate करने वाला tool बनाया गया
.g4file को पढ़ने वाला अलग parser भी लिखा गया- बाद में token swapping या parentheses जोड़ने जैसे mutations को भी generation stage में शामिल किया गया
fragile fixes रोकने के लिए prompt setup
- PBT ने नए tests लगातार बनाए, लेकिन Claude बार-बार fragile fixes करता रहा — जैसे one-token lookahead से किसी खास case को ठीक करना, और बाद में पता चलना कि two-token lookahead की ज़रूरत है
- context window अक्सर भरकर compress हो जाती थी, इसलिए संभव था कि fixes करते समय वह actual grammar या reference parser के behavior को भूल गया हो
- इसे कम करने के लिए, हर mismatch fix करने वाला code लिखने से ठीक पहले उससे संबंधित grammar files और C++ source code दोनों को context में फिर से लोड करने को कहा गया
CPU और Claude को साथ-साथ लगातार चलाना
- PBT background में लगातार चलता रहा, failures को file में लिखता रहा, और Claude के पास जब दूसरा काम न हो तो वह उन cases को उठा सके — ऐसा tooling बनाया गया
- failure cases कई रास्तों से इकट्ठे किए गए
- existing regression tests
- PBT से generated SQL
- production query logs से ली गई anonymized queries
- background agents से “edge cases पर बहुत गहराई से सोचो” कहकर बनवाए गए cases
- parallel में विकसित किए गए दोनों parsers ने shared regression test suite का उपयोग किया, इसलिए एक session में मिला failure तुरंत दूसरे session में भी दिखने लगा
- Hypothesis अपने generated cases को minimal reproductions तक shrink कर सकता था, लेकिन external SQL पर यह लागू नहीं होता था, इसलिए ऐसे cases पर ShrinkRay इस्तेमाल किया गया
- बाद में code-coverage-guided test generation भी जोड़ा गया
- जो SQL constructs अभी तक execute नहीं हुए थे, उन्हें detect कर generation को उनकी तरफ bias किया गया
- production query set पर 100% accuracy पाने के लिए यह अनिवार्य नहीं था, लेकिन बहुत सूक्ष्म cases पकड़ने में मददगार रहा
failures ढूँढने और ठीक करने का automated loop
- अंतिम development loop autonomously इन चरणों को दोहराता रहा
- PBT, real query sets, regression tests, और edge-case exploration से नए failures generate करना
- failures को shrink करके लगातार बढ़ती regression test list में जोड़ना
- सामान्य समाधान को प्राथमिकता देते हुए grammar और C++ reference implementation के handling को जाँचना
- fix के बाद इंसान द्वारा पढ़े जा सकने वाला एक paragraph summary output करना
- पूरी regression test suite चलाकर सभी cases के pass होने की पुष्टि करना
- फिर वही process दोबारा autonomously चलाना
- क्योंकि नया parser बहुत तेज़ था, उसे production में पुराने C++ parser के साथ shadow mode में चलाकर result differences report किए जा सके
production validation और switch
- production query logs के साथ pre-comparison में लगभग 50,000 queries test की गईं
- shadow mode में लाखों parses तेज़ी से चलाए गए और पुराने parser से एक भी mismatch नहीं मिला
- मूल योजना कुछ दिनों तक shadow mode बनाए रखने की थी, लेकिन result इतने मजबूत थे कि कुछ घंटों में ही production traffic नए parser पर switch कर दिया गया
- switch के बाद भी पुराने parser से दोबारा compare करने के लिए 0.1% reverse shadow बनाए रखा गया
- नया parser सिर्फ AST ही नहीं, बल्कि source location information भी C++ ANTLR parser के समान output करता है
laptop पर 70x, production में 454x
- laptop benchmark में इसने पुराने parser की तुलना में लगभग 70x faster performance दर्ज की
- production queries में औसतन 454x speedup मिला
- production में अधिकतर लंबी SQL process होती थी जो parser cache hit नहीं करती थी, इसलिए laptop की तुलना में performance gap और बड़ा था
- अंतिम output में लगभग 16K lines parser code, लगभग 5K lines tools, और हजारों lines tests शामिल हैं
- वास्तविक queries पर यह पुराने parser के बराबर है, और अंतर केवल बहुत कम, कृत्रिम रूप से बनाए गए queries तक सीमित है
अंतिम parser architecture और बदला हुआ development तरीका
- अंतिम parser की संरचना इस प्रकार है
- अधिकांश हिस्सा predictive recursive descent parser के रूप में implement किया गया
- expressions को handle करने के लिए Pratt parser core का उपयोग हुआ
- default cursor LL(2) है, और केवल कुछ खास points पर input consume किए बिना सीमित lookahead exploration से इसकी range बढ़ाई जाती है
- केवल बेहद ज़रूरी कुछ decisions में ordered-choice आधारित local speculative backtracking लागू किया जाता है
- पूरे parser को मई 2026 में Claude Opus 4.7 ने Rust में लिखा, और code हाथ से सीधे नहीं लिखा गया
- grammar से input generate करना और code coverage से generation guide करना — यह PBT setup parser fuzzing के आधुनिक तरीकों के काफ़ी करीब है, और साधारण “vibe coding” से अलग है
- ऐसा काम, जिसमें खास parser knowledge वाले developer को भी कई महीने लग सकते थे, कुछ ही दिनों में पूरा हुआ
- आगे चलकर ANTLR जैसे parser generators reference implementation दे सकते हैं, और LLM PBT तथा fuzzing के ज़रिए equivalence मिलाते हुए उससे तेज़ hand-written parser लिख सकते हैं
अभी कोई टिप्पणी नहीं है.