FAST'20
Zhichao Cao, University of Minnesota, Twin Cities, and Facebook; Siying Dong and Sagar Vemuri, Facebook; David H.C. Du, University of Minnesota, Twin Cities
本文主要关注真实生产环境下的RocksDB的负载特征,以及当前常用的Benchmark——YCSB能否在测试的时候还原生产环境的负载特征展开。主要收集了Facebook中三个使用RocksDB作为底层存储引擎的组件的trace数据,分别是1)UDB:用于MySQL中用来存储社交图数据,使用RocksDB作为底层存储;2)ZippyDB:用于存储分布式对象存储的元数据的分布式KV;3)UP2X:用于存储AI/ML数据的分布式KV。然后根据收集的数据分析到达RocksDB的各种负载特征,并与YCSB的测试结果进行对比,得出YCSB并不能准确代表生产环境的结论,而这其中最关键的因素是YCSB没有考虑到真实负载所表现出的key space loaclity(我的理解是key range的局部性,即热点key会按照key range进行分布,而不是随机分布在整个key空间内)。最后根据收集到的负载特征进行建模,自己构建了一个benchmark,达到了更加准确地还原生产环境负载的目的。
问题背景 & Motivation
- 现有的关于真实workload的描述以及分析的工作很少,然而KV Store的性能又跟workload十分相关
- 针对KV Store的workload的分析方法与传统的存储系统(诸如块存储系统或者文件系统)有较大区别
- 使用benchmark测试KV Store的性能的时候,并不知道benchmark生成的workload能否代表真实的workload
三个系统的介绍
UDB
UDB是Facebook用来存储社交图数据(社交关系图)的组件,向上提供MySQL接口,UDB上层有读cache,read miss以及write数据都通过UDB处理,数据通过MyRocks转换为KV并写入RocksDB。数据主要由object和associations构成,分别代表图的点和边。
UDB的RocksDB通过6个ColumnFamily来存储不同类型的数据,分别是:
Object:存储object数据
Assoc:存储associations数据
Assoc_count:存储每个object的association数
Object_2ry,Assoc_2ry:object和association的secondary index
Non-SG:存储其他非社交图数据相关的服务
ZippyDB
ZippyDB是一个分布式的KV Store,并通过Paxos协议保证一致性,KV数据被分为不同的shard,每个shard通过一个单独的RocksDB实例进行存储。ZippyDB主要用于存储上层的对象存储的元数据。
UP2X
UP2X是一个分布式KV Store,在Facebook中主要被用来存储AI/ML计算的数据集,AL/ML的数据集的特点是经常被Update,如果先Get数据再Put则效率很低,所以UP2X主要使用RocksDB的Merge来实现Read-Modify-Write(Merge的实现就是将要修改的数据的delta数据put进去,避免随机读开销,delta数据在compaction的时候会被删除)
主要的分析工具
- Tracing:收集RocksDB的操作,主要记录操作类型,CF,key,value,时间戳
- Trace Replaying:通过db_bench回放trace file
- Trace Analyzing:分析workload,但是因为生产环境的workload涉及隐私不能公布,所以最终输出内容是一些描述,主要包括:1)每个CF内KV的操作次数,操作类型;2)KV size的统计数据;3)KV数据热度(Popularity?);4)key空间的局部性;5)QPS统计
- Modeling and Benchmarking:对workload进行建模
workload分析
workload常规统计分析
查询操作的组成
结论:Get在UDB与ZippyDB中占比最多,UP2X最多的操作是Merge
UDB:
- Get/Put/Iterator是占比最高的操作,而且主要是在Object / Assoc / Non_SG这几个CF
- Object_2ry中Iterator为主要操作
- 没有重复update操作的CF,如Assoc_2ry通过Single_Delete删除数据
ZippyDB:
- 只有一个CF,Get : Put : Delete : Iterator = 78 : 13 : 6 : 3
UP2X:
- Merge : Get : Put = 92.53 : 7.46 : 0.01
KV热点分布
结论:UDB和ZippyDB中大部分KV数据是冷数据
UDB:
上图是UDB的Get与Put操作的KV数据访问的CDF图。对于Get,除了Assoc之外,其他CF的数据60%或者以上的数据都只被访问了一次。对于Put,超过75%的数据都只被访问一次,Put次数超过10的数据仅占不到2%,所以UDB中的KV数据大部分很少被Update。
上图是UDB的Iterator操作的start key统计以及scan length统计,大部分的scan起始key都只被访问一次,没有表现出局部性,而scan长度超过65%都只有1次,但也有少部分的scan长度非常长。
上表是统计的UDB在一段时间内访问的unique key的占比,可以看到24小时内访问的key最高不超过3%,而14天内访问的key最高不超过15%,因此对UDB来说,大部分数据在RocksDB内都是冷数据。
ZippyDB:
对于ZippyDB,大约80%的key只被访问一次,1%的key被访问超过100次,因此表现出较好的局部性。约73%的数据只被Put一次,访问次数超过10次的数据仅有0.001%,因此Put的局部性较差。Iterator操作的阶梯式增涨主要来源于ZippyDB的作为对象存储元数据存储的使用场景,当上层访问一个对象的时候需要对元数据做scan,那么总是会从这个对象的起始位置开始扫,这个起始key就总会被访问。
UP2X:
对于UP2X,Get与Merge的访问次数分布较广,并且访问次数高的数据占比比较大。
QPS
结论:UDB的部分CF表现出较强的昼夜模式,这跟社交网络用户习惯相关,ZippyDB和UP2X没有表现出这样的特征
KV size分析
结论:key size通常比较小,value size的大小与具体数据类型有关,key size的标准差较小但是value size较大, UDB平均的value size比其他两个例子要大
上表是三个使用场景的平均KV size以及KV size的标准差,可以看到三种情况的平均key size都较小,并且标准差都较小,而UDB的value size较大,并且三种情况的value size的标准差都较大。
上图是三种使用情况的KV size的CDF图
UDB:
对于key size,除了Assoc_2ry其他的CF的key size都不超过40B,而Assoc_2ry因为其作为secondary index特殊的构成,所以会有超过50B的key存在。value size变化较大,Object这个CF主要用于存储用户数据,所以value size从16B到10KB都有,而像是Assoc只存储Object的联系,所以value就相对较小只有100-200B左右。Assoc_count只存储Object的边的个数,数据格式固定,所以value大小也固定20B。
ZippyDB:
大部分的key size分布于[48, 53]以及[90,91]这两个范围内,这与其作为Object storage的元数据存储工作场景有关。对于value size,其分布较为广泛,存在较大的value,但超过90%的value都只有34B左右。
UP2X:
超过99.99%的Get的key只有9B,merge的key size有6%为9B,94%为17B,17B的key都会被compaction清理掉,所以Get只会访问到9B的key。
Key space以及访问模式分析
统计方法:对key按递增顺序编号,然后统计每个key的访问次数绘制heat-map,统计key的访问时间绘制time-series
结论:heat-map可以看到三种DB的访问具有较强的key space locality,也就是热数据往往聚集分布在某些key space内。UDB的Delete/Single Delete以及UP2X的Merge的time-series表明其访问具有时间局部性
UDB:
上图是UDB的Object和Assoc_count的Get操作的heat-map,其中红线表示不同的MySQL table。可以看到部分MySQL table访问热度较高,而部分基本没有访问,表现出较强的key-space locality。
上图为UDB的Delete以及Assoc_2ry的SingleDelete的time-series图,一个明显的规律是,当个key被删除的时候,它临近的key也会很快被删除。
总结:KV数据访问并不会随机分布在整个key space,而是根据key-space进行区分,部分key-space访问热度较高,这部分数据占比较小,而部分基本没有访问。属于同一个MySQL table的数据物理上也相邻存储,部分SST和block具有较高的热度,可以考虑基于这个优化compaction以及cache。
ZippyDB:
可以看到ZippyDB的访问具有较为明显的key-space locality
UP2X:
UP2X的访问也表现出较强的key-space locality,只有后半段key被访问,而前半段key基本没有访问。merge的time-series表现出来merge每一段时间内会集中访问一个range内的key。
建模以及测试
YCSB对比测试
目前YCSB是最常用的一个用于测试NoSQL数据库性能的一个benchmark,YCSB能够设置各种参数来生成不同的workload,但是YCSB生成的workload是否能让下层的IO符合生产环境的情况是个问题,所以本文做了测试。
主要对比参数:block reads,block_cache_hits,read bytes,write_bytes
对比对象:YCSB与真实ZippyDB的trace
YCSB设置:设置workload a/b的参数尽量靠近ZippyDB(YCSB不能设置compression ratio只能用默认设置)
测试结果:
- YCSB的block read是真实trace的7.71×
- read byte是真实trace的6.2×
- write bytes是真实trace的0.74×
- block cache hit是真实trace的0.17×
总的来说YCSB测workload相比于真实的trace会造成更大的读放大以及更小的写放大,同时cache命中率也更低。其中的主要原因在于忽略了真实workload的key space locality,YCSB中的热点数据随机分布在整个key范围内,访问这些key会造成大量的block读并且被缓存,而这些block可能仅包含较少的热数据,cache大小有限所以降低了cache命中率。对于put,随机的热点分布使得数据在前几层就被compaction掉,所以造成更小的写放大,update的数据具有key space locality,那么新数据会不断写入,就数据一直往下compact直到新数据遇到旧数据才会被处理掉。
建模
- 选取key range 大小:sst大小
- 建模
- 首先将key size,value size以及QPS套到模型里面去
- 然后处理kv的访问次数,访问顺序以及每个range的平均访问次数
- 测试:
Prefix_dist:基于建模构建的workload
Prefix_random:随机将冷热数据分布到各个key-range
All_random:热数据随机分布到整个key space
All_dist:热数据集中放置
根据实验结果,本文对YCSB提出几条建议:
1)增加基于key range的分布
2)增加throughput的控制
3)增加KV size分布的控制
4)增加compression ratio的设置
本文又针对UDB的Assoc workload进行了建模并与真实workload进行了对比: