yugabytedb之DocDB存储层

2022-04-11 00:00:00 数据 集合 时间 排序 标量

yugabytedb之DocDB存储层

文章以下内容借鉴于官网https://docs.yugabyte.com/latest/architecture/docdb/performance/#scan-resistant-block-cache

一旦数据被复制到多数派节点的Yugabytedb tablet peer上,就可以将数据应用到tablet peer本地的DocDB文档存储中。

存储模型

存储层采用的是key-object/document模型,存储模型如下图所示:

key和对应的document描述如下:

DocDB key

 DocDB document模型中的key由一个或多个hash组成,后面跟着零个或多个有序的组件组成。这些组件按照数据类型指定的排序方式进行顺序存储,每个key都支持升序或降序排序。

DocDB value

DocDB document模型中的values可以是:

基本类型:比如int32,int64,doouble,text,timestamp等。

非基本类型(有序maps):这些对象将标量key映射到values,这些值可以是标量映射或排序映射。

该模型允许多级嵌套,类似于Json格式。其他数据结构(比如list、sorted set等)使用DocDB的特殊编码方式,object类型。在DocDB中,每次更新的时间戳都会被记录,这样就可以恢复document在过去任何时候的状态。只要快照上没有读取旧值的事务,覆盖或删除的数据版本就会被垃圾收集。

Encoding documents

documents使用基于RocksDB的无类型key-value存储来保存。documents与时间戳一起转换为多个key-value对。因为文档包含很多不同的key-value,所以可以廉价的对它们进行部分修改。

例如,考虑以下存储在DocDB中的文档

DocumentKey1 = {
     SubKey1 = {
           SubKey2 = Value1
           SubKey3 = Value2
     },
     SubKey4 = Value3
}

 RocksDB中存储的key由许多部分组成,其中部分是“document key”,后面是一些标量部分,后是MVCC时间戳(按相反顺序排序)。DocumentKey、SubKey和Value中的每个部分都是基本值,只是key-value对,可以对字符串进行编码和解码。对key的基本value进行编码时,使用binary-comparable方式编码,因此编码的排序顺序和值的排序顺序相同。

Updates and deletes

假设上面的示例文档完全是在时间T10编写的。在内部,上述示例的文档使用5个RocksDB键值对进行存储:

DocumentKey1, T10 -> {} // This is an init marker
DocumentKey1, SubKey1, T10 -> {}
DocumentKey1, SubKey1, SubKey2, T10 -> Value1
DocumentKey1, SubKey1, SubKey3, T10 -> Value2
DocumentKey1, SubKey4, T10 -> Value3

删除Documents和SubDocuments的方法是在相应的值处写入一个Tombstone标记。在压缩过程中,将清除覆盖或删除的值以回收空间。

为了实现可扩展性和性能,对RocksDB进行了许多增强或定制。下文对这些问题进行了说明。

 

Efficiently model documents

要实现灵活的数据模型,如行(由列组成)或集合类型(如列表、映射、集),并在键值存储顶部进行任意嵌套,但更重要的是,要在此数据模型上实现高效操作,例如:

对行或集合的一部分进行细粒度更新,而不会对整个行或集合产生读-修改-写影响。

对要删除的KV集合,在任意嵌套级别执行删除或覆盖行或集合/对象,都不会影响读。

强制执行基于行/对象级别的TTL过期。

需要与底层RocksDB key-value存储的“读取/压缩”层进行更紧密的耦合。Yugabytedb将RocksDB用作append-only的存储,像行或集合删除之类的操作仅仅是插入一个特殊标记,这样只需向RocksDB添加一个key-value对,就可以高效地删除整个子文档。read hooks自动识别这些标记并抑制过期数据。subdocument中的过期值由我们定制的压缩hooks进行清除或垃圾收集。

Raft vs RocksDB WAL logs

DocDB使用Raft进行复制。对分布式系统的更改已作为Raft日志的一部分进行记录或日志记录。当成功复制到多数派节点时,它将应用于每个tablet peer的DocDB,但RocksDB(DocDB下)中的附加WAL机制是不必要的,并且会增加开销。为确保正确性,除了禁用RocksDB中的WAL机制外,YugabyteDB还跟踪Raft“sequence id”,直到该id为止,数据已从RocksDB的memtables刷新到SSTable文件。这确保了我们可以正确地对Raft-WAL日志进行垃圾收集,并在服务器崩溃或重新启动时从Raft-WAL日志中重放少数量的记录。

更高层的MVCC

DocDB中的多版本并发控制(MVCC)在更高的层上完成,并且没有使用RocksDB的MVCC机制。

系统中记录的改变,使用YBase层维护的混合时间戳进行版本控制。因此,在RocksDB(使用序列ID)中实现MVCC的概念是不必要的,只会增加开销。YugabyteDB不使用RocksDB的序列ID,而是使用作为编码key一部分的混合时间戳来实现MVCC。

Backups and snapshots

需要更别的操作,考虑DocDB以及Raft日志中的数据,以获得系统状态的一致性。

Data model aware Bloom filters

DocDB在RocksDB中存储的key由许多部分组成,其中个部分是“document key”,后面是一些标量部分,后是时间戳(按相反顺序排序)。

bloom filter需要知道将key的哪部分添加到bloom中,以便在读取操作期间仅查找LSM存储中的相关SSTable文件。

在传统的KV存储中,范围扫描不使用bloom filter,因为范围内的确切key未知。然而,我们已经实现了一个数据模型感知的bloom filter,当key的哈希部分值相同时,范围扫描可以使用bloom filter。例如,扫描以获取行中的所有列或集合的所有元素也可以受益于bloom filter。

范围查询优化

DocDB中的组合key的有序部分通常是自然顺序。例如int类型可以表示消息ID或者时间戳。在LSM存储中的每个SSTable文件中保存key的大值和小值,范围查询可以在读取操作期间智能地删除不相关SSTable文件的查找。

高效的内存使用

在两种情况下,以服务器全局方式跨组件使用内存会带来好处,如下所述:

Server-global block cache

YB-TServer保存的所有tablet的DocDB/RocksDB实例共享一个block cache,这可以大化的利用内存资源,避免需要针对不同的用户表创建不同大小的缓存库。

Server-global memstore limits

虽然可以配置每个memstore的刷新大小,但实际上,由于memstore的数量可能会随着用户创建新表或表的tablet在服务器之间移动而变化,因此我们增强了存储引擎,以强制执行全局memstore阈值。当达到这样一个阈值时,选择要刷新的memstore会考虑哪些memstore携带早的记录(使用混合时间戳确定),因此会保留Raft日志并防止它们被垃圾收集。

Scan-resistant block cache

我们增强了RocksDB的block cache,防止长时间运行的扫描操作用低质量的数据污染整个缓存,导致有用的热数据被清理。

相关文章