# 1. Background ## 1.1 Architecture Overview - ![](https://img.jonahgao.com/oss/note/2025p1/16721_2023_network_layer.png) --------- # 2. Database Access APIs - 现实中的程序往往通过 API 来访问 database - Direct Access(DBMS特定的),比如 MySQL 的 C 客户端 - ODBC([Open Database Connectivity](https://en.wikipedia.org/wiki/Open_Database_Connectivity)) - JDBC([Java Database Connectivity](https://en.wikipedia.org/wiki/Java_Database_Connectivity)) ## 2.1 ODBC - 访问 DBMS 的标准 API,设计成独立于 DBMS 和 OS。 - 最初在 1990s 初由 Microsoft 和 Simba Technologies 开发 - 每个主流的关系型数据库现在都有 ODBC 的实现 - ODBC 基于 [device driver](https://en.wikipedia.org/wiki/Device_driver)模型 - driver 负责将标准命令转换成 DBMS 特定的调用 - ![](https://img.jonahgao.com/oss/note/2025p1/16721_2023_network_odbc.png) - driver 可以模拟一些 DBMS 没有提供的特性,如 Cursor ## 2.2 JDBC - 由 Sun 在 1997 年开发,为 Java 程序连接数据库提供了标准的 API - JDBC 可以认为是 Java 版本的 ODBC(ODBC 是 C 语言编写的) - JDBC 支持不同的 client 侧的配置,因为可能有些 DBMS 没有 native 的 Java driver - 不同的实现方式: - ~~**JDBC-ODBC Bridge**(~~2014年移除) - 将 JDBC 的方法调用转换成 ODBC 的方法调用(适用于没有 jave native driver 的情况) - **Natvie-API Driver**(JNI 调用 C library) - 将 JDBC 方法调用转换成目标 DBMS 的 native calls - **Network-Protocol Driver** - Driver 连接到一个单独进程中的中间件,该中间件将 JDBC 调用转换为 DBMS 特定的协议 - **Database-Protocol Driver**(最好的方式) - 纯 Java 实现,将 JDBC 调用直接转换成 DBMS 特定的协议 -------- # 3. Database Network Protocols - 所有主流的 DBMSs 都在 **TCP/IP** 上实现了自己专有的客户端通信协议。 - 如果应用也运行在相同的机器上,可以使用 Unix domain sockets - 一个典型的 client/server 交互: - client 连接到 DBMS 后开始认证流程,可能会需要 SSL / TLS handshake - client 发生一个 query - DBMS 执行 query,然后序列化结果,返回给 client ## 3.1 Existing Protocols - 大多数**新的系统**实现了一种开源的 DBMS wire protocols,这样可以重用现有的 client driver 而不用再单独开发一个。 - MySQL - PostgreSQL - Redis - 实现协议不意味着完全兼容 - 需要支持 catalogs、SQL dialect 和其他功能 ## 3.2 Protocol Design Space - [Don't hold my data hostage: a case for client protocol redesign](https://dl.acm.org/doi/10.14778/3115404.3115408) - Design Space: - Row vs. Column Layout - Compression - Data Serialization - String Handling - 重新设计意味着需要更改 / 开发新的 Driver ### 3.2.1 Row vs. Column Layout - ODBC / JDBC 都是 row-oriented APIs - server 逐个将 tuple 打包到 messages 中 - client 逐个反序列化 tuple ```C++ String sql = "SELECT * FROM xxx"; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { // Do something magical row by row! rs.getInt(1); rs.getString(2); rs.getDate(3); } stmt.close(); ``` - 但是切换成 column-oriented API 也不高效,因为 client 可能需要访问一个 tuple 的多个列 - **Soluntion:Vector-oriented API** - 行列混合 - Apache Arrow(ADBC) ### 3.2.2 Compression - **方式一:Naive Compression** - 传输 message chunks 之前应用通用压缩方案(lz4、gzip、zstd) - 只有很少的系统支持这个 ([Oracle](https://blogs.oracle.com/dbstorage/post/advanced-network-compression-a-lessor-known-feature-of-advanced-compression), [MySQL](https://dev.mysql.com/doc/refman/8.0/en/connection-compression-control.html)). - **方式二:Columnar-Specific Encoding** - 分析结果、为每列挑选特定的压缩编码(dictionary、RLE、delta) - 没有系统实现这个 - 当网络慢时,使用重量级的压缩更好 - message chunks sizes 越大,压缩率越高 ### 3.2.3 Data Serialization #### Binary Encoding - client 处理 endian conversion - 序列化格式与 DBMS 的存储格式越接近,序列化的开销就越小 - DBMS 可以依赖现有的库(Protobuffer、Thrift、FlatBuffers)来实现自己的格式 #### Text Encoding - 将所有 binary values(来自存储层的) 转换成 strings - 比如使用 `atoi`, 123456 -> “123456” - 不需要担心 endianness ### 3.2.4 String Handling - **方式一:Null Termination** - **方式二:Length-Prefixes** - **方式三:Fixed Width** - 填充每个 string 到该列的 max size - 适合小 string ## 3.3 Apache Arrow - Table 在内存中的标准列存格式 - 类似 Parquet / ORC,但是数据在内存中 - 最初的 Java 实现:[Apace Drill](https://drill.apache.org/) - 允许系统间交互数据,而不需要(反)序列化成私有格式 - 将内存中的列存格式直接发送给 Client - Arrow 项目包含了围绕这个格式的其他组件 - Write Protocols([ADBC](https://arrow.apache.org/blog/2023/01/05/introducing-arrow-adbc/),[Arrow Flight](https://arrow.apache.org/blog/2019/10/13/introducing-arrow-flight/)) - 执行引擎 Datafusion --------- # 4. Kernel / User Bypass Methods - DBMS 的 网络协议实现不是 slowdown 的唯一原因 - OS 的 TCP / IP stack is slow - 昂贵的 context switch / interrupts - Data copying - 内核中的许多 latches ## 4.1 Kernel Bypass Methods - 允许系统直接从 NIC 获取数据到 DBMS address space - 不需要 data copying - 没有 OS TCP/IP stack - 不同的实现方式: 1. **Data Plane Developemnt Kit** 2. **Remote Direct Memory Access** 3. **io_uring** ### 4.1.1 DPDK - 一组 libraries,可以让程序直接访问 NIC。将 NIC 视为 bare metal device。 - 需要 DBMS 来额外的开发去管理 network stack(layers 3+4)、memory 和 buffers - TCP / IP in usercode(如 [F-Stack](http://www.f-stack.org/)) - 没有 data copying - 没有系统调用 - Example - [ScyllaDB's Seastar](https://seastar.io/) - [Yellowbrick's ybRPC](https://yellowbrick.com/resources/executive-overviews/engineered-for-extreme-efficiency/) ### 4.1.2 RDMA - 直接读写 remote host 的内存,不需要通过 OS - client 需要知道目标数据的正确地址 - server 无法感知内存被远程访问了(no callbacks) - Example - [Oracle RAC](https://en.wikipedia.org/wiki/Oracle_RAC) - [Microsoft FaRM](https://www.microsoft.com/en-us/research/publication/farm-fast-remote-memory/)(OLTP 系统) - 同样需要特殊的硬件 ### 4.1.3 io_uring - Linux 的系统调用接口,用于 **zero-copy** **异步 IO** 操作 - 最初添加于 [2019](https://lwn.net/Articles/810414/) ,用于访问 storage devices - [2022](https://github.com/axboe/liburing/wiki/io_uring-and-networking-in-2023)扩展支持 network devices - Windows 上的类似实现:[ICOP](https://learn.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports) - OS 暴露两个 circular buffers(queues)来存储 submission 和 completion I/O 请求 - DBMS 向 kernel 提交请求,读写到 DBMS 提供的 buffers(避免拷贝) - 当 OS 完成请求,将事件放到 completion queue,然后调用 callback - Example - [A journey to io_uring, AIO and modern storage devices](https://clickhouse.com/blog/a-journey-to-io_uring-aio-and-modern-storage-devices) Clickhouse - [Introduce io_uring support to ClickHouse](https://github.com/ClickHouse/ClickHouse/issues/10787) Clickhouse - [Importing 300k rows/sec with io_uring](https://questdb.io/blog/2022/09/12/importing-300k-rows-with-io-uring/) QuestDB - [A Programmer-Friendly I/O Abstraction Over io_uring and kqueue](https://tigerbeetle.com/blog/a-friendly-abstraction-over-iouring-and-kqueue/) - [Mainlining Databases: Supporting Fast Transactional Workloads on Universal Columnar Data File Formats](https://www.vldb.org/pvldb/vol14/p534-li.pdf) ## 4.2 User Bypass Methods - 通过 eBPF 当 packets 到达后在 OS kernel 内执行逻辑 - eBPF 由 DSL 编写,然后编译成 bytecode、verified、运行时 JIT-ed - programming mode 受限(比如 no malloc,限制可以执行的指令数量) - 只适合于 DBMS 操作 IO 的部分,不能处理数据太久 - [Tastes Great! Less Filling! High Performance and Accurate Training Data Collection for Self-Driving Database Management Systems](https://dl.acm.org/doi/10.1145/3514221.3517845) ------------- # Parting Thoughts - DBMS的网络协议是一个经常被忽视的性能瓶颈。 - Kernel bypass 能很大提升性能,但需要 more bookkeeping - 可能对于 **DBMS 内部通信**更有用 - User bypass 是 DMBSs 中 ephemeral I/Os 的一个有趣方向。