EvenDB: Optimizing Key-Value Storage for Spatial Locality

Eurosys'20

Eran Gilad Yahoo Research, Israel Edward Bortnikov Yahoo Research, Israel Anastasia Braginsky Yahoo Research, Israel Yonatan Gottesman Yahoo Research, Israel Eshcar Hillel Yahoo Research, Israel Idit Keidar Technion and Yahoo Research, Israel Nurit Moscovici∗Outbrain, Israel Rana Shahout∗Technion, Israel

本文是雅虎研究发表在Eurosys 2020上的文章,主要针对KV应用场景中的Spatial Locality的负载特征,设计了一个新的KV Store。现在的KV Store作为底层存储引擎,通常Key是多个属性组合起来的值,某些主属性的热点访问会导致KV Store中某个key range的数据成为访问热点,也就是所说的Spatial Locality。这个问题在FAST 2020上的RocksDB workload分析的文章中也有提到。本文针对这个Spatial locality特征,提出LSM-Tree不适用于这种场景,然后重新设计了一个新的结构并实现了EvenDB。

背景

Spatial locality

KV Store常作为应用或者其他存储系统的底层存储引擎,上层应用的数据在被转换成KV格式的时候,key往往会由多个属性组成,所以多个key也可能会有相同的前缀,比如一个表的数据,在存储到KV之后可能table-id就会作为key的一个前缀,这个表的所有数据前缀都相同。这样的影响是上层应用出现访问热点的时候,底层KV store可能热点会集中在某个key range内,也就是spatial locality。而数据热点现象在KV workload中也很常见,下图是本文做的一个统计,统计的是一个移动app数据分析平台的trace,横轴是app id,可以看到大约1%的app占了约94%的数据访问。

spatial locality

LSM-Tree的不适应

对于这种拥有spatial locality的workload,本文认为现在最常使用的LSM-Tree结构不再适用,主要的理由有三点:

  • LSM-Tree中主要按照时间顺序组织数据,会导致同一个key range的数据分散到不同的SST内;
  • LSM-Tree的compaction会消耗大量的带宽并产生读写放大;
    • 写放大缩短SSD这类存储设备的寿命;
    • compaction会将冷数据不断地进行读写;
  • 热数据会被flush到盘上,降低了数据访问效率。

EvenDB设计

EvenDB的设计核心思想是对于前缀相同的数据以chunk为单位进行组织,并以chunk为单位进行缓存或者写盘。

Design Goal

  • 针对spatial locality优化;
  • 减小写放大;
  • 针对热点数据可以完全存储到内存的应用场景;
  • 支持快速恢复。

结构

overall structure

EvenDB的组织数据的基本单位是chunk,当chunk被写入磁盘的时候成为一个funk(file chunk),被缓存在内存中的时候为一个munk(memory chunk),chunk以链表形式组织并通过index将key映射到对应的chunk,每个chunk代表一个key range。

Funk

funk是chunk在磁盘上的存储形式,由一个SSTable文件和一个log组成,新数据追加到log中并通过compaction合并到SST中。当log大小达到阈值之后,触发rebalance进行合并。由于log中查找数据是顺序查找,比较慢,所以每个funk有一个bloom filter用来减少不必要的log查找。

Munk

munk是chunk缓存到内存中的形式,数据通过一个数组链表来组织,数据首先根据前缀划分到各个数组中,前缀按照大小排序,然后同一个数组下标下的数据通过链表组织。新数据插入的时候先追加到log中,然后就追加到对应前缀的链表里面。查找的时候,首先在数组中进行二分查找,然后再在链表中顺序查找。

Reorganization

  • munk rebalance:对munk数据进行组织;
  • funk rebalance:频率较低(因为funk都是冷数据);
  • split:创建新的chunk。

加速磁盘访问

对于没有被cache的chunk的访问,本文提出两点优化措施:

  • 缓存单个KV的row cache

当某些chunk只有少量KV是访问热点,缓存整个chunk就没必要了,所以EvenDB也设计了一个KV为单位的row cache来缓存单个kv(rocksdb里面也有)。

  • bloom filter减少log访问

Concurrency and atomic scan

EvenDB支持原子的scan操作,并发控制通过一个简化的multi-version实现。get操作不需要锁和等待,put操作需要等待rebalance操作,scan可能需要等待put操作。

EvenDB中有一个全局的version number,被称为Global Version。这个version是针对scan的并发控制,put操作不需要修改。

对于scan,在执行scan之前需要创建一个snapshot,此时会访问global version并将其增加1。put操作将不允许修改version小于global version的KV数据。

对于旧版本的管理,EvenDB通过一个Pending Operation Array来记录每个活跃中的线程。当Array中的version不再被访问的时候就表明对应的旧版本数据可以被删除了。

对于不同操作的同步,put于scan之间可能存在的竞争通过Pending Operation Array处理,put与rebalance的竞争通过rebalance lock来处理。

基本操作

EvenDB中所有的操作之前都需要先定位key所在的chunk,这个操作通过内存中的index来进行。

put

  1. 通过index定位chunk;
  2. 获取rebalance lock;
  3. 获取global version,将要修改的key在po中注册;
  4. 写数据到log,如果有munk则更新munk;
  5. 如果key在row cache中,则同时更新row cache(row cache不服务scan,所以只保存最新的数据);
  6. 从po中注销key并释放rebalance lock。

对于同一个GV下针对同一个key的多次更新,EvenDB给每个chunk设置了一个counter,这样就能通过对来对每个put操作进行排序。

scan

  1. 获取global version并将其加1;
  2. 在PO中注册scan的key range和GV;
  3. 等待小于GV的put操作的完成;
  4. 读数据;
  5. 注销;

Rebalance

Rebalance可以移除无效数据并提升数据有序性,当munk数据超过一定的阈值之后则触发Rebalance,Rebalance是在一个新的地方创建,过程中不会动原来的数据,避免影响并发操作。Rebalance操作需要获取锁避免与put的竞争,但是不会影响get和scan。

Funk的rebalance会创建新的sst和log。如果funk有对应的munk,则只对munk进行rebalance,然后再将munk flush成SST即可。

Split

如果munk的数据在rebalance的时候数据量超过阈值则进行split,此时当前chunk会被拆分成两个chunk。

split分成两个阶段进行:

阶段1:此阶段获取rebalance lock,不允许put

  1. 拆分munk;
  2. 创建两个新的chunk并分别引用两个munk,但是共享同一个funk;
  3. 将新的chunk插入chunk list(此时已经可以访问,可以通过旧的chunk访问到新的chunk);
  4. 更新index。

阶段2:拆分funk,此阶段释放锁,put通过新的munk进行服务。

你可能感兴趣的:(EvenDB: Optimizing Key-Value Storage for Spatial Locality)