TinyKV Project1 Standalone KV

这篇文章总结了 TinyKV Project1 的实现内容,核心是在 Badger 之上构建一个支持 Column Family 的 standalone KV 存储引擎。正文先解释 TinyKV 里 `default`、`write`、`lock` 等 CF 的基本含义,以及如何通过 key 前缀在底层 KV 中模拟多列结构,随后说明 standalone storage、Reader 和批量写入的实现方式,再继续梳理 `RawGet`、`RawScan`、`RawPut`、`RawDelete` 等服务接口如何基于自定义 Reader 完成读写。文章最后还补充介绍了 Badger 参考 WiscKey 的存储设计,以及它在读写放大上的权衡。

这一节实验要求我们基于 badger 实现一个支持 Column Family 的 KV 数据库。

Column Family,也叫 CF,这个概念从 HBase 中来,就是将多个列合并为一个CF进行管理。这样读取一行数据时,你可以按照 CF 加载列,不需要加载所有列(通常同一个CF的列会保存在同一个文件中,所以这样有很高的效率)。此外因为同一列的数据格式相同,你可以针对某种格式采用高效的压缩算法。

当然这里的 CF 就没有那么高级,纯粹是让一个 KV 数据库支持多列而已。不然 KV 数据库按照概念就是一个 key 对应一个 value 。如果我想让 KV 数据库和传统数据库一样有多列怎么办呢?如下图:

KEYdefaultwritelock
smith123xxxxyyyy

你可以通过在 key 前面加前缀的方式,实现一个 key 有多列。像上面的你可以看做:

KEYVALUE
default_smith123
write_smithxxxx
lock_smithyyyy

Implement standalone storage engine

实现代码:kv/storage/standalone_storage/standalone_storage.go

关于 badger 的 API 不要从官网上看最新版,新版的 API 已经变动了。按照 https://github.com/Connor1996/badger 里面的文档进行操作就行。

badger 不提供 CF 的支持,所以你要通过kv/util/engine_util 提供的方法来添加前缀。

err := txn.Set(engine_util.KeyWithCF(v.Cf(), v.Key()), v.Value())

NewStandAloneStorage(conf *config.Config) 方法中,我们会创建 badger,badger 的创建参数从 conf 里面拿就行了。

Write() 方法注意传入的是 batch []storage.Modify 是一个批量操作,我们需要逐个遍历每一个元素,写入 badger 中的同一个 txn。

Reader 方法需要我们返回一个 storage.StorageReader ,这里自己写一个 Reader 实现StorageReader 接口就行了。Reader 里面包含一个 txn 成员变量。

从txn中读取或者遍历的方法不用自己写,使用 engine_util 提供的方法即可。

// get value
val, err := engine_util.GetCFFromTxn(txn, cf, key)
// get iterator
iter := engine_util.NewCFIterator(cf, txn)

Implement service handlers

实现代码:kv/server/raw_api.go,可以参考kv/server/server.go 但是远没有它这么复杂。

这里我们基于刚刚自己编写的 StandAloneStorage 来实现上层服务,提供 RawGet/ RawScan/ RawPut/ RawDelete四种请求。

这里我们会涉及到 storage.Modify 具体可以看 kv/storage/modify.go

比如一个 Put 请求,我们可以这么写:

put := storage.Put{
  Key:   req.GetKey(),
  Value: req.GetValue(),
  Cf:    req.GetCf(),
}
modify := storage.Modify{Data: put}
  • RawGet: 如果获取不到,返回时要标记 reply.NotFound=true
  • RawPut:把单一的 put 请求用 storage.Modify 包装成一个数组,一次性写入。
  • RawDelete:和 RawPut 同理。
  • RawScan:通过 reader 获取 iter,从 StartKey 开始,同时注意 limit。迭代的写法可以如下:
for iter.Seek(startKey); iter.Valid(); iter.Next() {
  if nums >= req.GetLimit() {
    break
  }
  item := iter.Item()
  // ......
  nums++
}
iter.Close()

Badger

Badger 采用的也是 LSM-tree 结构,但是它参考了 WiscKey 的设计,这里简单说下。

LSM有两个缺点,一个是读取放大,一个是写入放大。WicsKey 假设我们都是有钱人,用的是SSD,那么我们就没有必要像传统LSM一样耗费大量的性能去实现顺序读。WicsKey 采取如下设计:

TinyKV Project1 Standalone KV

将 key 和指向 value 的 addr 仍然放在 LSM-tree 里面,value 则单独放在 Value log 中。因为 key 和 addr 通常都很小,所以这一部分数据完全可以加载在内存中。也就是 LSM 中的 MemTable 可以容纳大量的 key,以至于你的读取某一个 key 时都不需要访问磁盘,直接可以从内存中获取该 key 所在的位置。

Value log 中的数据都是直接 append 的,不需要和 LSM 一样需要实现排序,这样就不存在写入放大的问题。

代价是什么?当你需要 scan 某一个范围的数据时,你会涉及大量的随机读取,因为 Value log 中的 value 是无序的。不过有些 SSD 的随机读性能可以基本持平顺序读,所以问题不大。

总结

这一节实验非常简单,估计是 TinyKV 为了不打击我们的自信心,给的一个热身项目,让我们熟悉一下 go 的语法。

原创文章,作者:Smith,如若转载,请注明出处:https://www.inlighting.org/archives/tinykv-project1

打赏 微信扫一扫 微信扫一扫
SmithSmith
上一篇 2022年2月20日 下午2:23
下一篇 2023年3月2日 下午10:47

相关推荐

  • TinyKV Project3 MultiRaftKV

    这一节中,最难的就是 Project 3B,引无数英雄竞折腰!!当然撑过 3B,你就解放了。 Membership Change 在 Project3A 中我们需要实现 Leade…

    2023年3月2日
    2.0K0
  • 理解 FLP-Impossibility 论文

    这篇文章尝试用更易读的方式梳理 FLP-Impossibility 论文,解释为什么在异步消息系统中,即使最多只有一个进程故障,也不存在能够同时满足 Validity、Agreement 和 Termination 的真正共识算法。正文先定义论文里的大量术语和系统模型,包括 process、configuration、schedule、bivalent、univalent 等概念,再围绕几个关键 lemma 逐步展开证明思路,说明系统总能停留在无法保证做出决议的状态。最后文章结合 Paxos、Raft 等现实算法讨论 FLP 的实际意义,即它证明的是最坏情况的存在,而工程系统必须通过随机化、时序假设或其他约束来换取可用性。

    2021年6月10日
    5.5K8
  • Amdahl’s law(阿姆达尔定律)公式推导与思考

    这篇文章围绕 Amdahl’s law 展开,说明并行计算中程序整体加速比为何会受到串行部分的限制。正文先解释公式里并行比例 a 和并行加速倍数 n 的含义,并引入 Fraction enhanced 与 Speedup enhanced 两个概念,再从总执行时间、并行代码时间和串行代码时间的关系出发,对公式 S = 1 / (1 – a + a / n) 进行推导。最后文章回到公式本身,讨论当并行比例固定、核心数持续增加时系统加速比仍存在理论上限这一结论。

    2020年10月13日
    7.1K2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注