CMU 15-445 (2023 Spring)数据库实验p2记录

文章讨论了BPlusTree在插入和删除操作中的优化,包括避免不必要的内存读取,调整分割策略以降低树的高度,以及锁管理的优化,如使用大锁和乐观锁。同时,提到了在处理大量数据时出现的BUG及解决方案,以及在删除时提高并行性的方法。
摘要由CSDN通过智能技术生成
2.1 Insertion
调用
guard_tmp.AsMut<BPlusTreeLeafPage>()
Candidate template ignored: invalid explicitly-specified argument for template parameter 'T'
这是因为BPlusTreeLeafPage也为模板类,需要知道模板参数。 BPlusTree已经替我们声明过了,使用LeafPage即可。

(1) 这里按照书中伪代码来写即可,在进行split时,书中伪代码首先将原先节点读入内存(作为局部变量),进行插入后,再进行分配。此处可以做一个优化,我们可以知道新key插入的索引位置new_idx,再根据new_idx和划分索引的关系进行判断复制起始位置,省去读入内存这一步,直接进行两页直接的复制即可。
(2)另一点需要注意的是,书中划分叶子节点是给的是 ⌈ n / 2 ⌉ \lceil n/2 \rceil n/2,这是因为一共有 n n n个key(算上要插入的key),代码实现中应该修正为 ⌈ ( n + 1 ) / 2 ⌉ \lceil (n+1)/2 \rceil ⌈(n+1)/2。其中 n n n为叶子节点的最大数量。

不按照书中策略来也可行,但一般情况下会导致树的高度比这种策略要高,从而造成性能损失。,假设我就强行指定原节点就保留一个key,剩下的都给新节点,只要向上层插入的key是正确的就可以保证有正确的行为。

  • bug记录
    在ScaleTest测试中出现如下BUG。
    在这里插入图片描述
    正常情况下加锁后,此处会出现一个Writer ID(线程PID)
    在这里插入图片描述
    在这里插入图片描述

从图中也可以看出,中途是没有加读锁的。此bug的诡异之处是在加锁前明明没有Writer ID,但提示出现死锁错误。

目前的解决措施是:将FetchPageWrite都改为FetchPageBasic,write_set_也改为BasicPageGuard的双端队列

(1)原因更新:被这个BUG折磨了一天也没有找到原因。逻辑上感觉没问题。在按照上述解决措施强行往下写后,反过来再改为FetchPageWrite时,偶然发现了原因。
当keys中元素很多时,就会出现此BUG

 for (auto key : keys) {
   int64_t value = key & 0xFFFFFFFF;
   rid.Set(static_cast<int32_t>(key >> 32), value);
   index_key.SetFromInteger(key);
   tree.Insert(index_key, rid, transaction);	// 这里都为FetchPageWrite
   std::string str = "/home/hsfw/bustub-zhang/build/my-tree.dot";
   tree.Draw(bpm, str);							   // 这里都为FetchPageBasic
 }
2.2 Delete

这块代码量比较多,但实际上比较简单。

2.3 优化措施
  • 将缓存管理改为大锁的形式,再结合乐观锁机制(乐观锁有所提升但效果并不明显),最终QPS如下图所示:最高能到14W左右。即使将叶子节点的MinSize改为1效果也没有提升(本地测试会提升很多)。
    在这里插入图片描述

  • WARN - page not exist的错误就是P1中提到的细节。

  • 使用单个读写锁的方式,但会导致锁竞争时间急剧上升(本地测试需要200s,加大锁的为十几秒)。因此采用了多个读写锁的方式,在project 1中也能获得2w多分,与简单的大锁方式有显著提升,但在P3中,还没有大锁得分高,比较奇怪。感觉加大锁和使用细粒度锁的方式区别不大。

    知乎上有人推荐,在UnpinPage中实现写回磁盘的操作,也可以避免这种情况的发生,但感觉会造成多次的写入,因为UnpinPage的调用比较频繁。

  • 删除操作中,如果发生Redistribution时,也可以提前释放祖先节点以增加并行性(不包括当前父节点)。

  • 避免在Internal中查找page_id,因为其使用的是线性搜索的策略,比较慢。在插入时需要知道新节点插入到父节点的哪个索引处,在删除时也需要找到’借用数据’的 N ′ N' N,这些都需要知道当前节点在父节点的索引。可以在从根到叶子节点遍历时,保存下各步的索引,方便后续使用。

  • (优化细节)再将PageGuard转换为实际指针时,尽量多使用As,因为AsMut会设置脏页,弹出时可能会需要写入磁盘。之前在插入和删除的遍历操作中,我都使用了AsMut,现在统一改为As。

  • 在实现乐观锁的过程中,因为叶子节点需要加写锁,中间节点需要读锁。因此提前得知树的高度,这样才能确定需要加哪种锁。
    维护树高比较简单,根结点发生分裂或合并时才会改变树高,而只有持有header写锁的情况下才会修改tree_height_,此处需要注意,如果直接在遍历时直接使用tree_height_就会造成问题。因为tree_height_变量很有可能在另一个线程中发生了改变,如果依据tree_height_遍历的话就会出现段错误,访问到无效内存。正确的方式是在持有header读锁时,就先把tree_height_变量保存为临时变量.

  • 插入时会重分配的思路

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值