如何为 Databend 添加新的系统表
作者:尚卓燃(PsiACE)
澳门科技大学在读硕士,Databend 研发工程师实习生
Apache OpenDAL(Incubating) Committer
Databend 的系统表x位于 query/storage 目录下,当然,如果因为一些特殊的构建原因无法放在这个位置的话,也可以考虑临时放到 service/databases/system
这个目录(不推荐)。
系统表的定义主要关注两个内容:一个是表的信息,会包含表名、Schema 这些;另一个就是表中数据的生成/获取。刚好可以对应到 SyncSystemTable
和 AsyncSystemTable
这两个 Trait 中的 get_table_info
和 get_full_data
。到底是同步还是异步,取决于在获取数据时,是否涉及到异步函数的调用。
实现
本文将会以 credits
表的实现为例,介绍 Databend 系统表的实现,代码位于 https://github.com/datafuselabs/databend/blob/main/src/query/storages/system/src/credits_table.rs 。credits
会返回 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
用于描述表的结构,需要使用 TableSchemaRefExt
和 TableField
来创建,字段名字和类型取决于表中的数据。
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
。
接下来,则是实现 SyncSystemTable
,SyncSystemTable
除了需要定义 NAME
之外,还需要实现 4 个函数 get_table_info
、get_full_data
、get_partitions
和 truncate
,由于后两个有默认实现,大多数时候不需要考虑实现自己的。(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(()) }
关于 Databend
Databend 是一款开源、弹性、低成本,基于对象存储也可以做实时分析的新式数仓。期待您的关注,一起探索云原生数仓解决方案,打造新一代开源 Data Cloud。
👨💻 Databend Cloud:https://databend.cn
📖 Databend 文档:https://databend.rs/
💻 Wechat:Databend

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
看华为云Serverless 4大特性如何让软件架构更丝滑
摘要:Serverless可以看作是一种云计算服务模型,它允许开发者在不需要管理服务器的情况下通过事件驱动的方式运行应用代码。 软件架构的发展从原先的单体架构到近十几年的微服务架构,再到现在新兴的Serverless架构。单体架构通常把应用的逻辑和功能耦合在一起,部署在BMS裸金属机或VM上,耦合模式使得一些通用功能或通用逻辑无法灵活复用,经常出现重复造轮子的现象,架构整体上是相对封闭的。 微服务时代对应用做了拆分,组件服务化,诞生了一系列优秀的设计原则,如接口标准化、CS/CD自动化,使得应用初步具备了弹性和自动容错的能力。 在Serverless的框架下,应用的粒度更细,例如以函数为粒度进行管理和开发迭代,应用通过事件驱动的方式触发运行。 Serverless可以看作是一种云计算服务模型,它允许开发者在不需要管理服务器的情况下通过事件驱动的方式运行应用代码,主要解决资源托管、调度、运维管理等一系列平台型问题,可以看作是DevOps的进一步延伸。 从传统微服务向Serverless演进的过程中,资源管理的边界逐步上移。在微服务时代,虚机/容器时代,开发者发布应用,需要管理虚拟化层或更...
- 下一篇
如何说服技术老大用 Redis ?
这个问题很微妙,可能这位同学内心深处,觉得 Redis 是所有应用缓存的标配。 缓存的世界很广阔,对于应用系统来讲,我们经常将缓存划分为本地缓存和分布式缓存。 本地缓存 :应用中的缓存组件,缓存组件和应用在同一进程中,缓存的读写非常快,没有网络开销。但各应用或集群的各节点都需要维护自己的单独缓存,无法共享缓存。 分布式缓存:和应用分离的缓存组件或服务,与本地应用隔离,多个应用可直接共享缓存。 1 缓存的本质 我们常常会讲:“加了缓存,我们的系统就会更快” 。 所谓的“更快”,本质上做到了如下两点: 减小 CPU 消耗 将原来需要实时计算的内容提前算好、把一些公用的数据进行复用,这可以减少 CPU 消耗,从而提升响应性能。 减小 I/O 消耗 将原来对网络、磁盘等较慢介质的读写访问变为对内存等较快介质的访问,从而提升响应性能。 假如可以通过增强 CPU、I/O 本身的性能来满足需求的话,升级硬件往往是更好的解决方案,即使需要一些额外的投入成本,也通常要优于引入缓存后可能带来的风险。 从开发角度来说,引入缓存会提高系统复杂度,因为你要考虑缓存的失效、更新、一致性等问题。 从运维角度来说,缓...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Red5直播服务器,属于Java语言的直播服务器
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库