P1 - Buffer Pool -- Task #3 - Buffer Pool Manager Instance

The BufferPoolManagerInstance is responsible for fetching database pages from the DiskManager and storing them in memory. The BufferPoolManagerInstance can also write dirty pages out to disk when it is either explicitly instructed to do so or when it needs to evict a page to make space for a new page.

介绍了 BufferPoolManagerInstance 的作用

Page objects are just containers for memory in the buffer pool and thus are not specific to a unique page. That is, each Page object contains a block of memory that the DiskManager will use as a location to copy the contents of a physical page that it reads from disk. The BufferPoolManagerInstance will reuse the same Page object to store data as it moves back and forth to disk. This means that the same Page object may contain a different physical page throughout the life of the system. The Page object's identifer (page_id) keeps track of what physical page it contains.

Page 是一个类对象,用来存储数据,其中有一个属性 page_id 定义了当前容器存储的物理页面。

这里主要是要分清 page 和 physical page,physical page 是存储在磁盘上的,page 是在内存里用来装 physical page 的,相当于一个框。当 physical page 在 buffer pool 里面的时候相当于和 page “合体”了,当要换页的时候需要"解体"将里面的 physical page 写回磁盘。

Each Page object also maintains a counter for the number of threads that have "pinned" that page. Your BufferPoolManagerInstance is not allowed to free a Page that is pinned. Each Page object also keeps track of whether it is dirty or not. It is your job to record whether a page was modified before it is unpinned. Your BufferPoolManagerInstance must write the contents of a dirty Page back to disk before that object can be reused.

每一个 page 都有一个计数器,使用前要 pin,可能有多个进程同时在使用。使用完后如果脏了要写回后才能让这个 page 装其它的 physical page。

It will use ExtendibleHashTable for the table that maps page_id to frame_id. It will also use the LRUKReplacer to keep track of when Page objects are accessed so that it can decide which one to evict when it must free a frame to make room for copying a new physical page from disk.

ExtendibleHashTable 负责将 page_id 转为 page_id,frame_id的 pair,注意 page_id 是磁盘上用来标识物理页的。通过哈希表可以得到某个 page 现在在不在 buffer pool。

LRUKReplacer  负责对 buffer pool 中的所有 page 访问情况进行记录,所以大小和 buffer pool 一样,frame_id 本质上就是 buffer pool 里 page 所在的位置,这也解释了 ExtendibleHashTable 本质就是通过物理页号得到该页应该存放在 buffer pool 中的哪个位置。

看一下 Page 类:

主要用到的有:

page_id_:装的物理页面页号

pin_count_:pin计数

is_dirty_:是否被修改过,决定了换出时是否需要写回磁盘

成员变量:

  /** Number of pages in the buffer pool. */
  const size_t pool_size_;
  /** The next page id to be allocated  */
  std::atomic<page_id_t> next_page_id_ = 0;
  /** Bucket size for the extendible hash table */
  const size_t bucket_size_ = 4;

  /** Array of buffer pool pages. */
  // 使用了一个数组存储 bufferpool 中的 page,偏移量即为 frame_id
  Page *pages_;
                  
  /** Pointer to the disk manager. */
  DiskManager *disk_manager_;
  /** Pointer to the log manager. Please ignore this for P1. */
  LogManager *log_manager_;
  /** Page table for keeping track of buffer pool pages. */
  ExtendibleHashTable<page_id_t, frame_id_t> *page_table_;
  /** Replacer to find unpinned pages for replacement. */
  LRUKReplacer *replacer_;

  /** List of free frames that don't have any pages on them. */
  std::list<frame_id_t> free_list_;

  /** This latch protects shared data structures. We recommend updating this comment to describe what it protects. */
  std::mutex latch_page_id_;
  std::recursive_mutex mutex_;

成员函数:

//构造函数
BufferPoolManagerInstance::BufferPoolManagerInstance(size_t pool_size, DiskManager *disk_manager, size_t replacer_k, LogManager *log_manager)
    : pool_size_(pool_size), disk_manager_(disk_manager), log_manager_(log_manager) {
  // we allocate a consecutive memory space for the buffer pool
  pages_ = new Page[pool_size_];
  page_table_ = new ExtendibleHashTable<page_id_t, frame_id_t>(bucket_size_);
  replacer_ = new LRUKReplacer(pool_size, replacer_k);

  //创建了一个 freelist,一开始 bufferpool 是空的
  for (size_t i = 0; i < pool_size_; ++i) {
    free_list_.emplace_back(static_cast<int>(i));
  }
}

