基于 GPU 数据库系统的并发查询性能优化.md

摘要

  • 用GPU的并行计算能力 优化查询执行的性能
  • 现有研究能利用 GPU 的高性能计算能力,
    • 通过查询任务间协同进行GPU资源管理的机制,支持并发的查询请求,有效提升 GPU 的资源利用率。
  • 但这种系统架构中由于各查询任务单独管理 GPU 资源带来重复开销,
    • 过度使用 PCIe 总线的数据传输带宽
    • 导致GPU的整体资源利用率低。
  • HyperQx-GPU 是一种GPU内存数据库系统新的软件架构设计与实现,
    • 共享CUDAContext和数据库列存储数据
  • 结果表明,HyperQx-GPU 能够达到 12.0 倍的提升。

1 背景和相关工作

1.1 GPU 硬件结构及编程框架

  • CPU 和 GPU 的硬件架构有很大差异,
    • 如图 1
  • 大量逻辑控制单元 和 缓存单元
  • 更多计算核心 和 高性能内存单元,
    • 适合用于处理批量数据的密集型并行计算。
  • NVIDIAGTX TITAN X 为例,
    • 3584 个计算核心,并行计算能力强。
    • 还有5005 MHz 时钟频率和
    • 384 bit 传输位宽的高性能内存单元,
    • 于通常CPU 搭配的 DDR4 内存,
      • 前者更高内存数据访问速度。

基于 GPU 数据库系统的并发查询性能优化.md

目前GPGPU,编程框架:OpenCL 和 CUDA [4] 。

  • 跟MultiQx-GPU 对比
  • 本文实现过用CUDA 框架
  • CUDA 中,程序代码被编译为CPU的宿主程序和GPU的
    • GPU中运行的函数叫 kernel。
    • GPU 是协处理器,
      • 执行计算任务时
      • 要宿主 通过 CUDA 提供的 API 进行驱动
  • 整体上是异构的编程模型。

CUDA运行时,宿主调用的CUDAAPI 被放到一stream 内。

  • CUDA 中的一个stream 代表一串连续的 CUDA API 调用命令,
    • 同stream 内的 CUDAAPI被 GPU 串行调度执行。
  • 宿主程序可创多stream,不同的stream里发起的命令在无数据依赖的情况下,
    • 会被GPU调度器并发执行
  • 为利用多个 stream
    • 同时发起互相不存在依赖关系的 PCIe 数据传输和
    • kernel调用,
    • 使两者同时执行,从而利用 GPU 的数据带宽和计算资源。

1.2 GPU数据库系统

基于GPU高性能,开始用GPU加速join和sort[5 -8]及事务的执行[9]

  • 后来研究考虑将GPU作为数据库查询执行的主要硬件[10 -11]
    • 前述YDB是典型的设计与实现。
  • 相比于CPU数据库,YDB能显著地提升。
    • 缺点也很明显,即串行执行查询任务时,
    • 每个任务独占GPU硬件
    • 无利用GPU计算和PCIe数据传输带宽等

支持并发的查询请求

  • MultiQx-GPU 和 Ocelot
  • 以 MultiQx-GPU为例
    • 通过动态链接库拦截的机制
    • 执行SQL查询任务前通过LD_PRELOAD环境变量
      • 预加载系统提供的动态链接库,
    • 拦截查询发起的 CUDARuntime API 调用,实现并发的查询任务间协同管理 GPU 资源,
    • 使不同任务进程间共享 GPU 资源。
  • 与YDB比,MultiQx-GPU 执行并发查询时能达到55% 的系统吞吐量提升
  • MultiQx-GPU 的系统架构如图2
    • 虚线上方为系统实现部分
    • 下方为 CUDA 提供的运行时环境。
      基于 GPU 数据库系统的并发查询性能优化.md

MultiQx-GPU改进之处:

  • (1)在不同查询中重复传输可共享的只读的列存储数据,
    • PCIe总线的带宽浪费。
    • 图 3是HyperQx-GPU中(关闭列存储数据共享功能)一次查询。
    • 334 ms 的查询任务执行时间中(不包括创建 CUDA Context 的时间)
    • CPU 内存到 GPU 内存的 PCIe 数据传输占了262ms。
    • 因为数据库中的计算任务通常涉及大量的列数据扫描和表之间的连接等操作,
    • 执行实际的SQL前要传输大量的列存储数据到 GPU 内存,
    • 计算结束后把结果数据从 GPU 传回 CPU
    • 大量的 PCIe 数据传输使得本来就稀缺的 PCIe
    • 总线带宽成为了该系统执行查询任务时的性能瓶颈。
  • (2)MultiQx-GPU 系统中的每个查询任务进程独立调用
    • CUDARuntimeAPI 来使用 GPU 资源,使得每个进程均需要创建独立的 CUDAContext,
    • 增加查询任务的执行时间,进而影响系统的整体性能。

基于 GPU 数据库系统的并发查询性能优化.md

