cockroachdb mysql_CockroachDB 的 key 空间是如何组织的?

CockroachDB 本身可以认为是一个无限大的有序 map,他本身没有采用一致性哈希的方式,因为这种 scaling 的方式对 key 的 scan 不友好,所以设计了一个类似操作系统页表的分层索引的结构。

keyspace 的组织结构

逻辑上,这张表包含了一些保留的系统 key/value 对,保存在真正的用户数据之前,并且由 SQL 子系统管理。\x02: 以 \x03结尾的 metadata range. meta1 key.

...

\x02: 以 \x03结尾的 metadata range. meta1 key.

\x03: 以 结尾的 range metadata. meta2 key.

...

\x03: 以 结尾的 range metadata. meta2 key.

\x04{desc,node,range,store}-idegen: ID generation oracles for various component types.

\x04status-node-: store 的 metadata.

\x04tsd: 时序数据的 key

: 用户的 key,实际上是由 SQL 子系统管理的,不纯粹是用户插入的数据,比如一些表的数据,事务的数据也是 SQL 子系统在这段范围内管理的。

整体结构如下图

注意:我们把 range 的最后一个key作为range的索引,这是因为RocksDB迭代器仅支持Seek()接口,其功能类似Ceil()。使用range开始的Key将会使Seek()函数要找两次才能找到key所属于的 range,而用结尾做划分的话,直接找到一个比自己大的key作结尾的 range 就能找到所属的 range 了。

range 的元数据

一个 range 默认大小接近 64M (2\^26 B),要支持 1PB(2^50 B) 的逻辑数据,大概需要 2^(50 - 26) = 2^24 个range 的元数据。一个元数据的最大合理上限大约是 256B(其中3*12字节保存三元组节点位置信息,余下220字节保存range本身的key)。 2^24 range *(2^8 B)大概需要4G (2^32 B)字节存储,这太大而不能在机器间进行复制。我们的结论是,对于大型集群部署,range 的元数据必须是分布式的。

面对分布的元数据,为了保持 key 检索的相对高效,我们把所有顶层的元数据保存在单一 range 里(第一个range)。这些顶层元数据的 key 被称为 meta1 key,并加上前缀以使得它们排序时在 key 空间的起始位置。前述给定一个元数据大小是256字节,一个64M的range可以保存 64M/256B=2^18 个range元数据,总共可以提供64M *(2^18) =16T的存储空间。为了提供上述1P的存储空间,我们需要两层寻址,第一层用来定位第二层的地址,第二层用来保存用户数据。采用两层寻址,我们可以寻址 2^(18 + 18) = 2^36 个range;每个 range 寻址2^26 B ,则总共可寻址 2^(36+26) B = 2^62 B = 4E 的用户数据。

对于一个给定的用户地址 key1 ,其对应的 meta1 记录位于meta1空间中**key1**的后驱 key 中。因为meta1空间是稀疏的,后驱key被定义为下一个存在的key。meta1记录标识了包含*meta2*记录的range,查找方式相同。meta2记录标识了包含**key1**的range,查找过程与前面一样也采用了相同方法(参见下面的例子)。

key的元数据被增加了前缀:\x02 (meta1) 、\x03 (meta2);前缀 \x02 和 \x03是为了得到期望的排序结果。这样,key1的meta1记录就被保存在 \x02的后驱key中。

下面的例子展示了有三个range的map目录结构。省略号表示补充的填满数据整个range的key/value对。为了清晰,例子使用meta1和meta2来指代前缀\x02和\x03。除了有切分 range 时需要更新range的元数据,需要知道元数据的分布信息,range元数据本身不需要特殊对待或者自举。

Range 0 (冗余在 dcrama1:8000, dcrama2:8000,

dcrama3:8000)meta1\xff: dcrama1:8000, dcrama2:8000, dcrama3:8000

meta2: dcrama1:8000, dcrama2:8000, dcrama3:8000

meta2: dcrama4:8000, dcrama5:8000, dcrama6:8000

meta2\xff: dcrama7:8000, dcrama8:8000, dcrama9:8000

...

:

Range 1 (冗余在 dcrama4:8000, dcrama5:8000,

dcrama6:8000)...

:

Range 2 (冗余在 dcrama7:8000, dcrama8:8000,

dcrama9:8000)...

:

如果数据量比较小的话,元数据和数据都会凑在一个 range 里面,对应如下表所示。

Range 0 (located on servers dcrama1:8000, dcrama2:8000,

dcrama3:8000)*meta1\xff: dcrama1:8000, dcrama2:8000, dcrama3:8000

meta2\xff: dcrama1:8000, dcrama2:8000, dcrama3:8000

:

...

以上都是复用了 range0 的例子,在 range0 需要存的索引比较小的时候会存储一些二级索引和真实数据。

下面这个例子是在数据量比较大的时候,range 指代了被用于冗余该range的节点的集合。

Range 0meta1: Range 0

meta1\xff: Range 1

meta2: Range 1

meta2: Range 2

meta2: Range 3

...

meta2: Range 262143

Range 1meta2: Range 262144

meta2: Range 262145

...

meta2\xff: Range 500,000

...

:

Range 2...

:

Range 3...

:

Range 262144...

:

Range 262145...

:

注意:选择range262144只是一个近似值。通过单一元数据range可寻址的range的实际数量依赖于key的大小。如果努力保持key的尺寸越小,则可寻址的range越多,反之亦然。

如下图所示

从上面的例子可以清楚的看到,至多3次key寻址就可获取 对应的值:找到一个比 meta1大的meta1,获取对应的 range

找到一个比 meta2大的meta2,获取对应的 range

从 range 中找到,获取 value

对于小 Map,可以在 Range 0 上一次 RPC 调用内完成所有检索。包含 16T 以下的 Map 需要 2 次检索。客户端缓存 range 元数据的各层,我们期望客户端各自都具有很高的数据局部性。如果在一次检索中,cache 中的索引没有命中,就要重新进行一次检索并且更新缓存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值