//在内存中创建一个新的 page,创建成功返回 page 指针,同时指针参数赋值 page_id,否则 nullptr
auto NewPgImp(page_id_t *page_id) -> Page * override;
/*
-> 尝试直接从 freelist 中获取空页 
-> 调用 replacer 成员函数尝试换出一页
-> 换出前如果那一页是脏的要把那个物理页面写回磁盘
-> 初始化装旧页的 page 对象
-> 在哈希表中删除对应项(那一页现在已经不在内存了)
-> 调用 AllocatePage() 得到新创建页的物理页号
-> pin 住新页,同时调用 RecordAccess(),即算一次访问
-> 在哈希表中添加对应项
*/

//获取指定 page_id 的页面,获取成功返回 page 指针,没有空位(LRUK里都不能换出)返回 nullptr
auto FetchPgImp(page_id_t page_id) -> Page * override;
/*
即把这个页调到 buffer pool 里面
-> 先在 buffer pool 里找是否存在
-> 创建一个新页,这一步和 NewPgImp() 实现基本一致,区别在于不需要分配新的 page_id
-> 调用 ReadPage(page_id_t page_id, char *page_data) 把需要的物理页读到新页中
*/

//指定页计数-1,若不在 buffer pool 或计数已经小于 0 返回 false
auto UnpinPgImp(page_id_t page_id, bool is_dirty) -> bool override;
/*
这个函数实际是一个进程对一个页面不再需要后相反于 pin 的操作
参数 is_dirty 代表这个进程对这个页面是否进行了修改
pincount 到 0 时要调用 LRUReplacer 的 SetEvictable() 使其可以被换出
*/

//把指定页写回磁盘,不在 buffer pool 返回 false
auto FlushPgImp(page_id_t page_id) -> bool override;

//把所有页写回磁盘
void FlushAllPgsImp() override;

//删除指定页,不在 buffer pool 返回 true,如果被 pin 返回 false
auto DeletePgImp(page_id_t page_id) -> bool override;
/*
-> 判断是否在 buffer pool
-> 判断是否被 pin
-> 判断是否为脏,写回磁盘(后面会讲为什么)
-> 初始化 page 对象
-> 在哈希表中删除对应项(那一页现在已经不在内存)
-> 在 LRUReplacer 中删除对应项(那一页现在为空)
-> 将 page 加入 freelist
*/

下面是一些坑:

1、 当有多个线程使用同一个页面时,UnpinPgImp(page_id_t page_id, bool is_dirty) 函数会被每个线程调用一次,这时如果一个进程修改数据后先调用,另一个进程没有修改数据后调用,如果不做条件判断直接赋值 is_dirty 会发生什么情况?

第一个进程将 page 的 is_dirty 置为 true,第二个又置为 false,本来应该脏的页面现在不脏了。所以我们只能将 is_dirty 属性由 false 变为 true,而不能由 true 变为 false。

2、在 DeletePgImp(page_id_t page_id) 函数中,需不需要写回处理?

需要!虽然在 NewPgImp() 和 FetchPgImp() 中换出时检测到脏都会写回,但存在调用 UnpinPgImp() 后直接调用 DeletePgImp() 的情况。

最后给出我 NewPgImp() 的实现:

auto BufferPoolManagerInstance::NewPgImp(page_id_t *page_id) -> Page * {
  frame_id_t frame_id;
  Page *page = GetPages();
  std::scoped_lock<std::recursive_mutex> lock(mutex_);
  if (!free_list_.empty()) {  // 有空 page,直接使用
    auto it = free_list_.begin();
    frame_id = *it;
    free_list_.pop_front();
  } else {                              // freelist 为空,需要访问 lru-k
    if (replacer_->Evict(&frame_id)) {  // lru-k 换出
      if (page[frame_id].IsDirty()) {   // page 被修改过,需要写出
        FlushPgImp(page[frame_id].GetPageId());
      }
      page_table_->Remove(page[frame_id].GetPageId());
    } else {  // lru-k 中没有可被换出的页
      page_id = nullptr;
      return nullptr;
    }
  }
  page_id_t new_id = AllocatePage();
  page[frame_id].ResetMemory();
  page[frame_id].pin_count_ = 1;  // 新的 page pinCount 为 1
  page[frame_id].page_id_ = new_id;
  replacer_->SetEvictable(frame_id, false);
  replacer_->RecordAccess(frame_id);
  page_table_->Insert(new_id, frame_id);  // 关联 pageId 和 frameId
  *page_id = new_id;
  return &page[frame_id];
}

P1 完结 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值