主要读了Database internal的LSM Tree相关部分,顺便完成了对6.824 lab2的优化.自己最近感兴趣的点主要就是存储以及分布式系统,这周准备带着看一下leveldb中LSM Tree相关实现,完成6.824 的lab 3.
LSM Tree
LSM Tree主要的优点在于写入速度很快(SSD sequential write速度和mem的random write相当),缺点则是读和空间放大比较严重.
自己目前还没有看过任何LSM Tree的实现,只通过Database Internal了解过一点简单的LSM原理,包括但不限于以下知识点:
- LSM Tree是什么(memtable, memtable)?为什么用它?
- LSM Tree怎么做read/write/delete?
- …怎么优化冗余的键值对?
- …怎么优化读放大?
raft优化
由于是二刷6.824,这一次完成lab2一共大概花了4天时间(通过所有测试),但是优化代码花了一周时间.
虽说是优化,其实只是面向测试样例的一些简单调优,并没有对raft算法本身做巨大改动.主要的测试指标为:rpc数量以及测试完成时间.
完成优化之后lab2共计耗时5分30秒左右(100次运行平均),此处给出2B以及2C的测试数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| Test (2A): initial election ... ... Passed -- 3.1 3 60 15080 0 Test (2A): election after network failure ... ... Passed -- 5.0 3 142 26268 0 Test (2A): multiple elections ... ... Passed -- 6.4 7 577 105547 0 Test (2B): basic agreement ... ... Passed -- 0.3 3 16 4107 3 Test (2B): RPC byte count ... ... Passed -- 0.5 3 48 153102 11 Test (2B): agreement despite follower disconnection ... ... Passed -- 2.9 3 81 20270 7 Test (2B): no agreement if too many followers disconnect ... ... Passed -- 3.3 5 191 37753 3 Test (2B): concurrent Start()s ... ... Passed -- 0.6 3 20 5614 6 Test (2B): rejoin of partitioned leader ... ... Passed -- 4.5 3 159 33320 4 Test (2B): leader backs up quickly over incorrect follower logs ... ... Passed -- 9.3 5 1354 946435 102 Test (2B): RPC counts aren't too high ... ... Passed -- 2.0 3 46 13453 12 Test (2C): basic persistence ... ... Passed -- 4.0 3 103 23403 6 Test (2C): more persistence ... ... Passed -- 13.6 5 835 176284 16 Test (2C): partitioned leader and one follower crash, leader restarts ... ... Passed -- 1.1 3 35 8487 4 Test (2C): Figure 8 ... ... Passed -- 34.7 5 1014 184489 32 Test (2C): unreliable agreement ... ... Passed -- 2.4 5 978 311040 246 Test (2C): Figure 8 (unreliable) ... ... Passed -- 36.7 5 5282 11695881 168 Test (2C): churn ... ... Passed -- 16.4 5 4607 2857762 767 Test (2C): unreliable churn ... ... Passed -- 16.1 5 2179 874603 374 Test (2D): snapshots basic ... ... Passed -- 1.8 3 173 69754 251 Test (2D): install snapshots (disconnect) ... ... Passed -- 43.4 3 1583 427709 388 Test (2D): install snapshots (disconnect+unreliable) ... ... Passed -- 45.7 3 1819 455134 344 Test (2D): install snapshots (crash) ... ... Passed -- 34.5 3 889 272795 377 Test (2D): install snapshots (unreliable+crash) ... ... Passed -- 37.3 3 1041 294722 355 PASS ok 6.824/raft 325.832s
|
我采取了如下的一些手段实现上述两个指标的优化:
- “批”处理start请求.leader维护一个$nextInd$至$nextInd+x$的窗口,只有在这个窗口内的start请求才会被立即发送.我在实验过程中注意到,经常会出现同时start若干个请求,我一开始的实现是对于每一个start请求都立刻进行一轮AE,但实际上没有必要.好处是,如果出现瞬时较多的start请求,会保证一个窗口内的请求立刻被处理(没有真正的批处理带来的一定时延),而剩余的请求则做batch处理(这些请求本身就已经不会被立刻处理了,直接作batch),保证了不会同时产生太多的AE rpc;坏处是对于窗口之外的请求会存在比较高昂的失败代价.
- heartbeat优化.实际上没有必要专门使用empty AE rpc去做heartbeat,如果在heartbeat timeout结束之前已经发送了AE请求,实际上可以不发送heartbeat.这样做可以减轻网络带宽压力.
- 使用Cond减少for check带来的开销.对于appCh的提交我使用了sync.Cond,只有在commitInd可能发生变化的时候才检查是否需要提交到appCh上.
实验整体感觉不难,主要的坑点在于多线程无法调试(实际生产环境中就是分布式集群了,但是可以在一个机器上调试),遇到的主要的问题有:
- 2D实验中的Snapshot和CondInstallSnapshot产生的隐蔽的死锁
- rpc上下文切换后带来的隐蔽错误(处理rpc回复时的rf的各个信息都已经被更新过了,可能有问题!)
- …
对了,还看了 Don’t look up,美国老完蛋了.
感谢阅读!欢迎评论嗷~