黄东旭解析 TiDB 的核心优势
668
2019-11-11
内容来源:http://mp.weixin.qq.com/s?__biz=MzI3NDIxNTQyOQ==&mid=2247490113&idx=1&sn=5322d6ae9795403e416724903c1d93ff&chksm=eb163d2bdc61b43d5e6bc91cb6d98b7f2f9ddf9816010433ada403487ff3e8521e3f5966a9dd#rd
上周我们正式宣布了 TiDB 性能挑战赛。在赛季内,通过向 TiDB、TiKV、PD 贡献代码完成指定类别任务的方式,你可以获得相应的积分,最终你可以使用积分兑换礼品或奖金。在性能挑战赛中,你首先需要完成几道 Easy 的题目,积累一定量积分后,才能开始挑战 Medium / Hard 难度的题目。
活动发布后,大家向我们反馈 TiKV 任务的资料比较少,上手难度比较高。因此本文以 TiKV 性能挑战赛 Easy 级别任务 PCP: Migrate functions from TiDB 为例,教大家如何快速又正确地完成这个任务,从而玩转“TiDB 性能挑战赛”。这个任务中每项完成后均可以获得 50 分,是积累分数从而挑战更高难度任务的好机会。既能改进 TiKV 为性能提升添砖加瓦、又能参与比赛得到积分,还能成为 Contributor——感兴趣的小伙伴们一起来“打怪”吧!
背景知识
SELECT * FROM t WHERE sqrt(col_area) > 10;
sqrt(col_area) > 10
对每一行进行求值,并根据结果对数据进行过滤,最后将过滤后的结果返回给 TiDB。为了能计算这个表达式,TiKV 必须实现与 TiDB 行为一致的 Sqrt
函数,当然 >
运算符也要提供对应的实现,这些统称为内置函数(built-in function)。FromDays
函数。BitLength
函数。LTReal
函数。FromDays
,从 TiDB 侧迁移它的代码到 TiKV 并实现在火山模型(Non-Vectorize)上,提个 PR +50 积分,再迁移到向量化模型(Vectorize)上,从而再提个 PR +50 积分。BitLength
函数,为它适配向量化模型(Vectorize)接口,提个 PR +50 积分。如何从 TiDB 迁移内置函数在火山模型上的实现
注:由于 Coprocessor 框架实现的是 Fallback 机制,不允许函数只有向量化实现而没有火山模型实现。因此,若一个内置函数完全没有在 TiKV 侧实现,请先将它在火山模型上进行实现,再迁移至向量化模型。
如何为函数适配向量化模型接口
1. 找到火山模型的实现
components/tidb_query/src/expr/scalar_function.rs
中搜索 LogicalXor
,可以发现这个函数的实现位于 logical_xor
函数:LogicalXor => logical_xor,
fn logical_xor
就可以定位到函数具体内容,位于 builtin_op.rs
(PS:不同内置函数会在不同文件中,不要照搬):pub fn logical_xor(&self, ctx: &mut EvalContext, row: &[Datum]) -> Result<Option<i64>> {
let arg0 = try_opt!(self.children[0].eval_int(ctx, row));
let arg1 = try_opt!(self.children[1].eval_int(ctx, row));
Ok(Some(((arg0 == 0) ^ (arg1 == 0)) as i64))
}
2. 翻译为向量化实现
LogicalXor
是一个二元内置函数。其中,第一个参数 children[0]
和第二个参数 children[1]
都是通过 eval_int
方式访问的,因此 LogicalXor
接受的两个参数都是 int 类型。最后,这个函数返回值是 Result<Option<i64>>
代表它计算结果也是 int 类型。可以由这些信息翻译为以下向量化计算代码,实现在 components/tidb_query/src/rpn_expr/impl_op.rs
文件中:#[rpn_fn]
#[inline]
pub fn logical_xor(arg0: &Option<Int>, arg1: &Option<Int>) -> Result<Option<Int>> {
// TODO
}
注: Int
是i64
的 Type Alias。你既可以写Int
也可以写i64
,不过更推荐Int
一些。你可以从 这里 找到所有的 Type Alias。eval_xxx
函数与类型的对应关系如下表所示。
火山模型函数名 | 对应参数类型 | 参数类型别名 |
eval_int | Int | i64 |
eval_real | Real | ordered_float::NotNan<f64> |
eval_decimal | Decimal | |
eval_bytes | Bytes | Vec<u8> |
eval_time | DateTime | |
eval_duration | Duration | |
eval_json | Json |
logical_xor
是一个接受两个参数且两个参数都是 Int 类型的函数,返回 Int,是不是非常直观呢?另外我们使用 None
来代表 SQL 中的 NULL
值,因此函数参数及返回值都是 Option<Int>
类型。None
/ Some
的情况),这个函数就算完成了:#[rpn_fn]
#[inline]
pub fn logical_xor(arg0: &Option<Int>, arg1: &Option<Int>) -> Result<Option<Int>> {
Ok(match (arg0, arg1) {
(Some(arg0), Some(arg1)) => Some(((*arg0 == 0) ^ (*arg1 == 0)) as i64),
_ => None,
})
}
fn logical_xor_vector_scalar(arg0: []Int, arg1: Int) -> []Int {
let r = vec![];
for i in 0..n {
r.push( logical_xor(arg0[i], arg1) );
}
return r;
}
fn logical_xor_scalar_vector(arg0: Int, arg1: []Int) -> []Int {
let r = vec![];
for i in 0..n {
r.push( logical_xor(arg0, arg1[i]) );
}
return r;
}
fn logical_xor_vector_vector(arg0: []Int, arg1: []Int) -> []Int {
let r = vec![];
for i in 0..n {
r.push( logical_xor(arg0[i], arg1[i]) );
}
return r;
}
fn logical_xor_scalar_scalar(arg0: Int, arg1: Int) -> []Int {
let r = vec![];
for i in 0..n {
r.push( logical_xor(arg0, arg1) );
}
return r;
}
#[rpn_fn]
过程宏中。3. 增加函数入口
logical_xor
的实现。这一步很简单,修改 components/tidb_query/src/rpn_expr/mod.rs
文件中的 map_expr_node_to_rpn_func
函数,增加一个对应关系即可:ScalarFuncSig::LogicalXor => logical_xor_fn_meta(),
_fn_meta
后缀,从而用上 #[rpn_fn]
过程宏自动生成的向量化版本函数实现。不要问为什么,问就是约定 :D4. 撰写单元测试
ScalarFuncSig::LogicalXor
可以找到火山模型下的该函数单元测试:#[test]
fn test_logic_op() {
let tests = vec![
...
(
ScalarFuncSig::LogicalXor,
Datum::I64(1),
Datum::I64(1),
Some(0),
),
(
ScalarFuncSig::LogicalXor,
Datum::I64(1),
Datum::I64(0),
Some(1),
),
(
ScalarFuncSig::LogicalXor,
Datum::I64(0),
Datum::I64(0),
Some(0),
),
(
ScalarFuncSig::LogicalXor,
Datum::I64(2),
Datum::I64(-1),
Some(0),
),
(ScalarFuncSig::LogicalXor, Datum::I64(0), Datum::Null, None),
(ScalarFuncSig::LogicalXor, Datum::Null, Datum::I64(1), None),
];
let mut ctx = EvalContext::default();
for (op, lhs, rhs, exp) in tests {
let arg1 = datum_expr(lhs);
let arg2 = datum_expr(rhs);
……
}
}
Option<Int>
,配上 RpnFnScalarEvaluator
进行执行,代码如下:#[test]
fn test_logical_xor() {
let test_cases = vec![
(Some(1), Some(1), Some(0)),
(Some(1), Some(0), Some(1)),
(Some(0), Some(0), Some(0)),
(Some(2), Some(-1), Some(0)),
(Some(0), None, None),
(None, Some(1), None),
];
for (arg0, arg1, expect_output) in test_cases {
let output = RpnFnScalarEvaluator::new()
.push_param(arg0)
.push_param(arg1)
.evaluate(ScalarFuncSig::LogicalXor)
.unwrap();
assert_eq!(output, expect_output);
}
}
5. 运行测试
make dev
EXTRA_CARGO_ARGS="test_logical_xor" make dev
PCP #5751
指明这个 PR 对应的性能挑战赛题目,不然合了是得不到积分的。另外我们鼓励每个 PR 都专注于做一件事情,所以请尽量不要在同一个 PR 内迁移或实现多个内置函数,否则只能得到一次 50 积分。6. 运行下推测试
@sre-bot /run-integration-copr-test copr-test=pr/10
运行下推测试。如果你的函数之前已经在 push-down-test/functions.txt 列表中了,可以直接回复 @sre-bot /run-integration-copr-test
运行下推测试。7. 在 TiDB 中增添签名映射
ScalarFuncSig
之间的映射关系。后者会在测试中产生 “TiDB internal error (unspecified PbCode)” 错误,非常容易辨别。如果出现了这种情况,大家可以参考 https://github.com/pingcap/tidb/pull/12864 的做法,为 TiDB 提 PR 增添相应内置函数的 PbCode 映射。添加完毕之后,可以在 TiKV PR 中回复 @sre-bot /run-integration-copr-test copr-test=pr/X tidb=pr/Y
(其中 X
是你提的 copr-test PR 号,Y
是你提的 TiDB PR 号)进行联合测试。完成!
直播预告
为了帮助大家快速上手玩转性能挑战赛,我们准备了一场线上直播,为大家介绍参赛流程和规范。如果大家对比赛有疑问,也可以在直播过程中讨论哦~
时间:2019-11-13 周三 21:00
直播链接:https://zoom.us/j/8167688561
下载最新版 Zoom 软件,准点加入会议室即可
Topic:30 分钟成为 Rust 明星项目的 Contributor!TiDB 性能挑战赛等你来战。
Speaker:龙恒,TiDB/TiKV 研发工程师。
Content:TiDB 是一个开源的分布式关系型数据库,其存储层 TiKV 目前是 CNCF 的孵化项目,主要由 Rust 语言编写,它支持跨行 ACID 事务,同时实现了自动水平伸缩、数据强一致性、跨数据中心高可用和云原生等重要特性。
目前 TiDB 性能挑战赛 正在火热进行中,在 TiKV 方面,有诸多从 Easy 到 Hard 的任务,大家可以通过做任务,“打怪升级”赢得相应的积分,并在赛季结束后用积分兑换奖品或奖金,同时还能成为 TiKV 项目的 Contributor!
本次 Topic 将为大家全面介绍参赛流程和规范,解答大家对比赛的疑惑,帮助大家快速上手,玩转性能挑战赛。期待 Rust 语言社区的伙伴们加入。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。