SYSTEM Cited by 1 source
Vitess evalengine¶
What it is¶
evalengine is the SQL expression evaluation engine inside
Vitess, implemented in pure Go and living
inside vtgate. Its job is to evaluate scalar SQL
sub-expressions locally whenever the Vitess query planner
cannot push the sub-expression down to the underlying
MySQL shards — most commonly when the
expression operates on results of cross-shard aggregation (e.g.
HAVING avg_price > 100 where AVG is aggregated across shards
inside Vitess). See the
Vitess repo source.
evalengine is bug-compatible with MySQL's C++ expression
engine on the hundreds of SQL quirks MySQL has accumulated
(collations, implicit casts, overflow promotion rules, string
edge cases). Vitess's fuzzer — which compares evalengine's
output against MySQL's output on billions of random expressions
— has routinely found bugs in MySQL itself that Vitess upstreams
(collation bug PR 602,
insert SQL function PR 517,
substring PR 515)
(Source:
sources/2025-04-05-planetscale-faster-interpreters-in-go-catching-up-with-cpp).
Architecture¶
Two-interpreter design¶
evalengine ships two coexisting interpreters for the same SQL expression language:
-
AST interpreter — the original Vitess evaluation engine. Recursively walks the SQL expression's concepts/abstract-syntax-tree; type-switches per operand at runtime. Accurate, simple, slow.
-
Bytecode-less virtual machine — the new interpreter (shipped over 2024–2025). Statically types expressions at compile time using semantic analysis + the MySQL information schema, then emits a
[]func(*VirtualMachine) intslice of closures — Martí's callback-slice interpreter design. Seego/vt/vtgate/evalengine/vm.go.
Both remain load-bearing¶
The AST interpreter is retained for three orthogonal reasons:
- Deoptimization fallback. When a specialized VM instruction
hits a value-dependent type promotion (canonical case:
-BIGINT_MIN = -9223372036854775808promotes toDECIMALinstead of overflowing), the instruction setsvm.err = errDeoptimizeand evaluation falls back to the AST interpreter. See concepts/vm-deoptimization and patterns/vm-ast-dual-interpreter-fallback. - One-shot evaluation. For single-pass uses like constant folding inside the Vitess planner, compile-then-execute on the VM costs more than a direct walk of the AST.
- Fuzz-oracle sibling. Vitess fuzzes both interpreters against each other — every discrepancy is a bug in one side, and many are bugs in MySQL itself. See patterns/fuzz-ast-vs-vm-oracle.
Why the design matters (on the wiki)¶
First canonical non-JIT high-performance Go interpreter on the wiki¶
The wiki's prior "fast interpreter in Go" category was empty. evalengine canonicalises a design that exploits Go's strengths instead of fighting its limits:
- Big-switch VM loops (the C/C++ mainstream) are hostile to Go's compiler — switches often dispatch via binary search instead of jump tables, and large VM functions spill registers on every branch. See concepts/jump-table-vs-binary-search-dispatch and concepts/go-compiler-optimization-gap.
- Tail-call continuation loops (the C/C++/Python 3.14
mainstream with LLVM
musttail) don't work in Go — Go's compiler can emit tail calls but not reliably. - Closures work beautifully in Go. The callback-slice design captures instruction arguments in closure state — no bytecode encoding, no VM ↔ compiler sync to maintain. "Developing the compiler means developing the VM simultaneously."
Static type specialization via the planner, not runtime rewriting¶
Most dynamic-language VMs (V8, LuaJIT, HotSpot) observe runtime types to specialize opcodes. Brunthaler's Efficient Interpretation using Quickening generalises this to bytecode rewriting. Vitess takes a different path: the semantic analyzer + MySQL information schema gives compile-time static types for every sub-expression, so specialized opcodes are emitted directly. See concepts/static-type-specialization and patterns/static-type-specialized-bytecode.
Consequence: the VM contains no type-switching code. An integer add is an integer add; a text push knows its collation at compile time. Zero memory allocation on 4/5 benchmarks is a direct consequence of eliminating runtime boxing.
Parity with MySQL's C++ implementation¶
Benchmark result: on 5 queries ranging complex-to-simple,
VM geomean is −48.60% vs the original AST interpreter;
MySQL C++ geomean is −43.58% vs the same baseline. The VM
is faster than MySQL on comparison_u64, comparison_dec,
and comparison_f, and statistically tied on complex_arith
(50.77n vs 49.40n). See the
source page
for full benchmark breakdown.
Seen in¶
- sources/2025-04-05-planetscale-faster-interpreters-in-go-catching-up-with-cpp — canonical disclosure. Martí walks through AST → bytecode-VM → callback-slice design, then covers static type specialization, deoptimization-to-AST, the fuzz-oracle architecture, and the explicit rejection of JIT compilation on the measured dispatch-overhead-share threshold (< 20%).
Related¶
- systems/vitess
- systems/mysql
- systems/planetscale
- concepts/bytecode-virtual-machine
- concepts/ast-interpreter
- concepts/static-type-specialization
- concepts/callback-slice-interpreter
- concepts/vm-deoptimization
- concepts/jit-compilation
- concepts/quickening-runtime-bytecode-rewrite
- concepts/instruction-dispatch-cost
- concepts/tail-call-continuation-interpreter
- concepts/go-compiler-optimization-gap
- concepts/jump-table-vs-binary-search-dispatch
- patterns/callback-slice-vm-go
- patterns/static-type-specialized-bytecode
- patterns/vm-ast-dual-interpreter-fallback
- patterns/fuzz-ast-vs-vm-oracle