关于kv存储:DatenLord前沿技术分享-No25

达坦科技专一于打造新一代开源跨云存储平台DatenLord,通过软硬件深度交融的形式买通云云壁垒,致力于解决多云架构、多数据中心场景下异构存储、数据对立治理需要等问题,以满足不同行业客户对海量数据跨云、跨数据中心高性能拜访的需要。在本周的前沿技术分享中,咱们邀请到了Deno Land Inc.的软件工程师周鹤洋,来为大家分享Deno KV存储。 01、演讲题目Deno KV 02、演讲工夫2023年5月21日上午10:30 03、演讲人周鹤洋Software Engineer @ Deno Land Inc. 04、引言Deno KV (https://deno.com/kv) 是 Deno 的强统一、程度可扩大的 KV 数据库产品,底层存储基于 FoundationDB ,与 JavaScript 深度整合。本次分享将会介绍 Deno KV 的零碎与 API 设计。 05、内容简介Deno KV 与 FoundationDB 简介存储系统分布式事务零碎寰球正本复制JavaScript API06、直播预约欢迎您预约直播,或者登陆腾讯会议观看直播:会议号:474-6575-9473 达坦科技(DatenLord)专一下一代云计算——“天空计算”的基础设施技术,致力于拓宽云计算的边界。达坦科技打造的新一代开源跨云存储平台DatenLord,通过软硬件深度交融的形式买通云云壁垒,实现无限度跨云存储、跨云联通,建设海量异地、异构数据的对立存储拜访机制,为云上利用提供高性能平安存储反对。以满足不同行业客户对海量数据跨云、跨数据中心高性能拜访的需要。公众号:达坦科技DatenLordDatenLord官网:http://www.datenlord.io知乎账号:https://www.zhihu.com/org/da-tan-ke-jiB站:https://space.bilibili.com/2017027518

May 18, 2023 · 1 min · jiezi

关于kv存储:一种KV存储的GC优化实践

作者:vivo 互联网服务器团队- Yuan Jian Wei从外部需要登程,咱们基于TiKV设计了一款兼容Redis的KV存储。基于TiKV的数据存储机制,对于窗口数据的解决以及过期数据的GC问题却成为一个难题。本文心愿基于从KV存储的设计开始解说,到GC设计的逐层优化的过程,从问题的存在到不同层面的剖析,能够给读者在相似的优化实际中提供一种参考思路。 一、背景介绍以后公司外部没有对立的KV存储服务,很多业务都将 Redis 集群当作KV存储服务在应用,然而局部业务可能不须要 Redis 如此高的性能,却承当着微小的机器资源老本(内存价格绝对磁盘来说更加低廉)。为了升高存储老本的需要,同时尽可能减少业务迁徙的老本,咱们基于 TiKV 研发了一套磁盘KV存储服务。 1.1 架构简介以下对这种KV存储(下称磁盘KV)的架构进行简略形容,为后续问题形容做铺垫。 1.1.1 零碎架构磁盘KV应用目前较风行的计算存储拆散架构,在TiKV集群下层封装计算层(后称Tula)模仿Redis集群(对外体现是不同的Tula负责某些slot范畴),间接接入业务Redis客户端。 图1:磁盘KV架构图示 业务写入数据基于Tula转换成TiKV中存储的KV对,基于相似的形式实现业务数据的读取。 留神:Tula中会选举出一个leader,用于进行一些后台任务,后续具体说。 1.1.2 数据编码TiKV对外提供的是一种KV的读写性能,然而Redis对外提供的是基于数据结构提供读写能力(例如SET,LIST等),因而须要基于TiKV现有提供的能力,将Redis的数据结构进行编码,并且能够不便地在TiKV中进行读写。 TiKV提供的API比较简单:基于key的读写接口,以及基于字典序的迭代器拜访。 因而,Tula层面基于字典序的机制,对Redis的数据结构基于字典序进行编码,便于拜访。 留神:TiKV的key是能够基于字典序进行遍历(例如基于某个前缀进行遍历等),后续的编码,机制根本是基于字典序的个性进行设计。 为了能够更好地基于字典序排列的搜寻个性下对数据进行读写,对于简单的数据结构,咱们会应用另外的空间去寄存其中的数据(例如SET中的member,HASH中的field)。而对于简略的数据结构(相似STRING),则能够间接寄存到key对应的value中。 为此,咱们在编码设计上,会分为metaKey和dataKey。metaKey是基于用户间接拜访的key进行编码,是编码设计中间接对外的一层;dataKey则是metaKey的存储指向,用来寄存简单数据结构中的具体外部数据。 另外,为了保障拜访的数据一致性,咱们基于TiKV的事务接口进行对key的拜访。 (1)编码&字段 以下以编码中的一些概念以及设定,对编码进行简述。 key的编码规定如下: 图2:key编码设计图示 以下对字段进行阐明 namespace为了不便在一个TiKV集群中能够寄存不同的磁盘KV数据,咱们在编码的时候增加了前缀namespace,不便集群基于这个namespace在同一个物理TiKV空间中基于逻辑空间进行分区。 dbid因为原生Redis是反对select语句,因而在编码上须要预留字段示意db的id,不便后续进行db切换(select语句操作)的时候切换,示意不同的db空间。 role用于辨别是哪种类型的key。 对于简略的数据结构(例如STRING),只须要间接在key上面存储对应的value即可。 然而对于一些简单的数据结构(例如HASH,LIST等),如果在一个value下把所有的元素都存储了,对与相似SADD等指令的并发,为了保证数据一致性,必然能够预感其性能有余。 因而,磁盘KV在设计的时候把元素数据依照独立的key做存储,须要基于肯定的规定对元素key进行拜访。这样会导致在key的编码上,会存在key的role字段,辨别是用户看到的key(metaKey),还是这种元素的key(dataKey)。 其中,如果是metaKey,role固定是M;如果是dataKey,则是D。 keyname在metaKey和dataKey的根底上,能够基于keyname字段能够较不便地拜访到对应的key。 suffix针对dataKey,基于不同的Redis数据结构,都须要不同的dataKey规定进行反对。因而此处须要预留suffix区间给dataKey在编码的时候预留空间,实现不同的数据类型。 以下基于SET类型的SADD指令,对编码进行简略演示: 图3: SADD指令的编码设计指令图示 基于userKey,通过metaKey的拼接形式,拼接metaKey并且拜访拜访metaKey获取value中的基于value中的uuid,生成须要的dataKey写入生成的dataKey(2)编码实战 编码实战中,会以SET类型的实现细节作为例子,形容磁盘KV在实战中的编码细节。 在这之前,须要对metaKey的局部实现细节进行理解 (3)metaKey存储细节 所有的metaKey中都会存储下列数据。 图4:metaKey编码设计图示 uuid:每一个metaKey都会有一个对应的uuid,示意这个key的惟一身份。create_time:保留该元数据的创立工夫update_time: 保留该元数据的最近更新工夫expire_time: 保留过期工夫data_type: 保留该元数据对应的数据类型,例如SET,HASH,LIST,ZSET等等。encode_type: 保留该数据类型的编码方式(4)SET实现细节 基于metaKey的存储内容,以下基于SET类型的数据结构进行解说。 SET类型的dataKey的编码规定如下: keyname:metaKey的uuidsuffix:SET对应的member字段因而,SET的dataKey编码如下: 图5:SET数据结构dataKey编码设计图示 以下把用户能够拜访到的key称为user-key。汇合中的元素应用member1,member2等标注。 这里,能够梳理出拜访逻辑如下: 图6:SET数据结构拜访流程图示 简述上图的拜访逻辑: ...

May 12, 2023 · 1 min · jiezi

bboltdb-的使用技巧

Tricks 桶的自增键使用 NextSequence()来创建自增键,见下例 // CreateUser saves u to the store. The new user ID is set on u once the data is persisted.func (s *Store) CreateUser(u *User) error { return s.db.Update(func(tx *bolt.Tx) error { // Retrieve the users bucket. // This should be created when the DB is first opened. b := tx.Bucket([]byte("users")) // Generate ID for the user. // This returns an error only if the Tx is closed or not writeable. // That can't happen in an Update() call so I ignore the error check. id, _ := b.NextSequence() u.ID = int(id) // Marshal user data into bytes. buf, err := json.Marshal(u) if err != nil { return err } // Persist bytes to users bucket. return b.Put(itob(u.ID), buf) })}// itob returns an 8-byte big endian representation of v.func itob(v int) []byte { b := make([]byte, 8) binary.BigEndian.PutUint64(b, uint64(v)) return b}type User struct { ID int ...} ...

October 2, 2019 · 3 min · jiezi

boltdb-基础使用

https://www.yuque.com/abs/dr4... Using Store ???? Using Buckets桶是键值对的集合。在一个桶中,键值唯一。 创建使用 Tx.CreateBucket() 和 Tx.CreateBucketIfNotExists() 建立一个新桶(推荐使用第二个)接受参数是 桶的名字 删除使用 Tx.DeleteBucket() 根据桶的名字来删除 例子func main() { db, err := bbolt.Open("./data", 0666, nil) if err != nil { log.Fatal(err) } defer db.Close() db.Update(func(tx *bbolt.Tx) error { b, err := tx.CreateBucketIfNotExists([]byte("MyBucket")) if err != nil { return fmt.Errorf("create bucket: %v", err) } if err = tx.DeleteBucket([]byte("MyBucket")); err != nil { return err } return nil })} ...

October 2, 2019 · 2 min · jiezi

Titan 的设计与实现

作者:郑志铨Titan 是由 PingCAP 研发的一个基于 RocksDB 的高性能单机 key-value 存储引擎,其主要设计灵感来源于 USENIX FAST 2016 上发表的一篇论文 WiscKey。WiscKey 提出了一种高度基于 SSD 优化的设计,利用 SSD 高效的随机读写性能,通过将 value 分离出 LSM-tree 的方法来达到降低写放大的目的。我们的基准测试结果显示,当 value 较大的时候,Titan 在写、更新和点读等场景下性能都优于 RocksDB。但是根据 RUM Conjecture,通常某些方面的提升往往是以牺牲其他方面为代价而取得的。Titan 便是以牺牲硬盘空间和范围查询的性能为代价,来取得更高的写性能。随着 SSD 价格的降低,我们认为这种取舍的意义会越来越明显。设计目标Titan 作为 TiKV 的一个子项目,首要的设计目标便是兼容 RocksDB。因为 TiKV 使用 RocksDB 作为其底层的存储引擎,而 TiKV 作为一个成熟项目已经拥有庞大的用户群体,所以我们需要考虑已有的用户也可以将已有的基于 RocksDB 的 TiKV 平滑地升级到基于 Titan 的 TiKV。因此,我们总结了四点主要的设计目标:支持将 value 从 LSM-tree 中分离出来单独存储,以降低写放大。已有 RocksDB 实例可以平滑地升级到 Titan,这意味着升级过程不需要人工干预,并且不会影响线上服务。100% 兼容目前 TiKV 所使用的所有 RocksDB 的特性。尽量减少对 RocksDB 的侵入性改动,保证 Titan 更加容易升级到新版本的 RocksDB。架构与实现Titan 的基本架构如下图所示:图 1:Titan 在 Flush 和 Compaction 的时候将 value 分离出 LSM-tree,这样做的好处是写入流程可以和 RockDB 保持一致,减少对 RocksDB 的侵入性改动。Titan 的核心组件主要包括:BlobFile、TitanTableBuilder、Version 和 GC,下面将逐一进行介绍。BlobFileBlobFile 是用来存放从 LSM-tree 中分离出来的 value 的文件,其格式如下图所示:图 2:BlobFile 主要由 blob record 、meta block、meta index block 和 footer 组成。其中每个 blob record 用于存放一个 key-value 对;meta block 支持可扩展性,可以用来存放和 BlobFile 相关的一些属性等;meta index block 用于检索 meta block。BlobFile 有几点值得关注的地方:BlobFile 中的 key-value 是有序存放的,目的是在实现 Iterator 的时候可以通过 prefetch 的方式提高顺序读取的性能。每个 blob record 都保留了 value 对应的 user key 的拷贝,这样做的目的是在进行 GC 的时候,可以通过查询 user key 是否更新来确定对应 value 是否已经过期,但同时也带来了一定的写放大。BlobFile 支持 blob record 粒度的 compression,并且支持多种 compression algorithm,包括 Snappy、LZ4 和 Zstd 等,目前 Titan 默认使用的 compression algorithm 是 LZ4 。TitanTableBuilderTitanTableBuilder 是实现分离 key-value 的关键。我们知道 RocksDB 支持使用用户自定义 table builder 创建 SST,这使得我们可以不对 build table 流程做侵入性的改动就可以将 value 从 SST 中分离出来。下面将介绍 TitanTableBuilder 的主要工作流程:图 3:TitanTableBuilder 通过判断 value size 的大小来决定是否将 value 分离到 BlobFile 中去。如果 value size 大于等于 min_blob_size 则将 value 分离到 BlobFile ,并生成 index 写入 SST;如果 value size 小于 min_blob_size 则将 value 直接写入 SST。Titan 和 Badger 的设计有很大区别。Badger 直接将 WAL 改造成 VLog,这样做的好处是减少一次 Flush 的开销。而 Titan 不这么设计的主要原因有两个:假设 LSM-tree 的 max level 是 5,放大因子为 10,则 LSM-tree 总的写放大大概为 1 + 1 + 10 + 10 + 10 + 10,其中 Flush 的写放大是 1,其比值是 42 : 1,因此 Flush 的写放大相比于整个 LSM-tree 的写放大可以忽略不计。在第一点的基础上,保留 WAL 可以使 Titan 极大地减少对 RocksDB 的侵入性改动,而这也正是我们的设计目标之一。VersionTitan 使用 Version 来代表某个时间点所有有效的 BlobFile,这是从 LevelDB 中借鉴过来的管理数据文件的方法,其核心思想便是 MVCC,好处是在新增或删除文件的同时,可以做到并发读取数据而不需要加锁。每次新增文件或者删除文件的时候,Titan 都会生成一个新的 Version ,并且每次读取数据之前都要获取一个最新的 Version。图 4:新旧 Version 按顺序首尾相连组成一个双向链表,VersionSet 用来管理所有的 Version,它持有一个 current 指针用来指向当前最新的 Version。Garbage CollectionGarbage Collection (GC) 的目的是回收空间,一个高效的 GC 算法应该在权衡写放大和空间放大的同时,用最少的周期来回收最多的空间。在设计 GC 的时候有两个主要的问题需要考虑:何时进行 GC挑选哪些文件进行 GCTitan 使用 RocksDB 提供的两个特性来解决这两个问题,这两个特性分别是 TablePropertiesCollector 和 EventListener 。下面将讲解我们是如何通过这两个特性来辅助 GC 工作的。BlobFileSizeCollectorRocksDB 允许我们使用自定义的 TablePropertiesCollector 来搜集 SST 上的 properties 并写入到对应文件中去。Titan 通过一个自定义的 TablePropertiesCollector —— BlobFileSizeCollector 来搜集每个 SST 中有多少数据是存放在哪些 BlobFile 上的,我们将它收集到的 properties 命名为 BlobFileSizeProperties,它的工作流程和数据格式如下图所示:图 5:左边 SST 中 Index 的格式为:第一列代表 BlobFile 的文件 ID,第二列代表 blob record 在 BlobFile 中的 offset,第三列代表 blob record 的 size。右边 BlobFileSizeProperties 中的每一行代表一个 BlobFile 以及 SST 中有多少数据保存在这个 BlobFile 中,第一列代表 BlobFile 的文件 ID,第二列代表数据大小。EventListener我们知道 RocksDB 是通过 Compaction 来丢弃旧版本数据以回收空间的,因此每次 Compaction 完成后 Titan 中的某些 BlobFile 中便可能有部分或全部数据过期。因此我们便可以通过监听 Compaction 事件来触发 GC,通过搜集比对 Compaction 中输入输出 SST 的 BlobFileSizeProperties 来决定挑选哪些 BlobFile 进行 GC。其流程大概如下图所示:图 6:inputs 代表参与 Compaction 的所有 SST 的 BlobFileSizeProperties,outputs 代表 Compaction 生成的所有 SST 的 BlobFileSizeProperties,discardable size 是通过计算 inputs 和 outputs 得出的每个 BlobFile 被丢弃的数据大小,第一列代表 BlobFile 的文件 ID,第二列代表被丢弃的数据大小。Titan 会为每个有效的 BlobFile 在内存中维护一个 discardable size 变量,每次 Compaction 结束之后都对相应的 BlobFile 的 discardable size 变量进行累加。每次 GC 开始时就可以通过挑选 discardable size 最大的 BlobFile 来作为作为候选的文件。Sample每次进行 GC 前我们都会挑选一系列 BlobFile 作为候选文件,挑选的方法如上一节所述。为了减小写放大,我们可以容忍一定的空间放大,所以我们只有在 BlobFile 可丢弃的数据达到一定比例之后才会对其进行 GC。我们使用 Sample 算法来获取每个候选文件中可丢弃数据的大致比例。Sample 算法的主要逻辑是随机取 BlobFile 中的一段数据 A,计其大小为 a,然后遍历 A 中的 key,累加过期的 key 所在的 blob record 的 size 计为 d,最后计算得出 d 占 a 比值 为 r,如果 r >= discardable_ratio 则对该 BlobFile 进行 GC,否则不对其进行 GC。上一节我们已经知道每个 BlobFile 都会在内存中维护一个 discardable size,如果这个 discardable size 占整个 BlobFile 数据大小的比值已经大于或等于 discardable_ratio 则不需要对其进行 Sample。基准测试我们使用 go-ycsb 测试了 TiKV 在 Txn Mode 下分别使用 RocksDB 和 Titan 的性能表现,本节我会简要说明下我们的测试方法和测试结果。由于篇幅的原因,我们只挑选两个典型的 value size 做说明,更详细的测试分析报告将会放在下一篇文章。测试环境CPU:Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz(40个核心)Memory:128GB(我们通过 Cgroup 限制 TiKV 进程使用内存不超过 32GB)Disk:SATA SSD 1.5TB(fio 测试:4KB block size 混合随机读写情况下读写 IOPS 分别为 43.8K 和 18.7K)测试计划数据集选定的基本原则是原始数据大小(不算上写放大因素)要比可用内存小,这样可以防止所有数据被缓存到内存中,减少 Cache 所带来的影响。这里我们选用的数据集大小是 64GB,进程的内存使用限制是 32GB。Value SizeNumber of Keys (Each Key = 16 Bytes)Raw Data Size1KB64M64GB16KB4M64GB我们主要测试 5 个常用的场景:Data Loading Performance:使用预先计算好的 key 数量和固定的 value 大小,以一定的速度并发写入。Update Performance:由于 Titan 在纯写入场景下不需要 GC(BlobFile 中没有可丢弃数据),因此我们还需要通过更新来测试 GC 对性能的影响。Output Size:这一步我们会测量更新场景完成后引擎所占用的硬盘空间大小,以此反映 GC 的空间回收效果。Random Key Lookup Performance:这一步主要测试点查性能,并且点查次数要远远大于 key 的数量。Sorted Range Iteration Performance:这一步主要测试范围查询的性能,每次查询 2 million 个相连的 key。测试结果图 7 Data Loading Performance:Titan 在写场景中的性能要比 RocksDB 高 70% 以上,并且随着 value size 的变大,这种性能的差异会更加明显。值得注意的是,数据在写入 KV Engine 之前会先写入 Raft Log,因此 Titan 的性能提升会被摊薄,实际上裸测 RocksDB 和 Titan 的话这种性能差异会更大。图 8 Update Performance:Titan 在更新场景中的性能要比 RocksDB 高 180% 以上,这主要得益于 Titan 优秀的读性能和良好的 GC 算法。图 9 Output Size:Titan 的空间放大相比 RocksDB 略高,这种差距会随着 Key 数量的减少有略微的缩小,这主要是因为 BlobFile 中需要存储 Key 而造成的写放大。图 10 Random Key Lookup: Titan 拥有比 RocksDB 更卓越的点读性能,这主要得益与将 value 分离出 LSM-tree 的设计使得 LSM-tree 变得更小,因此 Titan 在使用同样的内存量时可以将更多的 index 、filter 和 DataBlock 缓存到 Block Cache 中去。这使得点读操作在大多数情况下仅需要一次 IO 即可(主要是用于从 BlobFile 中读取数据)。图 11 Sorted Range Iteration:Titan 的范围查询性能目前和 RocksDB 相比还是有一定的差距,这也是我们未来优化的一个重要方向。本次测试我们对比了两个具有代表性的 value size 在 5 种不同场景下的性能差异,更多不同粒度的 value size 的测试和更详细的性能报告我们会放在下一篇文章去说明,并且我们会从更多的角度(例如 CPU 和内存的使用率等)去分析 Titan 和 RocksDB 的差异。从本次测试我们可以大致得出结论,在大 value 的场景下,Titan 会比 RocksDB 拥有更好的写、更新和点读性能。同时,Titan 的范围查询性能和空间放大都逊于 RocksDB 。兼容性一开始我们便将兼容 RocksDB 作为设计 Titan 的首要目标,因此我们保留了绝大部分 RocksDB 的 API。目前仅有两个 API 是我们明确不支持的:MergeSingleDelete除了 Open 接口以外,其他 API 的参数和返回值都和 RocksDB 一致。已有的项目只需要很小的改动即可以将 RocksDB 实例平滑地升级到 Titan。值得注意的是 Titan 并不支持回退回 RocksDB。如何使用 Titan创建 DB#include <assert>#include “rocksdb/utilities/titandb/db.h”// Open DBrocksdb::titandb::TitanDB* db;rocksdb::titandb::TitanOptions options;options.create_if_missing = true;rocksdb::Status status = rocksdb::titandb::TitanDB::Open(options, “/tmp/testdb”, &db);assert(status.ok());…或#include <assert>#include “rocksdb/utilities/titandb/db.h”// open DB with two column familiesrocksdb::titandb::TitanDB* db;std::vector<rocksdb::titandb::TitanCFDescriptor> column_families;// have to open default column familycolumn_families.push_back(rocksdb::titandb::TitanCFDescriptor( kDefaultColumnFamilyName, rocksdb::titandb::TitanCFOptions()));// open the new one, toocolumn_families.push_back(rocksdb::titandb::TitanCFDescriptor( “new_cf”, rocksdb::titandb::TitanCFOptions()));std::vector<ColumnFamilyHandle*> handles;s = rocksdb::titandb::TitanDB::Open(rocksdb::titandb::TitanDBOptions(), kDBPath, column_families, &handles, &db);assert(s.ok());Status和 RocksDB 一样,Titan 使用 rocksdb::Status 来作为绝大多数 API 的返回值,使用者可以通过它检查执行结果是否成功,也可以通过它打印错误信息:rocksdb::Status s = …;if (!s.ok()) cerr << s.ToString() << endl;销毁 DBstd::string value;rocksdb::Status s = db->Get(rocksdb::ReadOptions(), key1, &value);if (s.ok()) s = db->Put(rocksdb::WriteOptions(), key2, value);if (s.ok()) s = db->Delete(rocksdb::WriteOptions(), key1);在 TiKV 中使用 Titan目前 Titan 在 TiKV 中是默认关闭的,我们通过 TiKV 的配置文件来决定是否开启和设置 Titan,相关的配置项包括 [rocksdb.titan] 和 [rocksdb.defaultcf.titan], 开启 Titan 只需要进行如下配置即可:[rocksdb.titan]enabled = true注意一旦开启 Titan 就不能回退回 RocksDB 了。未来的工作优化 Iterator我们通过测试发现,目前使用 Titan 做范围查询时 IO Util 很低,这也是为什么其性能会比 RocksDB 差的重要原因之一。因此我们认为 Titan 的 Iterator 还存在着巨大的优化空间,最简单的方法是可以通过更加激进的 prefetch 和并行 prefetch 等手段来达到提升 Iterator 性能的目的。GC 速度控制和自动调节通常来说,GC 的速度太慢会导致空间放大严重,过快又会对服务的 QPS 和延时带来影响。目前 Titan 支持自动 GC,虽然可以通过减小并发度和 batch size 来达到一定程度限制 GC 速度的目的,但是由于每个 BlobFile 中的 blob record 数目不定,若 BlobFile 中的 blob record 过于密集,将其有效的 key 更新回 LSM-tree 时仍然可能堵塞业务的写请求。为了达到更加精细化的控制 GC 速度的目的,后续我们将使用 Token Bucket 算法限制一段时间内 GC 能够更新的 key 数量,以降低 GC 对 QPS 和延时的影响,使服务更加稳定。另一方面,我们也正在研究自动调节 GC 速度的算法,这样我们便可以,在服务高峰期的时候降低 GC 速度来提供更高的服务质量;在服务低峰期的时候提高 GC 速度来加快空间的回收。增加用于判断 key 是否存在的 APITiKV 在某些场景下仅需要判断某个 key 是否存在,而不需要读取对应的 value。通过提供一个这样的 API 可以极大地提高性能,因为我们已经看到将 value 移出 LSM-tree 之后,LSM-tree 本身会变的非常小,以至于我们可以将更多地 index、filter 和 DataBlock 存放到内存当中去,这样去检索某个 key 的时候可以做到只需要少量甚至不需要 IO 。 ...

January 23, 2019 · 4 min · jiezi