如何为 TiKV Coprocesser 做贡献
source link: https://xuanwo.io/2021/03-how-to-contribute-to-tikv-copr/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
如何为 TiKV Coprocesser 做贡献
TiDB 是 PingCAP 推出的开源分布式 SQL 数据库,而 TiKV 最初是作为 TiDB 的存储底层设计的,现在已经被 PingCAP 捐赠给 CNCF,作为一个通用的分布式 Key-Value 数据库存在。
从社区贡献者的角度来看,TiKV Coprocesser 模块可能是整个 TiKV 项目中最容易参与蹭贡献的模块:它与 TiKV 的其他部分关联性相对较弱,对存储/数据库背景知识的要求也更少。本文就来讲讲如何为 TiKV Coprocesser 模块 (蹭 PR) 做贡献。
正如最开始提到的,TiKV 是 TiDB 的存储底层,他们的关系如图所示:
于是我们不难构建出这样的模型:
这个模型的问题在于:
- TiDB 与 TiKV 之间有大量的传输开销:以
COUNT
为例,应用只需要知道结果,但是 TiDB 还是需要从 TiKV 中列取所有的 KV 对。 - TiDB 节点的负载很大,但是 TiKV 的负载却很低,没有充分利用资源。
于是工程师们提出可以将一部分计算任务交给 TiKV,而在 TiKV 中负责这部分计算任务的模块叫做 Coprocessor,这个过程也叫做(算子)下推。
而具体到工程实现上,Coprocessor 在 TiKV 侧的实现可分为如下几部分:
此处的介绍均基于成文时 master 分支的最新 commit aca3d67
components/tidb_query_aggr
:Coprocessor 中实现聚合函数的逻辑components/tidb_query_common
:Coprocessor 的通用库components/tidb_query_executors
:Coprocessor 的执行逻辑components/tidb_query_expr
:Coprocessor 中实现的 expression 函数components/tidb_query_codegen
:用于生成向量化实现的 codegencomponents/tidb_query_datatype
:Coprocessor 中用到的数据类型
Coprocessor 自从提出到现在已经经历过数次变更:
火山模型 -> 向量化模型
在向量化模型刚提出时,社区要求贡献者同时提供非向量化和向量化的实现,而现在由于已经彻底过渡到向量化模型,所有非向量化的实现都已经移除了,所以只需要实现向量化即可。
此外,所有向量化的实现均由 rpn_fn
这个过程宏自动生成,所以贡献者不需要自行实现,后续我会介绍这个宏的用法。
Raw based -> Chunk based
为了减少内存占用,尽可能使用 SIMD 指令优化,社区提出了 RFC: Using chunk format in coprocessor framework。
目前该 RFC 已经基本实现完毕。在实现过程中社区为 rpn_fn
宏引入了一个新的配置项叫做:nullable
,表明这个函数需要自行处理输入参数含有 None
的逻辑,实现了对旧有实现的兼容。
rpn_fn 介绍
rpn_fn
的实现位于 components/tidb_query_codegen/src/rpn_function.rs
,它的主要作用是为函数生成向量化实现。
#[rpn_fn]
fn foo(x: Option<&u32>) -> Result<Option<u8>> {
Ok(None)
}
rpn_fn
常用的参数如下所示,更多的细节请参见 rpn_fn
的注释和具体实现。
nullable
nullable
是为了兼容旧实现而增加的参数。
如果函数带 nullable
,那它需要传入形如 Option<X>
的参数,比如:
#[rpn_fn(nullable)]
#[inline]
pub fn logical_and(lhs: Option<&i64>, rhs: Option<&i64>) -> Result<Option<i64>> {
...
}
如果函数不带 nullable
,那 rpn_fn
会为其生成 None
处理的逻辑:只要输入参数至少有一个含有 None
那就返回 Ok(None)
。生成的逻辑中会进行很多优化,比自行处理要好很多,所以社区鼓励当函数的行为符合这一要求时,就去掉 nullable
。
#[rpn_fn]
#[inline]
pub fn length(arg: BytesRef) -> Result<Option<i64>> {
Ok(Some(arg.len() as i64))
}
writer
writer
是针对返回值为 BytesRef
函数的优化,设计的细节可以参见 RFC。
以 repeat
为例:
#[rpn_fn(writer)]
#[inline]
pub fn repeat(input: BytesRef, cnt: &Int, writer: BytesWriter) -> Result<BytesGuard> {
let cnt = if *cnt > std::i32::MAX.into() {
std::i32::MAX.into()
} else {
*cnt
};
let mut writer = writer.begin();
for _i in 0..cnt {
writer.partial_write(input);
}
Ok(writer.finish())
}
capture
capture
用于捕获调用函数时传递的变量,比较常用在时间相关的处理逻辑中,因为 SQLMode, 时区之类的信息是存储在调用函数时传递的 EvalContext
中的,比如:
#[rpn_fn(capture = [ctx])]
#[inline]
pub fn date(ctx: &mut EvalContext, t: &DateTime) -> Result<Option<DateTime>> {
if t.invalid_zero() {
return ctx
.handle_invalid_time_error(Error::incorrect_datetime_value(t))
.map(|_| Ok(None))?;
}
let mut res = *t;
res.set_time_type(TimeType::Date)?;
Ok(Some(res))
}
varg
用来处理输出一组同类型参数的函数,可以与 min_args
/ max_args
配合使用。
使用方式参考:
#[rpn_fn(varg, writer, min_args = 1)]
#[inline]
pub fn concat(args: &[BytesRef], writer: BytesWriter) -> Result<BytesGuard> {
let mut writer = writer.begin();
for arg in args {
writer.partial_write(arg);
}
Ok(writer.finish())
}
最适合社区贡献者的内容就是实现 Expression 函数,也就是 MySQL 中的 add_time
/ add_durtion
等函数。根据上文中提到的 Coprocessor 变迁,目前模块中需要贡献的 expr 函数状态如下:
- 完全没有实现(包括之前只有非向量化实现的函数,它们在 master 分支中已经被删除了)
- 有实现但是可以去
nullable
化 - 返回值是
BytesRef
但是没有加writer
对于完全没有实现的函数可以浏览 UCP: Migrate functions from TiDB 挑选自己感兴趣的来实现(具体的实现需要参考 TiDB 侧的逻辑),对于后面两种可以在 components/tidb_query_expr
下搜索关键词来进行修改。
此外,TiKV 在最近的迭代中加入了 UTF-8 Collation 的支持,所以部分 UTF-8 相关的函数需要重构以支持 Collation,Issue 可以参见: copr: UTF8 functions should consider collation #8986。
最后,新一期的 LFX Mentorship 计划即将开始了,本次 TiKV 提交的 Idea 包括 Coprocessor Plugin 和 TiKV compile on Windows 等,感兴趣的同学可以加入 https://tikv-wg.slack.com/ 的 #lfx-mentorship channel 以了解最新动态。
无利不起早,大家都是俗人,为什么要在工作之余花费心力为别的项目做贡献呢?
在我的角度看,为 TiKV 做贡献有这样几个好处:
- 可以直接学习生产级别的 Rust 代码,包括性能优化,接口设计,边界判断的技巧;
- 可以直接参与一个开源项目的运作,包括项目组织,计划,安排,沟通等;
- 可以了解一个分布式存储是如何实现的,相比于具体的实现,更重要的方面在于它遇到了什么样的问题,它是如何解决的,利弊都在哪里。
具体到为 TiKV Coprocesser 做贡献,我理解的好处是这样的:
- 上手门槛更低,学习曲线更平滑,沟通的成本也较低;
- Coprocesser 想象空间很大,目前正在进行很多前沿的实践,比如说基于 wasm 实现插件化等,上限很高;
- sig-copr 的同学们都超有趣,每天水 channel 都很有意思(误。
- 在贡献过程中遇到任何问题都可以来 https://tikv-wg.slack.com/ #sig-copr 频道询问(中英文皆可),可以获得勇斗恶龙的迟先生手把手指点。
- TiKV 是 Apache 2.0 协议授权的开源项目,所有代码版权均属于 CNCF
- TiKV 要求所有 commit 都进行 sign-off,请在 commit 代码时使用
git commit -s
- 运行完整的单元测试会耗时很久,可以在根目录下运行
cargo test -p tidb_query_expr
来只测试 expr 模块。 - 不需要特别深厚的 Rust 背景,只需要能看懂别人的代码并模仿即可,Reviewer 会提出修改意见,这正是学习 Rust 的大好机会(
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK