EXTENDIBLE HASH INDEX
- 通关记录
- [Task1 Read/Write Page Guards](#Task1 Read/Write Page Guards)
- [Task2 Extendible Hash Table Pages](#Task2 Extendible Hash Table Pages)
- [Task3 Extendible Hashing Implementation](#Task3 Extendible Hashing Implementation)
- [Task4 Concurrency Control](#Task4 Concurrency Control)
本文对应的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。