HotRing 热点感知KV索引 有序环哈希
论文阅读笔记
【FAST'20】HotRing: A Hotspot-Aware In-Memory Key-Value Store
https://www.usenix.org/system...
huazhong
简介
背景、动机
- 热点问题很普遍。如:阿里生产环境中,50%~90%的访问,只涉及1%的数据。
- 内存KV结构的热点问题不被重视,大多数KV结构不能感知热点。
- 目前内存KV结构减少热点访存的方法有局限:CPU cache太小;rehash(为减少冲突)不适合本身巨大的表,且性能提升有限。
主要思想
设计热点感知的内存KV结构——有序环哈希结构。
主要挑战及应对方法:
- 热点转移:把冲突链改为环。头节点移动到热点项;两种策略检测热点转移。
- 高并发访问:使用无锁结构,实现插删,并扩展到热点特定操作(热点转移检测、头指针移动、有序环rehash)。
贡献
- 支持快速访问热点数据。
- 有轻量的运行时热点转移检测策略。
- 无锁机制,支持高并发访问,贯穿热点特定操作(热点转移检测、头指针移动、有序化rehash)。
- 真实环境测试,高度不平衡的负载实验,2.58x。
有序环
把冲突链改为有序环结构。
$order_k = (tag_k, key_k) $。 对冲突环以 tag-key 方式排序。
查找。不命中:
不命中,平均只需要查找一半的元素(冲突链则需要遍历整条)。
热点转移识别
两种衡量:accurancy、反应延迟。
把头指针移动到热点项。
随机移动
每隔R个请求,若第R个请求不是访问热点项(指头指针所指元素),则将头指针指向当前访问项。不需历史统计数据,移动到潜在热点项。
实现简单,反应时间较快。
R小,反应延迟可能小,但会造成频繁的指针移动,低效。实验表明,R = 5 较好。
缺点:
- 识别精度低。
- 负载倾斜不明显时,该方法低效。
- 若冲突环有多个热点,无法应对,频繁的移动反而影响其他操作
统计采样
识别精度更高,反应延迟稍长。可以应对环上多个热点的情况。
索引格式
利用头指针、item的剩余16bit空间。
active:控制统计采样。 total count:统计采样时环的总访问次数。
rehash:控制rehash。 occupied:控制并发,保证并发访问的正确性。 counter:某一项的访问次数。
address:下一项的地址。
统计采样:每R个请求,若第R个请求不是访问热点项(指头指针所指元素),则触发采样:active位置1(需CAS原语),之后对该环的访问都会被计数(total counter 和 counter),采样的次数等于该环item的个数。
热点挑整
计算每一项 $t$ 的income:$ W_t = \sum \limits_{i=1}^N \frac{n_i}{N} \cdot [(i-t)mod k ] $. ($n_i$:项$i$ 的计数, $N$:总计数 $k$ : 环长)
$W_t$ 衡量的是:将头指针指向 $t$ 后,该环的平均访存。
选择$W_t$ 最小的项$t$,作为新热点。 使用CAS 原语来移动头指针。最后重置计数器。
可处理多个热点。
RCU
RCU:read-copy-update。小于8B的value,可用CAS原地更新。大于8B的,需用RCU更新。这时需遍历整个环来获取前驱结点,因而,写密集热点会让它的前驱变热。因此,需修改统计采样法。
对RCU更新,增加其前驱的counter,而非其本身counter。这可助头指针指向写密集热点的前驱,加速RCU修改。
热点继承
头节点发生RCU更新或删除。
- 若环只有一项,用CAS更新头指针即可。
- 若环有多项:若头节点RCU更新,将头指针指向新版的头(因为它被再次访问的概率高);若头节点删除,则头指针移到下一项,
并发
- 读:不需要额外机制,无锁。
- 插入:创建新节点,修改前驱的next指针。使用CAS操作,防止两个线程同时修改同一个next item address,一个成功一个失败,失败的需重新执行操作。
- 更新:
- 小于8B,CAS更新。
RCU更新则需要创建新节点,替换旧节点,有并发问题。(图8)
- 进程1在B后插入C,进程2将RCU更新B。若用CAS,则会成功,因为它们修改的是不同的指针。出错。
- 进程1RCU更新B,进程2RCU更新D。若用CAS,则两个操作都会成功,而D‘将无法访问,出错。
- 进程1RCU更新D,进程2删除B。将导致D’无法访问,出错。
- RCU更新,更新项occupied位置1。如插入&更新,要RCU更新B,需将B的occupied置1,此时C插入会失败,稍后,A的next指针指向B‘,B’的occupied为0。
- 删除。被修改项的occupied置1,改完其前驱的next后再置回0。如,删除B,B的occupied置1,此时找D的前驱会不命中,D更新失败,需重来。
头指针移动。移动头指针时,需检查其他操作,防止头指针指向无效项;删除或更新时,需要检查头指针是否指向它。
- 将头指针移到一项时,将该项的occupied置1,防止该项更新、删除。
- 头节点更新,将新头的occupied置1,再移动头节点。
- 头节点删除,将头、头的后续的occpuied置1,再移动头节点。
无锁 rehash
传统rehash,由负载因子触发。这不适用于热点处理。HotRing 使用访问开销(检索一个item的平均访存次数)来触发rehash。(怎么计算 采样还是?)
初始化
创建rehash线程,初始化一个两倍大小的哈希表,共用tag的最高位。用来hash的部分从 k 位变为 k+1位,tag则减少一位。设hash value有n bit,旧表的tag范围[0, T),T = 2^(n-k),则两个新头指针分别对应[0, T/2),[T/2, T)。
同时,创建一个rehash node,包含两个子哈希项,其tag分别为0、T/2, rehash 位均为1,里面不存有效KV对。
分割
rehash线程将两个rehash item插入到环中,插入后,新表被激活。如图,分别插入到B前、E前。
这样,此时通过old head访问,可以通过检查rehash位跳过rehash item,后续的通过new head访问新表也能正常。
删除
过渡期:等待所有通过old head访问的操作结束。然后就可以删除旧表和 rehash node,真正拆成两个环。
注意,过渡期只会阻塞rehash 线程,不会阻塞access线程。
(new head1指向old head,new head2 指向tag > T/2的下一项吗?)
实验
baseline:chaining hash(用HotRing的无锁机制), FASTER, Masstree, Memcached。
HotRing-r(用随机采样),HotRing-s(用统计采样)。
- 单线程、64线程下,比较吞吐量。
- 不同冲突链长度(key-bucket率从2到16),比较吞吐量。
- 负载倾斜率(不同zipfian 分布参数,记为$\theta$ ),比较吞吐量。
- 用YCSB产生写密集负载,进行RCU实验,测吞吐量。
- 测break-down cost。
其他:(用两种HotRing,chaining hash)
- 改变负载,即发生热点转移,通过测吞吐量,分析反应延迟。
- 用read miss负载,测吞吐量,以分析三种机制处理read miss的性能。HotRing更好,因为它们平均只需要查找一半的元素,而冲突链需要遍历整条。
- 不同R下,测吞吐量,以找最优的R。
- 尾延迟。统计采样周期最后一个线程需要计算,会有尾延迟。不同$\theta$下,测access 延迟随acess ratio的变化。
- 检验rehash。250百万key,key-bucket ratio = 8。YCSB产生50%读、50%插入的负载,模拟哈希表增长。这里,I、T、S分布表示rehash的初始化、过渡期、分割。可知,rehash操作,能使得吞吐量恢复到增长前的水平。短期的陡降是因为新哈希表短期的热点感知匮乏。
后续工作
随机移动针对只能应对冲突环上单个热点的情况,随机采样可处理多个热点。很多情况下,可以通过rehash拆分热点。但是极端情况下,这会失效。作者称将继续探索。
个人体会
链改环,采样,设置几个flag控制并发,本文的设计思路很清晰自然,虽然是常规的想法,但整合成了漂亮的系统设计。
实验做得很充分,通过各种对照,分析各项设计带来的性能提升,值得学习。
一些困惑:
- 这种结构有没有更好的热点转移识别方法?
- RCU更新,需要遍历环来获得前驱,理论上来看对于高并发的RCU写密集的负载不友好吧(实验结果倒好),但是设置前向指针又浪费空间得不偿失。
- Rehash过渡期(即插入rehash node之后,分割前),发生RCU 更新需要阻塞吗?
- 是否可以把统计采样周期末尾的额外计算交给专门的线程,以减少尾延迟?
- 这种热点感知方法可以迁移到关系数据库、或者LSM结构上吗?
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。