首页 > 编程语言 >Databend 源码阅读: Meta-service 数据结构

Databend 源码阅读: Meta-service 数据结构

时间:2023-12-20 10:56:58浏览次数:45  
标签:service Meta pub 查询 Databend 源码 key Table ID

作者:张炎泼(XP)

Databend Labs 成员,Databend 分布式研发负责人

https://github.com/drmingdrmer

引言

Databend 是一款开源的云原生数据库,采用 Rust 语言开发,专为云原生数据仓库的需求而设计。

  1. 面向云架构:Databend 是完全面向云架构的数据库,可以在云环境中灵活部署和扩展 简介 | Databend 内幕大揭秘
  2. 弹性扩缩容能力:Databend 提供秒级的弹性扩缩容能力,可以根据需求快速增加或减少计算资源。
  3. 存算分离:Databend 实现了存储和计算的分离,可以独立增加计算节点而无需进行数据迁移,提高了计算资源的利用效率。
  4. 共享存储:Databend 使用共享存储,可以方便地接入各种数据源,并支持 SQL 查询。
  5. 弹性多租户隔离:Databend 的 Meta Service 层提供了弹性的多租户隔离的服务,可以满足不同用户的需求 Databend:新一代云原生数仓的架构与展望 - 知乎

它的主数据以对象存储的形式存储在云端,而关键的元数据则托管在专用的 Meta Service 中。

本文旨在介绍 Meta Service 的基本数据结构组织方式,并阐述 Databend 中不同数据结构的功能及其相互关系。通过解读源码,我们展示了现代云数据仓库的构建细节,并探讨如何利用 Meta Service 的元数据来高效地操作 Databend,例如管理数据表(table)。

本文面向希望使用 Databend 数据库的用户,特别是那些愿意深入了解其内部机制以便更好地利用该数据库的用户。同时,本文也为有兴趣参与 Databend 开源项目贡献的人提供了宝贵信息。

Databend 整体结构

Databend 是一个开源的、完全面向云架构的新一代云数仓,它的系统架构包括以下组件:客户端、查询端点、元服务端点和 KV 存储。

  1. 客户端:

    • Databend 的客户端是用户与 Databend 系统进行交互的接口。
    • 客户端可以使用各种编程语言(如 Rust、Python 等)编写,以便用户可以根据自己的需求进行数据操作和查询。
  2. Databend endpoint:

    • 它是 Databend 系统中处理查询请求的组件。
    • 它接收来自客户端的查询请求,并将其转发给适当的计算节点进行处理。
    • 查询端点还负责将计算节点返回的查询结果返回给客户端。
  3. 元服务 meta-service:

    • 元服务端点是 Databend 系统中负责管理元数据的组件。
    • 它存储和管理表结构、用户认证信息等元数据。
    • 元服务端点还提供了事务性支持,用于保证数据的一致性和可靠性。

下面我们以一个简单的 sql select * from <database_name>.<tabe_name>
的执行来介绍元数据的作用以及 Databend 如何管理和使用元数据。

Databend 中的元数据 key

在 Databend 这个数据处理系统中,我们掌握了三种关键的方式来追踪和管理数据资源,确保我们可以精确地操作这些资源。

首先,外部用户通常会使用一串可读的字符串名称来指代一个对象,比如一个数据库(database)或数据表(table)。这些名称可以随时更改,并且这样的变动不会影响到操作的准确性。在 Databend 系统的内部,我们通常需要多个字符串字段来唯一确定一个资源。例如,为了指定一个数据库,我们需要用到 <tenant>.<database_name> 这样的结构。我们把这些信息打包成一个结构体,比如 DatabaseNameIdent

pub struct DatabaseNameIdent {
    pub tenant: String,
    pub db_name: String,
}

其次,对于系统内部来说,每一个资源都会被分配一个独一无二的整数ID来进行标识。这种 ID,我们一般以 xxxID 的形式出现,仅在内部流转使用。Databend 里的 ID 是对用户透明的,也就是说,用户通常不会接触到这些 ID。在处理一个 SQL 请求的最初阶段,我们会把用户提供的字符串名称转换成这样的内部 ID 来使用,以此来避免名称变更可能带来的混淆。一个重要的规则是,一旦分配,这个对象的 ID 就永远不会变更,并且在全局范围内绝不会出现两个不同对象拥有相同的 ID,不论这些对象是否类型相同。

