CMU15-445数据库系统(Spring Lab)lab笔记
题图:TNO经济面板
剧透警告
在ACM败犬群里听说人均cmu15 445,想补一补缺少的数据库知识(题主在某普通一本,数据库课程很垃圾),就来做做cmu15445。
学cmu15-445前:数据库不就是std::map
学cmu15-445后:存储模型,事务控制,内存装不下
Project #1 - Buffer Pool Manager
Project #1 - Buffer Pool - CMU 15-445/645 :: Intro to Database Systems (Spring 2023)
先上AC图~
总的来说还是比较难的,隔着Page
和DiskManager
头一次写中层代码,需要考虑一堆细节问题。
然后莫名其妙上了rk9,没做啥特别的优化啊
Task #1 - LRU-K Replacement Policy
写一个独立的LRU-K置换策略,有几点需要注意的
- 一般的LRU置换器会遇到sqeuential flooding(简单的顺序for可占用过多的可能不会继续用的缓存导致缓存命中率降低),LRU-K根据第k访问的时间戳进行LRU,这样强制访问次数大于等于k次才能够保留在缓存中,能够缓解这一问题。
- LRU-K置换器内部与具体的
Page
和Buffer Pool Manager
无关。 - 还需要实现
SetEvict
接口,因为当一个页面被占用时我们不希望被逐出缓存。 - 线程安全
Size
返回的是可逐出页的数量而不是缓存中页的总数。
我的思路简单来说就是维护一个std::map<size_t, std::deque<frame_id_t>>
的distance->frame_id_t的数据结构,每一次访问/更新可逐出状态时更新。这样这个数据结构的第一个frame_id_t
就是我们期望的下一个逐出的frame。distance由节点的GetDis
函数计算,用一个队列保存访问时间戳,访问次数小于k时distance为0
。
实现线程安全直接给每一个成员函数上一把成员锁就行了,简单方便(逃
Task #2 - Buffer Pool Manager
Buffer Pool Manager(以下简称BPM)是本次project最难的了,它基于DiskManager
/Page
/我们刚写的LRUKReplacer
向上提供一个以Page
为基本单位的内存池。有几点需要注意的
- 分清楚page/page id和frame/frame id的概念。page是物理存在的一页内存空间,存在于
DiskManager
/BufferPoolManager
中(分别是一页内存的磁盘状态和内存状态);frame是容纳page的一个逻辑概念,存在于BufferPoolManager
和LRUKReplacer
中。DiskManager
根据page id管理磁盘上海量的页;BufferPoolManager
内部才有frame id,是BufferPoolManager
内部内存中的少数page的索引。 - 对于
NewPage
和如果内存中没有对应page的FetchPage
函数,找frame_id要先在free_list_
中找(空闲frame),然后再尝试逐出现有page,逐出前别忘了写回。我在FetchPage时找frame_id顺序搞反了花一个多小时才发现(。 UnpinPage
时对页面置dirty时记得要用逻辑或,不然一个clean的unpin会清除之前dirty的unpin状态。- 一些函数的返回值,
UnpinPage
返回0除了在内存中找不到页时还有pin_count已经小于等于0;DeletePage
在pin_count>0时返回false;NewPage(page_id_t *page_id)
返回nullptr
前还要把*page_id
置0, Guarded
系列函数要在返回前给页上锁- 线程安全
Task #3 - Read/Write Page Guards
实现一个类似unique_ptr
的page guard,析构时自动Unpin。有几点需要注意的
ReadGuard
/WriteGuard
析构还得解读/写锁- 移动构造函数/移动赋值函数移动后别忘了设置nullptr
- 移动赋值函数移动前别忘了Drop自己
Drop
时要检查是否有效
也是实现C++ RAII类的老生常谈的东西了。不过好像不需要考虑a=std::move(a)
这种问题,看来测试代码还是蛮规范的。