常用列存储,以减少扫描等查询操作在单条记录上的执行时间[11]。

  • SQL 中的where ,解析后生成进行列扫描的 SQL 操作,
  • 在某列的所有数据上做同样的操作,符合向量计算模型。
  • 若用 GPU的高性能并行计算能力来此扫描操作,数量级提升。

2 系统设计与实现

2.1 系统架构

  • HyperQx-GPU C/S 架构。
    • 查询任务端,用动态链接库的设计 来 系统对查询任务进程透明
    • 不侵入查询任务的代码逻辑
    • 查询进程通过动态链接库与数据库服务进程直接通过 IPC 机制通信
    • 保证了具体查询任务进程和数据库服务进程之间的低耦合,
  • HyperQx-GPU 的系统架构如图4

基于 GPU 数据库系统的并发查询性能优化.md

HyperQx-GPU 分两部分。

  • CPU 为查询进程和数据库服务进程;
  • GPU 包括 SQL 操作符的 kernel 实现,
    • 由数据库服务进程进行加载和调用。

查询进程

  • 包括两部分。
  • SQL查询语句请求生成,将解析后的SQL语法树与数据库schema结合,
    • 编译成控制数据读取和调用SQL 操作符的 CPU 宿主程序。
    • 宿主程序根据SQL查询语句逻辑,调用相应的CUDARuntimeAPI控制整个查询任务的逻辑流程。
  • 与数据库服务进程进行IPC通信
    • 的客户端动态链接库
    • 利用动态链接库拦截技术拦截了到 CUDA 动态链接库 libcudart的调用,
    • 将宿主程序中包含的 CUDARuntimeAPI调用映射为
      • 到服务进程的 IPC 调用。

服务进程

  • 服务进程监听 Unix Domain Socket
    • 来接收查询端通过动态链接库发起的IPC请求
    • 调用数据库管理层的函数进行实际的 GPU查询任务执行和资源调度等任务,
    • 并将结果返回给客户端。
    • 数据库的核心管理层
      • 列存储数据共享、GPU 硬件管理和 kernel 调用等核心服务的逻辑,
    • 以函数库的形式向服务进程提供数据库管理功能。

服务进程用多线程的架构,

  • 用独立的线程来服务不同查询任务进程的请求,以降低不同任务的相互影响,
  • 同时可以方便地支持并发查询
  • 使用多线程架构使得某一个任务的失败不影响其他任务,
  • 起到了查询任务隔离效果

2.2 系统设计与实现

  • 介绍 HyperQx-GPU两核心功能
    • ———共享 CUDAContext 和
    • 共享数据库列存储数据的设计与实现,
    • 并阐述该实现方式的优势。

2. 2. 1 共享 CUDAContext

  • 在数据库系统运行的所有查询任务间共享服务进程中的 CUDAContext,
  • 用于发起所有的 GPU 调用,
  • 是 HyperQx-GPU 系统的核心设计之一。
  • 该设计使得查询任务的 CUDARuntimeAPI 调用能够被数据库服务进程统一管理,
  • 节省了每个查询任务进程需要单独创建 CUDAContext 的开销,

HyperQx-GPU 服务端进程初始化时创一个CUDA Context,

  • 作为整个数据库系统生命周期中,调度GPU 资源的全局唯一环境。
  • 当查询任务启动时,其 CUDA Runtime API 调用被动态链接库拦截,
    • 不触发 CUDA 隐式创建 CUDA Context 的机制,
    • 而通过数据库服务进程用全局唯一CUDA Context,
  • 节省每个查询单独创建所消耗的时间。

使每个查询任务间的 CUDARuntime-API调用互不阻塞,

  • 服务进程在启动每个查询线程时,在CUDAContext 中创建一个单独的 stream,
  • 用于发起该查询进程请求的 GPU 调用。
  • 不同 stream 上进行的 CUDAAPI 调用不相互阻塞,
  • 会被 GPU 并发调度执行,
  • 因此可同时进行不同查询任务的 PCIe 数据传输和 kernel 调用,

2.2 共享列存储数据

  • 实现了跨查询任务的列存储数据共享机制:
  • 多个查询任务如果使用了相同的列存储数据,
  • 服务进程会根据当前的系统状态决定通过 PCIe 总线传输数据到 GPU 内存
  • 或者复用已经存在于 GPU 内存的数据。
  • 下面将详细介绍该机制的行为和实现。

服务进程初始化时,

  • 将数据库的列存储数据预加载到内存,
    • 生成列的名称到内存地址空间的映射表。
  • 查询进程要访问特定列时,
    • 会通过客户端动态链接库进行 IPC请求,
    • 服务进程根据表和列,
    • 返回该列对应的内存地址作为后续操作的句柄。
  • 列存储数据只存在于服务进程的内存地址空间,
    • 查询进程无法通过该地址直接访问或修改数据库的列存储数据,
    • 起到隔离效果。