CMU15-445 23fall P1,给小白的buffer pool教程

项目链接:Project #1 - Buffer Pool | CMU 15-445/645 :: Intro to Database Systems (Fall 2023)

23fall版本源码:Release Fall 2023 - Updated Release · cmu-db/bustub · GitHub

前言

  1. 写文章的目的其实是自己当初写的时候也基本上是照着别人代码写,现在要开始写P2了,总觉得不踏实,以这种形式回顾巩固一下。
  2. 按照andy的要求,不附上代码源码,主要是对任务详细的解读,因为我在自己做的时候更多是迷茫,不知道怎么下手,看别人代码又觉得“原来是这样”,(需要源码,csdn可以找到)
  3. 本人水平有限,如果有问题请及时提出来。
  4. 如第3点,对“上锁”以及性能优化也是不会,(leader board300多名)如果以后学到且有机会会优化。

有什么问题可以直接发在评论,我会尽力解答。

Task #1 - LRU-K Replacement Policy

  • 实现src/include/buffer/lru_k_replacer.h 类中的函数,相应实现在src/buffer/lru_k_replacer.cpp

  • 是什么?

    • Replacement Policy是替换策略,当我们的容器(在这里就是缓存池)满了后再插入新的需要决定应该去除哪个。
    • LRU-K: 是LRU进阶版
      • LRU:当我们需要驱逐一个页面的时候选取最久没有使用过的页面。
      • LRU存在的问题:只考虑了每个页面最近一次的使用,而没有考虑再之前的历史,比如:在插入6之前的访问操作是:1112241122332533345 ,按照LRU策略将驱逐1,但实际上用的最少的其实是4、5,但只是“运气好”在驱逐前访问。
      • 解决LRU问题:引入一个K值,核心思想:”最近使用的一次“→”最近使用的k次”,在驱逐时,我们优先驱逐访问次数小于k次的(LRU就是LRU-1)
      • 在上面的例子,如果我们设立k = 3 ,那么4、5会优先被驱逐,再考虑“倒数k次离现在最远的页面”
  • 怎么实现:

    • 对每一个页面维护历史访问记录history_,在需要驱逐时,优先驱逐history_.size() < k
  • 一些坑,或者说知识点

    • buffer pool到底是什么,为什么需要它(

      • 存储大小和读取速度是成反比的,但我们的内容都存储在disk中,如果每次读取都从disk中读取,将会非常慢
      • 所以我们会在memory中存储一些常用的,来提高读取速度
      • disk↔memory由os(操作系统)来负责
    • page_table_到底是什么,frame 和page又是什么关系

      • page table→<page_id, page>,通过id来指向page(就是value)

      • page directory → <page_id, location>,通过id 指向page所在的位置

      • page是什么

        The DBMS organizes the database across one or more files in fixed-size blocks of data called pages. Pages can contain different kinds of data (tuples, indexes, etc). Most systems will not mix these types within pages. Some systems will require that pages are self-contained, meaning that all the information needed to read each page is on the page itself.

      • 所以page可以看作一个容器,里面装的东西就是我们的“数据”,我们在disk中存储pages

      • frame是什么

        Memory region organized as an array of fixed-size pages,An array entry is called a frame;

        frame就是在memory(内存)中分配的容器,当我们需要调用一个page,先把它放到frame里面

    • 什么是脏页

      • 不知道就回去看课
  • 代码解读(中文注释,待完成函数后续细讲)

    • code

      //===----------------------------------------------------------------------===//
      //
      //                         BusTub
      //
      // lru_k_replacer.h
      //
      // Identification: src/include/buffer/lru_k_replacer.h
      //
      // Copyright (c) 2015-2022, Carnegie Mellon University Database Group
      //
      //===----------------------------------------------------------------------===//
      
      #pragma once
      
      #include <limits>
      #include <list>
      #include <mutex>  // NOLINT
      #include <unordered_map>
      #include <vector>
      
      #include "common/config.h"
      #include "common/macros.h"
      
      namespace bustub {
      
      enum class AccessType { Unknown = 0, Lookup, Scan, Index };
      
      //LRUK算法中的节点,保存了页面以及相关信息
      class LRUKNode {
       private:
        /** History of last seen K timestamps of this page. Least recent timestamp stored in front. */
        // Remove maybe_unused if you start using them. Feel free to change the member variables as you want.
      
        [[maybe_unused]] std::list<size_t> history_;//用于保存访问信息
        [[maybe_unused]] size_t k_;//LRUK中的k
        [[maybe_unused]] frame_id_t fid_;//页面的id,用来映射物理内存
        [[maybe_unused]]bool is_evictable_{false};//是否可驱逐
      };
      //看到这里就可以初步想想LRUKNode应该会有什么函数
      /**
      * 有个history_来存储,一定有读取、设置、删除之类的
      * 设置k和获取k(getter和setter)
      * 设置is_evictable_和获取is_evictable_等等
      */
      /**
       * LRUKReplacer implements the LRU-k replacement policy.
       *
       * The LRU-k algorithm evicts a frame whose backward k-distance is maximum
       * of all frames. Backward k-distance is computed as the difference in time between
       * current timestamp and the timestamp of kth previous access.
       *
       * A frame with less than k historical references is given
       * +inf as its backward k-distance. When multiple frames have +inf backward k-distance,
       * classical LRU algorithm is used to choose victim.
       */
       
       //replacer_可以看作工具,在后续任务要利用这个工具进行替换等工作
       //看函数前先看看private部分
      class LRUKReplacer {
       public:
       //构造函数和析构函数,不需要自己实现
        /**
         * 
         * TODO(P1): Add implementation
         *
         * @brief a new LRUKReplacer.
         * @param num_frames the maximum number of frames the LRUReplacer will be required to store
         */
        explicit LRUKReplacer(size_t num_frames, size_t k);
      
        DISALLOW_COPY_AND_MOVE(LRUKReplacer);
      
        /**
         * TODO(P1): Add implementation
         *
         * @brief Destroys the LRUReplacer.
         */
        ~LRUKReplacer() = default;
      
        /**
         * TODO(P1): Add implementation
         *
         * @brief Find the frame with largest backward k-distance and evict that frame. Only frames
         * that are marked as 'evictable' are candidates for eviction.
         *
         * A frame with less than k historical references is given +inf as its backward k-distance.
         * If multiple frames have inf backward k-distance, then evict frame with earliest timestamp
         * based on LRU.
         *
         * Successful eviction of a frame should decrement the size of replacer and remove the frame's
         * access history.
         *
         * @param[out] frame_id id of frame that is evicted.
         * @return true if a frame is evicted successfully, false if no frames can be evicted.
         */
         //参数是一个页面id的指针,你可以改变它,决定驱逐哪个
         //驱逐函数,按照LRU-k策略驱逐页面,结合node想想有哪些要调整
        auto Evict(frame_id_t *frame_id) -> bool;
      
        /**
         * TODO(P1): Add implementation
         *
         * @brief Record the event that the given frame id is accessed at current timestamp.
         * Create a new entry for access history if frame id has not been seen before.
         *
         * If frame id is invalid (ie. larger than replacer_size_), throw an exception. You can
         * also use BUSTUB_ASSERT to abort the process if frame id is invalid.
         *
         * @param frame_id id of frame that received a new access.
         * @param access_type type of access that was received. This parameter is only needed for
         * leaderboard tests.
         */
         //记录某次访问
        void RecordAccess(frame_id_t frame_id, AccessType access_type = AccessType::Unknown);
      
        /**
         * TODO(P1): Add implementation
         *
         * @brief Toggle whether a frame is evictable or non-evictable. This function also
         * controls replacer's size. Note that size is equal to number of evictable entries.
         *
         * If a frame was previously evictable and is to be set to non-evictable, then size should
         * decrement. If a frame was previously non-evictable and is to be set to evictable,
         * then size should increment.
         *
         * If frame id is invalid, throw an exception or abort the process.
         *
         * For other scenarios, this function should terminate without modifying anything.
         *
         * @param frame_id id of frame whose 'evictable' status will be modified
         * @param set_evictable whether the given frame is evictable or not
         */
         //将某个页面的状态修改
        void SetEvictable(frame_id_t frame_id, bool set_evictable);
      
        /**
         * TODO(P1): Add implementation
         *
         * @brief Remove an evictable frame from replacer, along with its access history.
         * This function should also decrement replacer's size if removal is successful.
         *
         * Note that this is different from evicting a frame, which always remove the frame
         * with largest backward k-distance. This function removes specified frame id,
         * no matter what its backward k-distance is.
         *
         * If Remove is called on a non-evictable frame, throw an exception or abort the
         * process.
         *
         * If specified frame is not found, directly return from this function.
         *
         * @param frame_id id of frame to be removed
         */
         //移除一个可驱逐的页面
        void Remove(frame_id_t frame_id);
      
        /**
         * TODO(P1): Add implementation
         *
         * @brief Return replacer's size, which tracks the number of evictable frames.
         *
         * @return size_t
         */
         //返回size
        auto Size() -> size_t;
      
       private:
        // TODO(student): implement me! You can replace these member variables as you like.
        // Remove maybe_unused if you start using them.
        [[maybe_unused]] std::unordered_map<frame_id_t, LRUKNode> node_store_;//id->node的映射
        [[maybe_unused]] size_t current_timestamp_{0};//当前时间
        [[maybe_unused]] size_t curr_size_{0};//当前容量
        [[maybe_unused]] size_t replacer_size_;//最大容量
        [[maybe_unused]] size_t k_;//LRU-K的K
        [[maybe_unused]] std::mutex latch_;//锁
      };
      
      }  // namespace bustub
      
  • Evict函数:

    • 先看要求:比较k-distance来确定驱逐哪个
       * A frame with less than k historical references is given +inf as its backward k-distance.
       * If multiple frames have inf backward k-distance, then evict frame with earliest timestamp
       * based on LRU.
       *
       * Successful eviction of a frame should decrement the size of replacer and remove the frame's
       * access history.
       Evict(frame_id_t* frame_id) : Evict the frame with largest backward k-distance compared to all other evictable frames being tracked by the Replacer. Store the frame id in the output parameter and return True. If there are no evictable frames return False.
    
    
    • 分解任务:

      A frame with less than k historical references is given +inf as its backward k-distance.

      • 获取最大的k-distance,如果访问记录小于k,设置为+inf

      If multiple frames have inf backward k-distance, then evict frame with earliest timestamp based on LRU

      • 再保存起来,再从“矮子里挑高个”,比较最早一次访问

      Successful eviction of a frame should decrement the size of replacer and remove the frame's access history.

      • 成功驱逐后应该做什么:调整replacer的size,清除该页面的所有记录

      Store the frame id in the output parameter and return True. If there are no evictable frames return False.

      • 保存这个frame id 在这个参数,然后返回bool值
    • 开始写:

      1. 保证线程安全→上锁
      2. 获取“可驱逐节点”最大的k- distance(需要判断如果小于k的设为+inf并单独先存起来)
      3. 确定待驱逐的页面
      4. 剩下的一些处理:
        1. 清除历史
        2. 将这个页面设置成不可取驱逐
      5. return
      • 直接写这个函数,当你发现需要对node有什么操作或者获取它的信息,就去.h文件实现相关函数
  • RecordAccess函数:

    • 先看要求:记录这次访问,无效id抛出异常
    * @brief Record the event that the given frame id is accessed at current timestamp.
    * Create a new entry for access history if frame id has not been seen before.
    * If frame id is invalid (ie. larger than replacer_size_), throw an exception. You can
    * also use BUSTUB_ASSERT to abort the process if frame id is invalid.
    void RecordAccess(frame_id_t frame_id, AccessType access_type = AccessType::Unknown);
    • 分解任务:
      • 确定是否异常
      • 确定有没有这个页面
        • 没有就新建一个
      • 对这个页面应该进行哪些操作
      • 考虑除了对页面操作还应该有什么操作(一次操作后time++
        • 操作的话需要怎么操作,实现在.h文件
  • SetEvictable函数

       /*
        * @brief Toggle whether a frame is evictable or non-evictable. This function also
       * controls replacer's size. Note that size is equal to number of evictable entries.
       *
       * If a frame was previously evictable and is to be set to non-evictable, then size should
       * decrement. If a frame was previously non-evictable and is to be set to evictable,
       * then size should increment.
       *
       * If frame id is invalid, throw an exception or abort the process.
       *
       * For other scenarios, this function should terminate without modifying anything.
     */
    
    void SetEvictable(frame_id_t frame_id, bool set_evictable);
    
    • 较为简单,除了一些常规操作(上锁、异常)只要考虑是否符合这两个条件就需要特殊操作

    If a frame was previously evictable and is to be set to non-evictable

    • size—

    If a frame was previously non-evictable and is to be set to evictable,

    • size++
  • Remove函数(较为简单)

      /**
       * TODO(P1): Add implementation
       *
       * @brief Remove an evictable frame from replacer, along with its access history.
       * This function should also decrement replacer's size if removal is successful.
       *
       * Note that this is different from evicting a frame, which always remove the frame
       * with largest backward k-distance. This function removes specified frame id,
       * no matter what its backward k-distance is.
       *
       * If Remove is called on a non-evictable frame, throw an exception or abort the
       * process.
       *
       * If specified frame is not found, directly return from this function.
       *
       * @param frame_id id of frame to be removed
       */
      void Remove(frame_id_t frame_id);
    • 直接移除→移除应该有哪些操作
      • 对node来说
      • 对replacer来说呢
        • 检查一下各个参数
  • Size()

    • 直接return curr_size_就行

Task #2 - Disk Scheduler

  • You will implement a new class called DiskScheduler in src/include/storage/disk/disk_scheduler.h and its corresponding implementation file in src/storage/disk/disk_scheduler.cpp.

  • 实现一个磁盘调度程序(磁盘管理程序DiskManager已经给出)

  • 认真理解一下任务要求和背景

    The disk scheduler can be used by other components (in this case, your BufferPoolManager in Task #3) to queue disk requests, represented by a DiskRequest struct (already defined in src/include/storage/disk/disk_scheduler.h). The disk scheduler will maintain a background worker thread which is responsible for processing scheduled requests.

    • 磁盘调度器可以被其他组件(在这种情况下,是你的BufferPoolManager在任务#3中)用来排队磁盘请求,这些请求由一个名为DiskRequest的结构体表示(已经在**src/include/storage/disk/disk_scheduler.h**中定义)。磁盘调度器将维护一个后台工作线程,负责处理已调度的请求。(gpt翻译)
    • 磁盘管理器类(src/include/storage/disk/disk_manager.h)负责从磁盘读取和向磁盘写入页面数据。你的磁盘调度器在处理读或写请求时将使用**DiskManager::ReadPage()DiskManager::WritePage()**。
  • 看源文件:

    • 磁盘管理程序(DiskManager)

      1. 构造函数,用于打开或创建数据库文件和日志文件
      2. DiskManager::ShutDown() :关闭所有文件流
      3. DiskManager::WritePage(page_id_t page_id, const char *page_data) :将指定页面的内容写入磁盘文件
      4. DiskManager::ReadPage(page_id_t page_id, char *page_data) :将指定页面的内容读取到给定的内存区域中。
    • DiskRequest结构(大概看一下)

      /**
       * @brief Represents a Write or Read request for the DiskManager to execute.
       */
      struct DiskRequest {
        /** Flag indicating whether the request is a write or a read. */
        bool is_write_;
      
        /**
         *  Pointer to the start of the memory location where a page is either:
         *   1. being read into from disk (on a read).
         *   2. being written out to disk (on a write).
         */
        char *data_;
      
        /** ID of the page being read from / written to disk. */
        page_id_t page_id_;
      
        /** Callback used to signal to the request issuer when the request has been completed. */
        std::promise<bool> callback_;
      };
      
    • disk_scheduler.h

      class DiskScheduler {
       public:
        explicit DiskScheduler(DiskManager *disk_manager);
        ~DiskScheduler();
      
        /**
         * TODO(P1): Add implementation
         *
         * @brief Schedules a request for the DiskManager to execute.
         *
         * @param r The request to be scheduled.
         * 任务:调度一个请求(queue
         */
        void Schedule(DiskRequest r);
        /**
         * TODO(P1): Add implementation
         *
         * @brief Background worker thread function that processes scheduled requests.
         *
         * The background thread needs to process requests while the DiskScheduler exists, i.e., this function should not
         * return until ~DiskScheduler() is called. At that point you need to make sure that the function does return.
         *  
         */
        void StartWorkerThread();
      
        using DiskSchedulerPromise = std::promise<bool>;
      
        /**
         * @brief Create a Promise object. If you want to implement your own version of promise, you can change this function
         * so that our test cases can use your promise implementation.
         *
         * @return std::promise<bool>
         */
        auto CreatePromise() -> DiskSchedulerPromise { return {}; };
      
       private:
        /** Pointer to the disk manager. */
        DiskManager *disk_manager_ __attribute__((__unused__));
        /** A shared queue to concurrently schedule and process requests. When the DiskScheduler's destructor is called,
         * `std::nullopt` is put into the queue to signal to the background thread to stop execution. */
        Channel<std::optional<DiskRequest>> request_queue_;//请求(request)的队列,排队调度
        /** The background thread responsible for issuing scheduled requests to the disk manager. */
        std::optional<std::thread> background_thread_;
      };
      }  // namespace bustub
      
  • 开始写:

    • Schedule(DiskRequest r) : Schedules a request for the DiskManager to execute. The DiskRequest struct specifies whether the request is for a read/write, where the data should be written into/from, and the page ID for the operation. The DiskRequest also includes a std::promise whose value should be set to true once the request is processed.
      • 调度一个请求供DiskManager执行。(DiskRequest结构体指定请求是读取/写入操作,数据应该写入/读取的位置,以及操作的页面ID。DiskRequest还包括一个**std::promise**,其值应在请求处理完成后设为true。)
      • 怎么样算是调度这个请求呢→放入queue中就行
    • StartWorkerThread() :Start method for the background worker thread which processes the scheduled requests. The worker thread is created in the DiskScheduler constructor and calls this method. This method is responsible for getting queued requests and dispatching them to the DiskManager. Remember to set the value on the DiskRequest's callback to signal to the request issuer that the request has been completed. This should not return until the DiskScheduler's destructor is called.
      • 后台工作线程的启动方法,用于处理已调度的请求。工作线程在DiskScheduler构造函数中创建,并调用此方法。此方法负责获取排队的请求并将其分派给DiskManager。记得在请求完成后设置DiskRequest的回调值,以通知请求发起者请求已完成。在DiskScheduler的析构函数调用前,该方法不应返回。

      • 启动线程→线程在哪里,在队列里,那就不停的从队列中取出请求

      • 创建一个线程来运行它并等待

      • 什么时候结束→“在DiskScheduler的析构函数调用”

        • 看析构函数
        DiskScheduler::~DiskScheduler() {
          // Put a `std::nullopt` in the queue to signal to exit the loop
          request_queue_.Put(std::nullopt);
          if (background_thread_.has_value()) {
            background_thread_->join();
          }
        }
        
        
        • 所以结束条件→(r = request_queue_.Get()) != std::nullopt

Task #3 - Buffer Pool Manager

Next, implement the buffer pool manager (BufferPoolManager). The BufferPoolManager is responsible for fetching database pages from disk with the DiskScheduler and storing them in memory. The BufferPoolManager can also schedule writes of 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.

  • 接下来,实现缓冲池管理器(BufferPoolManager)。缓冲池管理器负责使用磁盘调度器(DiskScheduler)从磁盘中获取数据库页面并将其存储在内存中。当缓冲池管理器被明确指示执行写入脏页到磁盘的操作,或者需要为新页面腾出空间时,它也可以调度脏页的写入到磁盘。

All in-memory pages in the system are represented by Page objects. The BufferPoolManager does not need to understand the contents of these pages. But it is important for you as the system developer to understand that Pageobjects 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 BufferPoolManager 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; if a Page object does not contain a physical page, then its page_id must be set to INVALID_PAGE_ID.

Each Page object also maintains a counter for the number of threads that have "pinned" that page. Your BufferPoolManager is not allowed to free a Pagethat 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 BufferPoolManager must writ

  • 结合代码看看page有什么信息,又怎么使用它:

    • page是什么→内存中缓冲池的容器,不和唯一的页面相关(前文已经提过了)
    • page里面有什么
      • 内容,缓存池管理器不在乎是什么
      • 标识符(page_id),用来跟踪它包含的物理页面,如果没有,则设置为 INVALID_PAGE_ID
      • 计数器,用于跟踪固定该页面的的线程数,固定的page不允许被释放(不然会导致在这个页面的线程内容无效)
      • 是否是脏页,重新使用该对象之前将脏页的内容写回到磁盘。
  • 成员变量:

      /** Array of buffer pool pages. */
      Page *pages_;
      /** Pointer to the disk sheduler. */
      std::unique_ptr<DiskScheduler> disk_scheduler_ __attribute__((__unused__));
      /** Pointer to the log manager. Please ignore this for P1. */
      LogManager *log_manager_ __attribute__((__unused__));
      /** Page table for keeping track of buffer pool pages. */
      std::unordered_map<page_id_t, frame_id_t> page_table_;
      /** Replacer to find unpinned pages for replacement. */
      std::unique_ptr<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_;
    
  • 任务

    • NewPage(page_id_t page_id):创建一个空的page*

        /**
         * TODO(P1): Add implementation
         *
         * @brief Create a new page in the buffer pool. Set page_id to the new page's id, or nullptr if all frames
         * are currently in use and not evictable (in another word, pinned).
         *
         * You should pick the replacement frame from either the free list or the replacer (always find from the free list
         * first), and then call the AllocatePage() method to get a new page id. If the replacement frame has a dirty page,
         * you should write it back to the disk first. You also need to reset the memory and metadata for the new page.
         *
         * Remember to "Pin" the frame by calling replacer.SetEvictable(frame_id, false)
         * so that the replacer wouldn't evict the frame before the buffer pool manager "Unpin"s it.
         * Also, remember to record the access history of the frame in the replacer for the lru-k algorithm to work.
         *
         * @param[out] page_id id of created page
         * @return nullptr if no new pages could be created, otherwise pointer to new page
         */
        auto NewPage(page_id_t *page_id) -> Page *;
      
      • 分解任务一个一个来
        • 要返回的是什么?
          • 无法创建返回空指针
          • 要是能创建,返回一个指向新page的指针
        • 我们有什么→pages:存放page的数组,那有数组是不是就要确定放在数组的哪个位置就是frame_id
        • 找到一个frame_id
          • 如果有现成的→free_list_不为空→直接从这里面取一个
          • 没有现成的→驱逐一个,没有可驱逐就返回nullptr
        • 确定page → page = pages_ + frame_id
        • 后续的操作:
          • 题目提示中的:
            • “Pin" the frame by calling replacer.SetEvictable(frame_id, false)
            • remember to record the access history of the frame in the replacer for the lru-k algorithm to work.
            • 前两个是对页面本身,那对整个管理系统呢,看看有哪些变量需要改
    • BufferPoolManager::FetchPage:

      For FetchPage, you should return nullptr if no page is available in the free list and all other pages are currently pinned.

        /**
         * TODO(P1): Add implementation
         *
         * @brief Fetch the requested page from the buffer pool. Return nullptr if page_id needs to be fetched from the disk
         * but all frames are currently in use and not evictable (in another word, pinned).
         *
         * First search for page_id in the buffer pool. If not found, pick a replacement frame from either the free list or
         * the replacer (always find from the free list first), read the page from disk by scheduling a read DiskRequest with
         * disk_scheduler_->Schedule(), and replace the old page in the frame. Similar to NewPage(), if the old page is dirty,
         * you need to write it back to disk and update the metadata of the new page
         *
         * In addition, remember to disable eviction and record the access history of the frame like you did for NewPage().
         *
         * @param page_id id of page to be fetched
         * @param access_type type of access to the page, only needed for leaderboard tests.
         * @return nullptr if page_id cannot be fetched, otherwise pointer to the requested page
         */
      auto FetchPage(page_id_t page_id, AccessType access_type = AccessType::Unknown) -> Page *;
      
      • 对于 FetchPage,如果在空闲列表中找不到页面并且所有其他页面当前都被固定,则应返回 nullptr
      • 从buffer pool中取出一页,
      • 一步一步来:
        • 处理无效id、上锁
        • 先检查buffer pool中有没有
          • 逻辑和new page差不多,找到可用的frame_id_
        • 考虑还需要那些处理
          • 如果之前是脏页要先写进磁盘
          • 更新现在page的信息(看注释
          • 更新管理系统
    • BufferPoolManager::UnpinPage

      For UnpinPage, the is_dirty parameter keeps track of whether a page was modified while it was pinned.

      is_dirty 参数用于跟踪页面在固定状态下是否被修改。

        /**
         * TODO(P1): Add implementation
         *
         * @brief Unpin the target page from the buffer pool. If page_id is not in the buffer pool or its pin count is already
         * 0, return false.
         *
         * Decrement the pin count of a page. If the pin count reaches 0, the frame should be evictable by the replacer.
         * Also, set the dirty flag on the page to indicate if the page was modified.
         *
         * @param page_id id of page to be unpinned
         * @param is_dirty true if the page should be marked as dirty, false otherwise
         * @param access_type type of access to the page, only needed for leaderboard tests.
         * @return false if the page is not in the page table or its pin count is <= 0 before this call, true otherwise
         */
      auto UnpinPage(page_id_t page_id, bool is_dirty, AccessType access_type = AccessType::Unknown) -> bool;
      • 按照任务要求一步一步走就好了
        • 根据page_id获取page
          • 先获取什么
        • If page_id is not in the buffer pool or its pin count is already 0, return false.
        • pin—
          • 如果变成0了,要变成可驱逐的
        • 根据参数is_dirty考虑是否要修改is_dirty_
    • BufferPoolManager::FlushPage和BufferPoolManager::FlushAllPages

      FlushPage should flush a page regardless of its pin status.

      FlushPage 应该无论页面的固定状态如何都会刷新页面。

        /**
         * TODO(P1): Add implementation
         *
         * @brief Flush the target page to disk.
         *
         * Use the DiskManager::WritePage() method to flush a page to disk, REGARDLESS of the dirty flag.
         * Unset the dirty flag of the page after flushing.
         *
         * @param page_id id of page to be flushed, cannot be INVALID_PAGE_ID
         * @return false if the page could not be found in the page table, true otherwise
         */
       auto FlushPage(page_id_t page_id) -> bool;
      • 按照任务一步步来就好
        • 上锁,无效id
          • Use the DiskManager::WritePage() method to flush a page to disk
          • Unset the dirty flag of the page after flushing.
      • FlushAllPages:差不多
    • BufferPoolManager::DeletePage:

      On the other hand, the DeallocatePage() method is a no-op that imitates freeing a page on the disk and you should call this in your DeletePage() implementation.

        /**
         * TODO(P1): Add implementation
         *
         * @brief Delete a page from the buffer pool. If page_id is not in the buffer pool, do nothing and return true. If the
         * page is pinned and cannot be deleted, return false immediately.
         *
         * After deleting the page from the page table, stop tracking the frame in the replacer and add the frame
         * back to the free list. Also, reset the page's memory and metadata. Finally, you should call DeallocatePage() to
         * imitate freeing the page on the disk.
         *
         * @param page_id id of page to be deleted
         * @return false if the page exists but could not be deleted, true if the page didn't exist or deletion succeeded
         */
      auto DeletePage(page_id_t page_id) -> bool;
      • 另一方面,DeallocatePage() 方法是一个无操作,模拟在磁盘上释放页面,并且您应该在 DeletePage() 实现中调用它。
      • Delete a page from the buffer pool. If page_id is not in the buffer pool, do nothing and return true.If the page is pinned and cannot be deletedreturn false immediately.
      • stop tracking the frame in the replacer and add the frame back to the free list.
      • reset the page's memory and metadata.
      • Finally, you should call DeallocatePage() to imitate freeing the page on the disk.
    • 最后附上满分记录。

  • 35
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值