Skip to main content

One post tagged with "贡献者手册"

View All Tags

Alt text

Databend 的系统表位于 query/storage 目录下。当然,如果因为一些特殊的构建原因无法放在这个位置的话,也可以考虑临时放到 service/databases/system 这个目录(不推荐)。

系统表的定义主要关注两个内容:一个是表的信息,会包含表名、Schema 这些;另一个就是表中数据的生成/获取。刚好可以对应到 SyncSystemTableAsyncSystemTable 这两个 Trait 中的 get_table_infoget_full_data 。到底是同步还是异步,取决于在获取数据时,是否涉及到异步函数的调用。

实现

本文将会以 credits 表的实现为例,介绍 Databend 系统表的实现,代码位于 https://github.com/datafuselabs/databend/blob/main/src/query/storages/system/src/credits_table.rscredits 会返回 Databend 所用到的上游依赖的信息,包括名字、版本和许可三个字段。

首先,需要参考其他系统表的实现,去定义表对应的结构,只需要保有表信息的字段就可以了。

pub struct CreditsTable {
table_info: TableInfo,
}

接下来是为 CreditsTable 表实现 create 方法,对应的函数签名如下:

pub fn create(table_id: u64) -> Arc<dyn Table>

传入的 table_id 会在创建表时由 sys_db_meta.next_table_id() 生成。

schema 用于描述表的结构,需要使用 TableSchemaRefExtTableField 来创建,字段名字和类型取决于表中的数据。

let schema = TableSchemaRefExt::create(vec![
TableField::new("name", TableDataType::String),
TableField::new("version", TableDataType::String),
TableField::new("license", TableDataType::String),
]);

对于字符串类数据,可以使用 TableDataType::String ,其他基础类型也类似。但如果你需要允许字段中存在空值,比如字段是可以为空的 64 位无符号整数,则可以使用 TableDataType::Nullable(Box::new(TableDataType::Number(NumberDataType::UInt64))) 的方式,TableDataType::Nullable 表示允许空值,TableDataType::Number(NumberDataType::UInt64) 表征类型是 64 位无符号整数。

接下来就是定义表的信息,基本上只需要依葫芦画瓢,把描述、表名、元数据填上就好。

let table_info = TableInfo {
desc: "'system'.'credits'".to_string(),
name: "credits".to_string(),
ident: TableIdent::new(table_id, 0),
meta: TableMeta {
schema,
engine: "SystemCredits".to_string(),
..Default::default()
},
..Default::default()
};

SyncOneBlockSystemTable::create(CreditsTable { table_info })

对于同步类型的表往往使用 SyncOneBlockSystemTable 创建,异步类型的则使用 AsyncOneBlockSystemTable

