【CMU 15-445】Proj2 Hash Index

EXTENDIBLE HASH INDEX

CMU-15445汇总

本文对应的project版本为CMU-Fall-2023的project2

由于Andy要求,本博客只提供思路,不会公开任何代码

本博客默认读者已懂可扩展哈希的插入删除理论知识,若不清楚流程可以看这篇博客

通关记录


目前的rank还比较低,后续会优化下

Task1 Read/Write Page Guards

Task1要求实现三种PageGuard类,分别是BasicPageGuard、ReadPageGuard和WritePageGuard。三种PageGuard类都使用RAII的思想保护Buffer Pool Manager的缓冲页,防止用户遗漏调用Unpin方法导致缓冲页被固定无法驱逐,与智能指针相似,当PageGuard对象生命周期结束时,在析构函数中调用Unpin方法来确保释放缓冲页;进一步的,ReadPageGuard和WritePageGuard还会保护缓存页的读写一致性,且避免死锁(若其他时候后忘记解锁,则在析构时解锁)。当然,PageGuard类也要对外提供方法用于手动释放。实现Task1中的类会为后面Task3的可扩展哈希做铺垫,后面用到的时候就知道多好用了。

三种类中的主要成员有:

  • BufferPoolManager *bpm_
  • Page *page_
  • bool is_dirty_

三种类中主要实现的方法有:

  • 三种PageGuard的移动构造函数、移动赋值运算符、Drop方法与析构函数
  • BasicPageGuard类中的UpgradeRead()函数和UpgradeWrite函数

在实现完三种PageGuard类中的方法后,我们需要使用这三种PageGuard,实现BufferPoolManager类中的FetchPageBasic、FetchPageRead、FetchPageWrite和NewPageGuarded函数。

移动构造函数

移动构造函数的实现比较简单,将参数中的成员复制到待构造对象,然后清空参数的所有成员即可。

Drop方法

Drop函数为对外提供的释放页接口,它需要做的事情就是调用一下BufferPoolManager的UnpinPage函数,将page_中维护的页释放(如果有的话);在ReadPageGuard和WritePageGuard中,则还需要解锁(读锁或写锁)

移动赋值运算符

移动赋值与移动构造差不多,不同点在于如果运算符左右两边的对象不同,需要将左边对象维护的缓冲页释放掉,并解开读锁或写锁,再将右边对象中的成员复制到左边,最后情况右边对象。

析构函数

调用Drop方法释放页并解锁即可。

UpgradeRead函数

利用BasicPageGuard中的成员构造出一个ReadPageGuard,并加写锁返回即可。UpgradeWrite同理。

FetchPageBasic、FetchPageRead、FetchPageWrite、NewPageGuarded

这几个方法的实现都比较简单,先调用bpm中对应的函数FetchPage或NewPage,然后构造BasicPageGuard或者ReadPageGuard或者WritePageGuard返回即可。值得注意的是在返回后两种之前需要先加RLatch或WLatch。

Task2 Extendible Hash Table Pages

Task2要求实现三种可扩展哈希中使用的数据结构,也就是三层结构中每一层的页面布局。分别是HeaderPage、DirectoryPage和BucketPage,如下图:

HeaderPage

HeaderPage的成员包括第二层DirectoryPage的PageId数组,以及一个位深度x,PageId数组的大小为 2 x 2^x 2x,且使用哈希值的最高有效x位进行索引。如位深度为2,32位哈希值为0x5f129982,最高有效位前2位为01,则会被索引到01对应的DirectoryPage上。

DirectoryPage

DirectoryPage的成员包含第三层的BucketPage的PageId数组,全局位深度global_depth和每一个Bucket的局部位深度local_depth数组,PageId数组的大小为 2 g l o b a l _ d e p t h 2^{global\_depth} 2global_depth,且使用哈希值的最低有效global_depth位进行索引。如全局位深度为2,32位哈希值为0x51129982,最低有效位后两位为10,则会被索引到10对应的BucketPage上。

BucketPage

BucketPage的成员包含一个Key-Value数组,以及数组大小size。被索引到该Bucket的Key-Value会追加到该Bucket上进行存储。

以上三个类的实现都比较简单,跟着头文件中的注释实现就是了。值得注意的是DirectoryPage的IncrGlobalDepth方法,在增加global_depth的同时还需要设置PageId数组中新增的PageId(具体见插入时的操作,见引言中的理论博客)

Task3 Extendible Hashing Implementation

Task3要求利用Task1和Task2中实现的类与方法,实现可扩展哈希类DiskExtendibleHashTable的方法,主要就是可扩展哈希的插入和删除。

理论知识见引言中的理论博客,大致跟着理论实现就行,需要注意的细节有:

  • 不需要使用的PageGuard需要及时释放,防止页面缓冲池满了
  • 关于插入,当Bucket满了导致分裂,可能分裂后的重新分配仍会产生Bucket满的情况,此时需要继续分裂,直到可以正常插入新tuple,我的实现方法是递归,也可以循环实现。
  • 关于删除,我删除空白页的方法是,每当删除一条tuple后,检查当前是否有空白的Bucket,如果有,判断global_depth与该Bucket的local_depth是否相等,相等则删除,不相等则后续缩短DirectoryPage之后再考虑;删除tuple导致的DirectoryPage缩短也需要循环的进行,直到无法再缩短为止。

Task4 Concurrency Control

Task4要求保证可扩展哈希的并发安全性,其实在Task3中如果FetchPageGuard和NewPageGuard使用正确的话,自然就保证并发安全性了,故不需要额外考虑这个Task。

相关推荐
lsc_blog8 小时前
年终自评别贴提交记录,把技术产出讲成变化
数据库·面试·职场和发展·跳槽·求职招聘
Zan16 小时前
SQLite + SQLx 实战:为什么已经发布的数据库迁移文件,绝对不能再修改?
数据库·后端·面试
这个DBA有点耶17 小时前
数据同步的三个工程底线:DDL同步、断点续传、一致性校验——开源CDC的边界与KFS的路径
数据库·dba·代码规范
SelectDB18 小时前
列式存储为什么适合分析类负载?原理、限制与日志场景应用
数据库
数据库小学妹18 小时前
MySQL大字段治理:行溢出机制、覆盖索引与拆表
数据库·mysql·性能优化
爱吃奥利奥_wen19 小时前
SQL Server 基础语法详解:从建库建表到约束与增删改查
服务器·数据库·oracle
这个DBA有点耶20 小时前
MySQL 8.0不可见索引:删索引怕出事,不删又拖慢写入——中间态怎么用?
数据库·mysql·代码规范
测试界的小码农21 小时前
【图数据库实战进阶(01):从零认识 Neo4j,搭建第一个图数据库项目】
数据库·neo4j
x_SpiderMan21 小时前
搭建Oracle RAC 集群
数据库·oracle