例如 DatabaseId 结构体:

pub struct DatabaseId {
    pub db_id: u64,
}

你可以看到,它实质上只是对一个 u64 类型的封装。我们这样做的目的是为了让不同类型的 ID 能够拥有不同的行为或属性。

最后,我们还有一种结合了 sequence number 的 ID,用于标记资源的具体某个版本。这个序列号代表了资源的每一次变动。在 Databend 的内部,我们不仅使用唯一的 ID 来标识一个资源,还会进一步使用序列号来追踪该资源的特定变化。比如说,我们用表 ID 来唯一确定一个数据表。一个表的特定版本就是由其表 ID 加上序列号 seq 共同构成的。

这种结合 ID 和序列号的唯一标识符在 Databend 的内部操作中非常常见。当需要对元数据进行更新时,我们总是针对某个特定版本的资源进行操作,这样可以保证我们的修改是以原子方式进行的,即通过 CAS(Compare-And-Swap)操作来确保数据一致性。

例如,TableIdent 结构体就是用于操作特定版本的表的关键结构体。它确保我们的操作仅限于特定版本的 table:如果在我们读取数据和写入数据之间,table 的版本发生了变化(也就是 seq 改变了),我们的修改就会被拒绝。

pub struct TableIdent {
    pub table_id: u64,
    pub seq: u64,
}

通过这三种关键的识别方式,Databend 能够高效而准确地管理其数据资源。

Databend 元数据 KV 存储关键 trait

在 Databend 的世界里被称作 meta-service 的组件,可以把它想象成一个通过 gRPC 接口(一种编程接口)来访问的 KV-store。而这个 KV-store 的接口是通过两个 trait 来定义的。

Trait kvapi::Key

在 Databend 中,我们对键值存储中的键(key)有着严格的结构定义。我们只使用结构化的键,比如 DatabaseNameIdent,将它序列化为 KV-store 的 String key 的动作是由 trait kvapi::Key 来定义的:

pub trait Key
{
    const PREFIX: &'static str;
    fn to_string_key(&self) -> String;
}