接下来,则是实现 SyncSystemTableSyncSystemTable 除了需要定义 NAME 之外,还需要实现 4 个函数 get_table_infoget_full_dataget_partitionstruncate ,由于后两个有默认实现,大多数时候不需要考虑实现自己的。(AsyncSystemTable 类似,只是没有 truncate

NAME 的值遵循 system.<name> 的格式。

const NAME: &'static str = "system.credits";

get_table_info 只需要返回结构体中的表信息。

fn get_table_info(&self) -> &TableInfo {
&self.table_info
}

get_full_data 是相对重要的部分,因为每个表的逻辑都不太一样,credits 的三个字段基本类似,就只举 license 字段为例。

let licenses: Vec<Vec<u8>> = env!("DATABEND_CREDITS_LICENSES")
.split_terminator(',')
.map(|x| x.trim().as_bytes().to_vec())
.collect();

license 字段的信息是从名为 DATABEND_CREDITS_LICENSES 的环境变量(参见 common-building)获取的,每条数据都用 , 进行分隔。

字符串类型的列最后是从 Vec<Vec<u8>> 转化过来,其中字符串需要转化为 Vec<u8> ,所以在迭代的时候使用 .as_bytes().to_vec() 做了处理。

在获取所有数据后,就可以按 DataBlock 的形式返回表中的数据。非空类型,使用 from_data ,可空类型使用 from_opt_data

Ok(DataBlock::new_from_columns(vec![
StringType::from_data(names),
StringType::from_data(versions),
StringType::from_data(licenses),
]))

最后,要想将其集成到 Databend 中,还需要编辑 src/query/service/src/databases/system/system_database.rs,将其注册到 SystemDatabase 中 。

impl SystemDatabase {
pub fn create(sys_db_meta: &mut InMemoryMetas, config: &Config) -> Self {
...
CreditsTable::create(sys_db_meta.next_table_id()),
...
}
}

测试

系统表的相关测试位于 src/query/service/tests/it/storages/system.rs

对于内容不会经常动态变化的表,可以使用 Golden File 测试,其运行逻辑是将对应的表写入指定的文件中,然后对比每次测试时文件内容是否发生变化。

#[tokio::test(flavor = "multi_thread")]
async fn test_columns_table() -> Result<()> {
let (_guard, ctx) = crate::tests::create_query_context().await?;

let mut mint = Mint::new("tests/it/storages/testdata");
let file = &mut mint.new_goldenfile("columns_table.txt").unwrap();
let table = ColumnsTable::create(1);

run_table_tests(file, ctx, table).await?;
Ok(())
}

对于内容可能会变化的表,目前缺乏充分的测试手段。可以选择测试其中模式相对固定的部分,比如行和列的数目;也可以验证输出中是否包含特定的内容。


#[tokio::test(flavor = "multi_thread")]
async fn test_metrics_table() -> Result<()> {
...
let result = stream.try_collect::<Vec<_>>().await?;
let block = &result[0];
assert_eq!(block.num_columns(), 4);
assert!(block.num_rows() >= 1);

let output = pretty_format_blocks(result.as_slice())?;
assert!(output.contains("test_test_metrics_table_count"));
#[cfg(feature = "enable_histogram")]
assert!(output.contains("test_test_metrics_table_histogram"));

Ok(())
}

Alt text

在最近的一个 Issue 中 (#9387),Databend 社区的朋友希望能够利用 PGO 技术构建性能优化的可执行文件。让我们一起来看一下如何使用 Rust 构建 PGO 优化后的 Databend 吧!

背景知识

Profile-guided optimization 是一种编译器优化技术,它会收集程序运行过程中的典型执行数据(可能执行的分支),然后针对内联、条件分支、机器代码布局、寄存器分配等进行优化。

引入这一技术的背景是:编译器中的静态分析技术能够在不执行代码的情况下考虑代码优化,从而提高编译产物的性能;但是这些优化并不一定能够完全有效,在缺乏运行时信息的情况下,编译器无法考虑到程序的实际执行情况。

PGO 可以基于应用程序在生产环境中的场景收集数据,从而允许优化器针对较热的代码路径优化速度并针对较冷的代码路径优化大小,为应用程序生成更快和更小的代码。

rustc 支持 PGO,允许创建内置数据收集的二进制文件,然后在运行过程中收集数据,从而为最终的编译优化做准备。其实现完全依赖 LLVM。

典型过程

构建 PGO 优化的二进制文件通常需要进行以下几步工作:

  1. 构建内置数据收集的二进制文件
  2. 运行并收集数据,数据会以 .proraw 的形式存在
  3. .proraw 文件转换为 .prodata 文件
  4. 根据 .prodata 文件进行构建优化

前置准备

运行过程中的收集到的数据最终需要使用 llvm-profdata 进行转换,经由 rustup 安装 llvm-tools-preview 组件可以提供 llvm-profdata ,或者也可以考虑使用最近版本的 LLVM 和 Clang 提供的这一程序。

rustup component add llvm-tools-preview

安装之后的 llvm-profdata 可能需要被添加到 PATH ,路径如下:

~/.rustup/toolchains/<toolchain>/lib/rustlib/<target-triple>/bin/

具体步骤

这里并没有选用某个具体生产环境的工作负载,而是使用 Databend 的 SQL 逻辑测试作为一个示范。在性能上可能并不具有积极意义,但可以帮助我们了解如何进行这一过程。

特别提示: 提供给程序的数据样本必须在统计学上代表典型的使用场景; 否则,反馈有可能损害最终构建的整体性能。

  1. 清除旧数据

    rm -rf /tmp/pgo-data
  2. 编译支持收集 profile 数据的 release,使用 RUSTFLAGS 可以将 PGO 编译标志应用到所有 crates 的编译中。

    RUSTFLAGS="-Cprofile-generate=/tmp/pgo-data" \    
    cargo build --release --target=x86_64-unknown-linux-gnu
  3. 运行编译好的程序,实际情况下推荐使用符合生产环境典型工作负载的数据集和查询。

    • 通过脚本启动 Databend 单机节点,考虑到生产环境更多是以集群模式运行,也可以启动 Databend 集群。
    • 导入数据集并运行典型的查询工作负载。

    示例中选择执行 SQL 逻辑测试,仅供参考。

    BUILD_PROFILE=release ./scripts/ci/deploy/databend-query-standalone.sh 
    ulimit -n 10000;ulimit -s 16384; cargo run -p sqllogictests --release -- --enable_sandbox --parallel 16 --no-fail-fast
  4. 使用 llvm-profdata 合并 profile 数据

    llvm-profdata merge -o /tmp/pgo-data/merged.profdata /tmp/pgo-data
  5. 在 profile 数据指导下进行编译,其实可以注意到,两次编译都使用 --release 标志,因为实际运行情况下,我们总是使用 release 构建的二进制。

    RUSTFLAGS="-Cprofile-use=/tmp/pgo-data/merged.profdata -Cllvm-args=-pgo-warn-missing-function" \    
    cargo build --release --target=x86_64-unknown-linux-gnu
  6. 再次运行编译好的程序,运行之前的工作负载以检查性能。

    BUILD_PROFILE=release ./scripts/ci/deploy/databend-query-standalone.sh 
    ulimit -n 10000;ulimit -s 16384; cargo run -p sqllogictests --release -- --enable_sandbox --parallel 16 --no-fail-fast

参考资料

今天 Databend 合并了一个重大重构,expression 正式合并到 main 分支,为 Databend 装载了全新的静态类型系统和表达式计算框架。接下来一到两周内会进行全面测试,这个期间 nightly release 停发。如果大家对这套 New Expression 系统感兴趣,不妨参考这篇文章,亲自动手体验一下。

如何构建 Databend

Make

采用 make 构建的方式会调用预先设定好的脚本,脚本位于 scripts/build 路径下。

只开启默认特性,并且一次性构建 databend-querydatabend-metadatabend-metactl 以及 open-sharing 四个程序。

参考之前文章《如何设置 Databend 开发环境》,设置好开发环境。

  • make build 可轻松构建 debug 版本。
  • make build-release 则会构建 release 版本,并会采用 objcopy 减少二进制体积。
  • make build-native 构建针对本机 CPU 优化的版本。

Cargo

使用 cargo 构建的好处在于可以按需开启特性,并灵活控制要构建的目标二进制文件。

常用的命令格式如:

RUSTFLAGS="--cfg tokio_unstable" cargo build --bin=databend-query --features=tokio-console

即可构建启用 tokio-console 支持的 databend-query ,使用 RUSTFLAGS="--cfg tokio_unstable" 是因为 tokiotracing 特性还没有稳定下来。

Databend features 速览

这里只介绍几个比较常用的特性,具体支持的特性可以查看 Cargo.toml 中的 features 字段。

  • simd = ["common-arrow/simd"]:默认开启的特性,启用 arrow2 的 SIMD 支持(meta & query)。
  • tokio-console = ["common-tracing/console", "common-base/tracing"]:用于监控和调试异步调用程序(meta & query)。
  • memory-profiling = ["common-base/memory-profiling", "tempfile"]:用于内存分析(meta & query)。
  • hive = ["common-hive-meta-store", "thrift", "storage-hdfs"]:用于提供 hive 支持(query)。

跨平台构建

Databend 提供 Docker build-tool Image,可以简化跨平台构建所需工作。

示例选用 x86_64-unknown-linux-gnu 目标平台,其他支持平台也类似:

IMAGE='datafuselabs/build-tool:x86_64-unknown-linux-gnu' RUSTFLAGS='-C link-arg=-Wl,--compress-debug-sections=zlib-gnu' ./scripts/setup/run_build_tool.sh cargo build --target x86_64-unknown-linux-gnu

如何运行 Databend

一旦构建完成后,就可以在 target 目录下找到对应的二进制程序。对于 Databend 的运行,同样有多种可供选择的方式。

Make

Databend 同样预置了一些方便开发人员快速运行的 make 命令:

  • make run-debug:构建并运行 debug 版本。
  • make run-release:构建并运行 release 版本。
  • make run-debug-management:构建 debug 版本并以 management 模式运行。

如果你检查过位于 scripts/ci/deploy 的脚本,就会发现,这里本质上是直接执行了对应程序的二进制文件,并为其导入了位于 scripts/ci/deploy/config 目录下的相应配置。

Cargo

使用 cargo 也可以运行 Databend,通常我们会推荐采用指定配置文件的方式来启动。

cargo run --bin databend-query -c scripts/ci/deploy/config/databend-query-embedded-meta.toml

执行上述命令可以以 embedded meta 的模式启用 databend-query 的单节点模式。如果不指定在 embedded meta 模式下运行,就必须提供 your-meta-service-endpoints

另外,如果有需要的话也可以参考 scripts/ci/deploy/config 中的文件编写自己的配置。

集群模式

Databend 总是推荐用户在生产环境中使用集群模式,为了方便模拟这一情况,也可以执行:

BUILD_PROFILE=release ./scripts/ci/deploy/databend-query-cluster-3-nodes.sh

来启动一个具有 3 个 databend-meta 节点和 3 个 databend-query 节点的集群,通过指定 BUILD_PROFILE 可以在 release 和 debug 版本间切换。

快速安装

为方便开发者快速建立开发环境,Databend 维护了一个复杂的 shell 脚本,位于 scripts/setup/dev_setup.sh

只需执行一条指令即可完成开发环境配置:

$ make setup -d

注意:此过程会辅助安装部分 python 环境,可能会对本地原开发环境造成影响,建议预先执行以下命令以创建并启用专属虚拟环境。

$ python -m venv .databend
$ source .databend/bin/activate

如果遇到依赖缺失问题,可以参考「分步安装 - 测试必备」这一部分的内容安装。

分步安装

这里以 Fedora 36 为例,考虑到不同系统和发行版之间的差异,你可能需要自行安装 gccpythonopenssl

安装 Rust toolchain

推荐使用 rustup 来管理 Rust toolchain ,参考 https://rustup.rs/ 进行安装。

对于 MacOS 和 Linux 用户,执行:

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Databend 通常使用最新发布的 nightly 工具链进行开发,相关信息记录在 rust-toolchain.toml 中。

Rustup 会在使用时对工具链进行自动重载,安装时只需默认配置。

$ cargo build
info: syncing channel updates for 'nightly-2022-05-19-x86_64-unknown-linux-gnu'
info: latest update on 2022-05-19, rust version 1.63.0-nightly (cd282d7f7 2022-05-18)

安装必备依赖

以下列出了一些安装构建和测试必备依赖的关键步骤,说明及报错信息以注释形式呈现。

构建必备

# common-hive-meta-store 需要,thrift not found
$ sudo dnf install thrift
# openssl-sys 需要,Can't locate FindBin.pm, File/Compare.pm in @INC
$ sudo dnf install perl-FindBin perl-File-Compare
# prost-build 需要,is `cmake` not installed?
# The CMAKE_CXX_COMPILER: c++ is not a full path and was not found in the PATH.,安装 clang 时也会安装 gcc-c++ 和 llvm
$ sudo dnf install cmake clang

测试必备

# 功能测试和后续体验需要
$ sudo dnf install mysql
# 包含目前功能测试和 lint 需要的所有 Python 依赖
$ cd tests
$ pip install poetry
$ poetry install
$ poetry shell
# sqllogic 测试需要(包含在上面步骤中,按需选用)
(tests) $ cd logictest
$ pip install -r requirements.txt
# fuzz 测试需要
(tests) $ cd fuzz
$ pip install -r requirements.txt

Lint 必备

# taplo fmt 需要
$ cargo install taplo-cli

编辑器 - Visual Studio Code

Visual Studio Code

插件推荐

rust-analyzer

  • 作者:The Rust Programming Language
  • 为 Visual Studio Code 提供 Rust 语言支持。

crates

  • 作者:Seray Uzgur
  • 帮助 Rust 开发者管理 Cargo.toml 中的依赖。仅支持来源为 crates.io 的依赖。

CodeLLDB

  • 作者:Vadim Chugunov
  • 由 LLDB 驱动的原生调试工具。支持调试 C++ 、Rust 和其他编译语言。

Remote - Containers

  • 作者:Microsoft
  • 在 Docker 容器内打开任何文件夹或 Repo ,并利用 Visual Studio Code 的全部功能。

利用 Dev Containers 开发(For Linux)

安装「Remote - Containers」插件,打开 Databend 后会看到右下角弹出窗口并提示「Reopen in Container」。

安装 Docker

根据 Docker Docs - Install 安装并启动对应你发行版的 docker 。

Fedora 36 为例,步骤如下:

# 移除旧版本 docker
$ sudo dnf remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine
# 设置存储库
$ sudo dnf -y install dnf-plugins-core
$ sudo dnf config-manager \
--add-repo \
https://download.docker.com/linux/fedora/docker-ce.repo
# 安装 Docker Engine
$ sudo dnf install docker-ce docker-ce-cli containerd.io docker-compose-plugin

将当前 User 添加到 'docker' group 中

参考 Docker Docs - PostInstallManage Docker as a non-root user 一节配置,可能需要重启。

步骤如下:

# 添加 docker 用户组
$ sudo groupadd docker
# 将用户添加到 docker 这个组中
$ sudo usermod -aG docker $USER
# 激活更改
$ newgrp docker
# 更改权限以修复 permission denied
$ sudo chown "$USER":"$USER" /home/"$USER"/.docker -R
$ sudo chmod g+rwx "$HOME/.docker" -R

其他步骤

启用 Docker :

$ sudo systemctl start docker

点击左下角「打开远程窗口」选中「Reopen in Container」即可体验。

其他实用工具推荐

这里列出一些可能有助于 Databend 开发的实用工具,根据实际情况按需选用。

starship

轻量级、反应迅速、可无限定制的高颜值终端!

starship

参考 starship - installation 进行安装。

curl -sS https://starship.rs/install.sh | sh

hyperfine

命令行基准测试工具。

hyperfine

参考 hyperfine - installation 进行安装。

cargo install hyperfine