# 1. Background - [Compilation in the Microsoft SQL Server Hekaton Engine](http://sites.computer.org/debull/A14mar/p22.pdf) - 减少指令数的一种方式就是 **code specialization** - 生成一个 task / query 特定的代码 ## Code Specialization - DBMS 为执行模式相似、inputs 不同的 CPU-intenseive tasks 生成代码 - Access Methods - Stored Procedures - Query Operator Execution - Predicate Evaluation(最常见) - Logging Operators ![](https://img.jonahgao.com/oss/note/2025p1/16721_2023_compilation_code.png) - 方式一:**Transpilation** - Write code that converts a relational query plan into imperative language **source code** and then run it through a conventional compiler to generate native code. - 方式二:**JIT Compilation** - 生成 intermediate representation(IR) - DBMS 编译成 native code ## Architecuture Overview ![](https://img.jonahgao.com/oss/note/2025p1/16721_2023_compilation_arch.png) -------- # 2. Code Generation / Transpilation ## HIQUE - Code Generation - [Generating code for holistic query evaluation](https://15721.courses.cs.cmu.edu/spring2020/papers/14-compilation/krikellas-icde2010.pdf) - 针对给定的查询计划,创建一个C/C++程序来实现该查询的执行。 - Bake in all the predicates and type conversions - 使用现成的 compiler 将 code 转换为 shared object,链接到 DBMS 进程,然后调用执行。 ## HIQUE - Operator Templates - `SELECT * FROM A WHERE A.val = ? + 1` - ![](https://img.jonahgao.com/oss/note/2025p1/16721_2023_compilation_template.png) - 不再需要 traverse expression tree ## HIQUE - DBMS Intergration - generated query code 可以调用 DBMS 中的其他任意代码(可以跟 interperted queries 一样使用所有相同的组件) - Network Handlers - Bufer Pool Manager - Concurrency Control - Logging / Checkpoints - Indexes - Debugging 相对来说比较容易(调试的是 C++ 代码) ## Observation - relational operators 是推理查询的有用方式,但不是执行查询的最有效方式。 - 将 C/C++ 代码 compile 成可执行代码,需要耗费相对**较长**的时间 - HIQUE 不支持 full pipelining。 ------ # 3. JIT Compilation ## Hyper - JIT Query Compilation - [Efficiently Compiling Efficient Query Plans for Modern Hardware](https://15721.courses.cs.cmu.edu/spring2020/papers/14-compilation/p539-neumann.pdf) - 使用 LLVM toolkit 在内存中将查询编译成 native code。 - Hyper 输出的不是 C++ 代码,而是 LLVM IR。 - 在管道内进行积极的 **operator fusion**,以尽可能长时间地将 tuple 保留在CPU寄存器中。 ## Push-based Execution - ![](https://img.jonahgao.com/oss/note/2025p1/16721_2023_compilation_push.png) - 从 leaf node 开始,在 pipeline 中尽可能地往上 push tuple ## Query Compilation Cost - Hyper 的 query compilation time 与查询大小呈超线性增长。 - joins 的数量 - predicates 的数量 - aggregations 的数量 - 对于 OLTP 应用问题不大 - 经常使用 prepared 语句,相同的语句(不同输入)反复查询 - redshift:compiled query cache(缓存 fragment 级别) ## Hyper - Adaptive Execution - [Adaptive Execution of Compiled Queries](https://15721.courses.cs.cmu.edu/spring2020/papers/14-compilation/kohn-icde2018.pdf) - 为查询生成 LLVM IR 后,使用 interpreter 马上开始执行 IR; - DBMS 在后台 compiles the query - 当 compiled query 完成后,无缝替换掉 interpiretive execution - 针对每个 morsel,检查是否有可用的 compiled version - ![](https://img.jonahgao.com/oss/note/2025p1/16721_2023_compilation_hyper.png) --------- # 4. Real-world Implementations - **Custom** - IBM System R - Actian Vector - Amazon Redshift - Oracle - Microsoft Hekaton - SQLite - TUM Umbra - **JVM-based** - Apache Spark - Neo4j - Splice Machine - Presto / Trino - **LLVM-based** - SingleStore - VitesseDB - PostgreSQL(2018) - CMU Peloton - CMU NoisePage ## IBM System R - [A history and evaluation of System R](https://dl.acm.org/doi/10.1145/358769.358784) - IBM 在1970年代使用了一种原始的代码生成和查询编译形式 - 通过为每个 operator 选择 code templates,将编译后的 SQL 语句转换成汇编代码。 - IBM 在1980年代构建 SQL/DS 和 DB2 时废弃了该项技术 - 外部函数调用带来的很高开销(调用segments) - 可移植性差 - 工程实现复杂 ## Actian Vector - [Micro adaptivity in Vectorwise](https://dl.acm.org/doi/10.1145/2463676.2465292) - **预编译**几千个 primitives(在 typed data 上执行基础操作) - 示例:通过在特定类型的某一列上应用小于运算符来生成 a vector of tuple ids。 - 执行查询计划时,在运行时调用这些 primitives - 一次处理多个 tuples 来摊销函数调用 - primitives示例: ```c size_t scan_lessthan_int32(int *res, int32_t *col, int32_t val) { size_t k = 0; for (size_t i = 0; i < n; i++) if (col[i] < val) res[k++] = i; return (k); } ``` ## Amazon RedShift - [AMAZON REDSHIFT RE-INVENTED](https://dl.acm.org/doi/10.1145/3514221.3526045) - 将 query fragments 转换为 templated C++ code - Push-based execution with vectorization - DBMS 检查每个 templated fragment 是否在客户的 local cache 中存在 compiled version - 如果 local cache 中不存在,会再检查 global cache(**所有客户的**) ## Oracle - 将 PL/SQL 存储过程转换为 `Pro*C` 代码,然后编译成本地的C/C++代码。 ## Microsoft Hekaton - [Compilation in the Microsoft SQL Server Hekaton Engine](http://sites.computer.org/debull/A14mar/p22.pdf) - 可以编译 procedures 和 SQL - 从 imperative syntax tree 生成 C 代码,编译成 DLL,在运行时链接 - 采用安全措施防止有人在查询中注入恶意代码。 ## SQLite - DBMS 将 query plan 转换为 opcodes,然后在一个自定义的 VM(bytecode engine)中执行。 - 也称为 Virtual Database Engine(VDBE) - opcode 的标准可能随着版本而变化 - [The SQLite Bytecode Engine](https://www.sqlite.org/opcode.html) ## TUM UMBRA - [Tidy Tuples and Flying Start: fast compilation and fast execution of relational queries in Umbra](https://dl.acm.org/doi/10.1007/s00778-020-00643-4) - "FlyingStart" adpative execution framework - 从 custom IR 直接生成 x86 汇编 - DBMS 基本上是一个 compiler ## Apache Spark - [Spark SQL: Relational Data Processing in Spark](https://dl.acm.org/doi/10.1145/2723372.2742797) - 2015 年引入新的 Tungsten engine - 将查询的 **WHERE** 从句的表达式树转化为 Scalar ASTs - 然后编译这些 ASTs,来生成 JVM bytecode,再执行(natively)。 - 2010年 Databricks 在新的 Photon engine 中废弃了这种方式 ## Java Databases - JVM-based DBMSs,输出 JVM bytecode - Neo4j - Splice Machine - Presto / Trino - Derby - 类似于生成 LLVM IR ## SingleStore(Pre-2016) - 跟 HIQUE 一样生成 C/C++ 代码,然后调用 GCC - 将所有查询转换为 parameterized 形式,并**缓存**编译后的查询计划 - ![](https://img.jonahgao.com/oss/note/2025p1/16721_2023_compilation_singlestore.png) ## SingleStore (2016-Present) - 将 query plan 转换为使用高级 imperative DSL 表达的 imperative plan - [MPL](https://docs.singlestore.com/managed-service/en/query-data/code-generation.html) - 可以将它看作是一种 C++ 方言 - DBMS 接着将 DSL 转换为 custom opcodes - MemSQL Bit Code(BMC) - 可以认为是 JVM byte code - 最后,DBMS 将 bytecodes 编译成 LLVM IR,然后到 natvie code ## PostgreSQL - 2018 年支持使用 LLVM JIT 编译 predicates 和 tuple 反序列化 - 依赖 optimizer estimates 来决定何时编译 expressions ## VitessDB - [Vitesse DB: 100% PostgreSQL, 100 Times Faster for Analytics](https://www.youtube.com/watch?v=PEmVuYjhQFo) - 使用 LLVM + intra-query parallelism 来加速查询 Postgres / Greenplum - JIT predicates - Push-based processing model - Indirect calls 变为 direct 或者 inlined - 利用硬件来检测 overflow ## Peloton(2017) - 使用 LLVM,采取 Hyper 方式,编译整个 query plan - Relax pipeline breakers,创建 min-batches 来向量化 - software pre-fetching to hiden memory stalls - [Relaxed Operator Fusion for In-Memory Databases: Making Compilation, Vectorization, and Prefetching Work Together At Last](https://15721.courses.cs.cmu.edu/spring2018/papers/22-vectorization2/menon-vldb2017.pdf) ## CMU NoisePage(2019) - SingleStore 的方式,将查询计划转换为面向 database 的 DSL - DSL:更容易解释运行和后续编译 - DSL 编译成 opcodes - interpret opcodes,同时在后台使用 LLVM 编译 - ![](https://img.jonahgao.com/oss/note/2025p1/16721_2023_compilation_noisepage.png) ------ # Parting Thoughts - Query compilations 有价值,但是不容易实现 - MemsQL 的 2016 版本是目前最好的查询编译实现 - 任何想要竞争的新DBMS都必须实现查询编译 --- # Note - 编译的范围 - predicate - 表达式 - 整个 query plan - parametered + cache