通过这个特性 kvapi::Key,Databend-query 会把结构化的键(比如DatabaseNameIdent 转换成字符串形式的 key,例如__fd_database/<tenant>/<database_name>。然后,它会通过 meta-service 暴露的 KVApi 来查询对应名称的数据库 ID。

类似的,DatabaseId 类型的键会被序列化为字符串类型的__fd_database_by_id/<database_id> key,以便在 KV-store 中检索 DatabaseMeta 数据结构。

Trait KVApi

kvapi::Key 定义了 KV-store 的 key 的行为,
KVApi 特性定义了访问 KV-store 的 API:

它提供了非常基础的 key-value 修改和查询接口。所有更复杂的其他的元数据访问都是建立在这个 KVApi 之上的:

pub trait KVApi: Send + Sync {
    async fn upsert_kv(&self, req: UpsertKVReq) -> Result<UpsertKVReply, Self::Error>;
    async fn get_kv(&self, key: &str) -> Result<GetKVReply, Self::Error>;
    async fn mget_kv(&self, keys: &[String]) -> Result<MGetKVReply, Self::Error>;
    async fn list_kv( &self, prefix: &str) -> Result<BoxStream<'static, Result<StreamItem, Self::Error>>, Self::Error>;
}

简而言之,Databend 的元数据管理非常依赖于这些关键的特性来维持其结构化的存储和高效的数据检索方式,从而确保整个系统的数据管理既有序又高效。

Databend 中的元数据 value 简介

在 Databend 中,不仅 key 需要结构化定义,value 也同样需要严格的结构化定义。想象一下,我们的数据仓库是一个大柜子,里面的每个抽屉都用字符串标记,而抽屉里面放的都是二进制数据包。当我们想要存取这些数据包时,我们需要确切地知道每个数据包的内容和结构,这就要求我们用一个独特的结构体来代表这个值的结构,并在必要时将值序列化成二进制数据存储到数据仓库中。

在 Databend 中,我们使用 trait FromToProto 来定义存储在 KV-store 中的 value。简单来说,就是使用 protobuf 技术来帮助我们把一个结构体转换成一串 Vec<u8>(也就是二进制数据),以及把这串数据再转换回原来的结构体。

trait FromToProto {
    type PB;

    fn get_pb_ver(p: &Self::PB) -> u64;
    fn from_pb(p: Self::PB) -> Result<Self, Incompatible>;
    fn to_pb(&self) -> Result<Self::PB, Incompatible>;
}

这个 FromToProto trait 就像是一个翻译官,它描述了如何把我们用 Rust 语言写的数据类型转换成 protobuf 定义的数据类型,以及反过来的转换。

  • type PB: 代表了与实现了 FromToProto trait 的 Rust 类型相对应的 protobuf 类型。每次转换时,我们都会把 Rust 结构体转换成相应的 protobuf 格式,并处理可能出现的兼容性问题,最终将 protobuf 序列化成二进制数据存储在 KV-store 中。

  • fn get_pb_ver(p: &Self::PB) -> u64:这个函数用来获取 protobuf 版本号,帮助我们在加载一个值之前处理版本兼容性问题。

  • fn from_pb(p: Self::PB) -> Result<Self, Incompatible>:这个方法把 protobuf 类型转换回Rust 类型。如果 protobuf 消息与 Rust 类型不匹配,转换可能会失败,此时会返回一个 Incompatible 错误。

  • fn to_pb(&self) -> Result<Self::PB, Incompatible>:这个方法则是将 Rust 类型转换成protobuf 类型。和 from_pb 一样,如果存在不兼容问题,这个转换也可能失败。

在 Meta-service 中,存储的值有不同的类型,比如 DatabaseMetaTableMeta,它们都实现了 FromToProto

struct DatabaseMeta {
    engine: String,
    options: BTreeMap<String, String>,
    // ...
}
pub struct TableMeta {
    pub schema: Arc<TableSchema>,
    // ...
}

总而言之,Databend 系统中的每个值都是精心设计的结构体,用来保持数据的一致性和准确性。通过 FromToProto trait 的转换机制,我们能够保证数据在存储和检索时的完整性和可用性。这样的设计确保了系统在处理大量数据时的高效运行。

简单 SQL 执行背后的元数据管理:Databend 中的 Database 查询深入解析

想象一下,你对着你的电脑或手机说:“给我看看这个数据库里都有什么。” 你其实就发起了一个 SQL 查询命令。在Databend 这个大数据平台里,当这样的一个命令来临时,后台发生了一系列精妙的操作。今天,就让我们走进Databend 的世界,看看执行一个简单的select * from database_name.table_name SQL 命令背后,元数据是如何被使用和管理的。

查询 Database

Client            Databend-query                Meta-service
+---------------->|                             |
         SQL      |                             |
                  +---------------------------->|
                  |  DatabaseNameIdent          |
                  |                             |
                  |<----------------------------+
                  |                 DatabaseId  |
                  |                             |
                  +---------------------------->|
                  |  DatabaseIdent              |
                  |                             |
                  |<----------------------------+
                  |               DatabaseMeta  |
                  |                             |
                 ...

步骤一:提取查询信息

从 SQL 中提取tenantdb_name, 构造 DatabaseNameIdent, 用于对 meta-service 的查询.

pub struct DatabaseNameIdent {
    pub tenant: String,
    pub db_name: String,
}

步骤二:首先通过name查询出id:

然后通过 trait kvapi::Key 将这个结构化的 key 序列化成 String key:
__fd_database/<tenant>/<database_name>, 再通过 trait KVApi 定义的接口访问 meta-service, 获得 DatabaseId

pub struct DatabaseId {
    pub db_id: u64,
}

DatabaseId 用于唯一定位一个 database 对象, 即使数据库改名了,这个 ID 也不会变。

步骤三:通过 Id 获取 Database 的档案

有了 ID,现在查询服务就可以直接向元数据服务请求数据库的个人档案,也就是 DatabaseMeta

struct DatabaseMeta {
    engine: String,
    options: BTreeMap<String, String>,
    // ...
}

这一步也用类似于第一步的方式从 meta-service 中查找:

现在我们可以获得这个 database 对象的全局唯一版本了, 也就是 DatabaseId
DatabaseMetaseq 组成的 DatabaseIdent :

步骤四:确保版本正确

为了保证信息的新鲜和准确,Databend 会用一个 sequence number 来标记数据库的版本。这个 seq 和 ID 一起,组成了一个DatabaseIdent,确保了信息是最新的且唯一的。

pub struct DatabaseIdent {
    pub db_id: u64,
    pub seq: u64,
}

在后续的操作中, 如果要对 Database 修改, 只会去修改这个版本(seq)的对象,
从而实现并发保护。

最终:打包并送回

最后, 将 DatabaseIdent, 和 database 自身的信息
DatabaseMeta, 封装到一个 DatabaseInfo的结构体中返回给调用者.

pub struct DatabaseInfo {
    pub ident: DatabaseIdent,
    pub meta: DatabaseMeta,
}

这整个过程就像一个精心编排的接力赛,每一步都为了保证信息的准确性和安全性。通过这样的机制,Databend 确保了即使在大量并发请求中,每一个数据的访问都是可靠和一致的。

就这样,你的一个简单查询,背后其实经过了一系列复杂的元数据传递和管理。现在,你已经了解了 Databend 中一个 SQL 查询背后的故事,下次当你看到数据库中的数据时,也许会对这背后的智慧有一个全新的认识。

Table 查询的技术解析

查询 Table 的过程,在 Databend 中与查询 Database 的过程非常相似,但有其特有的细节。在查询 Table 时,我们首先利用从上一步获取的 DatabaseId 信息。

由于每个 Table 都隶属于某个 Database,元数据服务中存储 Table 的 key 会以 DatabaseId 作为其前缀,格式如下:

__fd_table/<database_id>/<table_name>

通过这个键,我们可以检索到对应的 TableId ,这是 Table 的唯一标识符。进而,我们利用 TableId 来获取 Table 的元数据(TableMeta)以及相关的序列号(seq)。序列号在此上下文中代表 Table 版本的唯一标识。

接下来,我们将 TableId 和seq结合,构造出一个内部使用的唯一版本标识(TableIdent):

pub struct TableIdent {
    pub table_id: u64,
    pub seq: u64,
}

TableIdent代表了Table的特定版本,这对于确保一致性和处理并发更新至关重要。

最终,我们将 TableIdentTableMeta 包装在 TableInfo 结构中,返回给调用者:

pub struct TableInfo {
    pub ident: TableIdent,
    pub meta: TableMeta,
}

调用者接收到 TableInfo 后,可以通过 TableMeta 中的信息进行数据读取操作,或者基于 TableIdent 指定的版本对 Table 进行更新操作。这是一个确保数据一致性和版本控制的重要流程,允许 Databend 在处理大规模并发数据操作时维护高效率和准确性。

总结

在本文中,我们深入探讨了在 Databend 中查询 Table 的内部工作机制。通过这一过程,我们了解了如何利用DatabaseId 来定位 Table,并使用 TableIdTableMeta 来获取和操作 Table 的元数据。我们还学习了如何通过seq来追踪 Table 的版本,以及如何将这些信息封装在 TableIdentTableInfo结构中,以支持数据的读取和更新操作。

这一流程不仅体现了 Databend 强大的数据管理能力,也展示了其如何优雅地处理并发和维护数据一致性。无论是数据库管理员还是开发人员,理解这些基础的原理都是至关重要的,因为它们是构建高效和可靠数据服务的基石。

随着数据管理技术的不断进步,我们期待见证 Databend 及其查询机制如何继续演进,以满足不断增长的性能和可伸缩性需求。希望本文能够帮助你更好地理解 Table 查询的过程,为你在数据管理和应用开发的旅程中提供支持。

感谢你的阅读,希望你能在你的项目中应用这些知识,并且期待你对 Databend 和数据查询有更深入的探索。如果你有任何问题或想法,欢迎在评论中分享,让我们一起推动技术界的知识共享和进步。

关于 Databend

Databend 是一款开源、弹性、低成本,基于对象存储也可以做实时分析的新式数仓。期待您的关注,一起探索云原生数仓解决方案,打造新一代开源 Data Cloud。

标签:service,Meta,pub,查询,Databend,源码,key,Table,ID
From: https://www.cnblogs.com/databend/p/17916021.html

相关文章

  • iTOP-RK3568开发板实时系统编译,Preemption系统/Xenomai系统编译,获取Linux源码包
    1获取Linux源码包编译环境说明:本手册使用的是迅为提供的编译环境ubuntu20.04,在网盘资料“iTOP-3568开发板\03_【iTOP-RK3568开发板】指南教程\05_NPU开发配套资料\03_RKNN_Toolkit2环境搭建\01课程用到的资料\01_初始Ubuntu20虚拟机”路径下。这里的ubuntu20默认开发......
  • LANDSAT LC08 C02 T1_L2 metadata dictory 元数据字典
    LANDSAT/LC08/C02/T1_L2metadatadictory元数据字典2023年12月20日星期三byxrkvarimage=ee.ImageCollection('LANDSAT/LC08/C02/T1_L2').first();print("image",image);vardic=image.toDictionary();print("dic",dic)ALGORITHM_SOURCE_SURFACE_REFLE......
  • springboot016高校学生党员发展管理系统(vue,毕业设计,附数据库和源码)
    1绪论1.1研究背景1.2研究现状1.3研究意义2系统开发工具介绍2.1Springboot2.2VUE框架2.3Mysql数据库3可行性分析3.1技术的可行性3.2经济的可行性3.3操作可行性 4系统需求分析4.1系统功能需求管理系统的主要功能结构图呈现如下:图4.1高校发展学生党员管理系统功能结构......
  • SourceBrowser 的源码流程(待继续细化)
    先读取sln项目里的文件,csorpj,在读取cs等文件,创建输出文件夹会生成一个类似集合对象,里面有你的代码文件,然后遍历集合,找出代码文件如xx.cs文件,然后读取cs文件里的代码,还有一些roslyn读取语法根,获取语义模型异步,获取文档范围rangs部分该不太确定具体意义根据cs文......
  • SSO单点登录源码解析
    单点登录:用户在单点登录成功后,会访问某一平台模块,1. 向客户端发送一个请求(免密登录接口),请求客户端接口会携带一个ticket,类似于:http//:ip:port:xxx?ticket=xxxx1. 这个请求会被客户端配置的EKPSSOCLIENT的过滤器拦截,读取配置文件sso-config.properties,获取过滤器链(CASURLFilter、Us......
  • 任务调度处理系列之 Spring源码分析-【SchedulingConfigurer实现原理】转
     一、可能的场景在做业务平台的时候我们经常会遇到,某些跟时间打交道的需要修改状态,比如说在时间区间之前,属于未生效状态,区间之内属于有效期,区间之后,属于过期,或者需要每天每周每月,甚至是年为单位的做一些固定的操作。通过定时任务可以通过开启定时任务来完成这些需求。我做合......
  • 构建可扩展的网校平台:在线教育系统源码设计与架构最佳实践
    随着科技的不断发展,在线教育系统在教育领域扮演着越来越重要的角色。本文将深入探讨如何构建一个可扩展的网校平台,重点关注在线教育系统的源码设计和架构最佳实践。 一、引言在当前信息时代,教育已经超越了传统的教学方式,转向更加灵活和便捷的在线教育平台。构建一个可扩展的网校平......
  • 启动微服务报错:java.lang.IllegalStateException: Service id not legal hostname (se
    Order微服务通过opneFeign调用storage和account时报错原因分析:服务名称不能有下划线,可以使用中划线,Springcloud无法识别下划线,将下划线改为中划线即可注意:这个改了之后,你在进行远程调用的时候,接口对应的方法接口也需要相应改变【分布式开发,一个微服务模块修改了,很可能......
  • IoC源码
    主心骨:解析配置定位与注册对象注入对象一、Bean与BeanDefinition1、全局掌握核心接口和类解决了关键问题:将对象之间的关系转而用配置来管理依赖注入:依赖关系在Spring的IoC容器中管理通过把对象包装在Bean中以达到管理对象和进行额外操作的目的Bean是Spring的一等公......
  • Docker启动Nacos报错:Nacos Server did not start because dumpservice bean construct
    一、表象重启服务器之后Docker运行Nacos容器,启动成功,但是外网无法访问。查看了一下Nacos启动日志(dockerlogsnacos容器名)二、分析很明显是数据库配``置问题。。如果是数据库配置的问题,可以着重检查以下信息尤其是MySQL内网Host,查询方式见Docker安装Nacos三、解决我已......