refactored

This commit is contained in:
2024-05-01 04:12:05 +00:00
parent 6c20530cc8
commit ec920f7255
37 changed files with 79 additions and 57 deletions

1
storage/CMakeLists.txt Normal file
View File

@ -0,0 +1 @@
add_library(storage STATIC src/disk_manager.cpp src/replacer.cpp src/buffer_pool_manager.cpp src/bpt.cpp)

View File

@ -0,0 +1,854 @@
#ifndef BPT_HPP
#define BPT_HPP
#include <cassert>
#include <cstring>
#include <shared_mutex>
#include "vector.hpp"
#include "storage/bpt_page.hpp"
#include "storage/buffer_pool_manager.h"
#include "storage/config.h"
/**
* @brief B+ Tree Indexer
* @warning The KeyType must can be stored byte by byte. As this is only the indexer, the type of value is always
* b_plus_tree_value_index_t. And also, this is only the indexer, the value is not stored in the indexer, the value is
* stored in the value file, and the BPlusTreeIndexer should not be used directly.
*/
template <typename KeyType, typename KeyComparator>
class BPlusTreeIndexer {
typedef BPlusTreePage<KeyType> PageType;
typedef ActualDataType<KeyType> _ActualDataType;
typedef std::pair<KeyType, default_numeric_index_t> key_index_pair_t;
// typedef std::pair<KeyType, b_plus_tree_value_index_t> value_type;
private:
struct PositionSignType {
sjtu::vector<std::pair<BasicPageGuard, in_page_key_count_t>> path;
bool is_end{false};
};
PositionSignType FindPosition(const KeyType &key) { // Finish Design
if (root_page_id == 0) {
// special case for the empty tree
return PositionSignType{.is_end = true};
}
BasicPageGuard current_page_guard(bpm->FetchPageBasic(root_page_id));
static auto comparer_for_key_index_pair = [](const key_index_pair_t &a, const KeyType &b) {
return key_cmp(a.first, b);
};
// fprintf(stderr, "current page has %u keys\n", current_page_guard.As<PageType>()->data.key_count);
in_page_key_count_t nxt = std::lower_bound(current_page_guard.As<PageType>()->data.p_data,
current_page_guard.As<PageType>()->data.p_data +
current_page_guard.As<PageType>()->data.key_count,
key, comparer_for_key_index_pair) -
current_page_guard.As<PageType>()->data.p_data;
PositionSignType res;
res.path.push_back(std::make_pair(std::move(current_page_guard), nxt));
while ((res.path.back().first.template As<PageType>()->data.page_status & PageStatusType::LEAF) == 0) {
default_numeric_index_t nxt_page_id;
in_page_key_count_t internal_id = res.path.back().second;
if (internal_id < _ActualDataType::kMaxKeyCount)
nxt_page_id = res.path.back().first.template As<PageType>()->data.p_data[internal_id].second;
else
nxt_page_id = res.path.back().first.template As<PageType>()->data.p_n;
BasicPageGuard next_page_guard(bpm->FetchPageBasic(nxt_page_id));
nxt =
std::lower_bound(next_page_guard.As<PageType>()->data.p_data,
next_page_guard.As<PageType>()->data.p_data + next_page_guard.As<PageType>()->data.key_count,
key, comparer_for_key_index_pair) -
next_page_guard.As<PageType>()->data.p_data;
res.path.push_back(std::make_pair(std::move(next_page_guard), nxt));
}
if (nxt == res.path.back().first.template As<PageType>()->data.key_count)
res.is_end = true;
else
res.is_end = false;
return res;
}
void InsertFixUpLoopPartA(PositionSignType &pos, BasicPageGuard &parent_page_guard, BasicPageGuard &new_page_guard,
BasicPageGuard &page_guard, default_numeric_index_t new_page_id) {
pos.path[pos.path.size() - 2].second++;
// now check we are able to "insert" (new_page_guard.template
// As<PageType>()->data.p_data[new_page_guard.template As<PageType>()->data.key_count - 1].first, new_page_id)
// at pos
if (parent_page_guard.template As<PageType>()->data.key_count < _ActualDataType::kMaxKeyCount) {
// Has enough space, reach end, just insert it
// first, manually move the last pointer
parent_page_guard.template AsMut<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second - 1].first =
page_guard.template As<PageType>()->data.p_data[page_guard.template As<PageType>()->data.key_count - 1].first;
if (parent_page_guard.template As<PageType>()->data.key_count == _ActualDataType::kMaxKeyCount - 1) {
parent_page_guard.template AsMut<PageType>()->data.p_n =
parent_page_guard.template As<PageType>()
->data.p_data[parent_page_guard.template As<PageType>()->data.key_count]
.second;
} else {
parent_page_guard.template AsMut<PageType>()
->data.p_data[parent_page_guard.template As<PageType>()->data.key_count + 1]
.second = parent_page_guard.template As<PageType>()
->data.p_data[parent_page_guard.template As<PageType>()->data.key_count]
.second;
}
// Then, use memmove to move the key_point pairs
// fprintf(stderr, "parent_page_guard.template As<PageType>()->data.key_count = %d\n",
// (int)parent_page_guard.template As<PageType>()->data.key_count);
if (pos.path[pos.path.size() - 2].second < parent_page_guard.template As<PageType>()->data.key_count) {
memmove(parent_page_guard.template AsMut<PageType>()->data.p_data + pos.path[pos.path.size() - 2].second + 1,
parent_page_guard.template As<PageType>()->data.p_data + pos.path[pos.path.size() - 2].second,
(parent_page_guard.template As<PageType>()->data.key_count - pos.path[pos.path.size() - 2].second) *
sizeof(key_index_pair_t));
}
// Then Set the key_point pair
if (pos.path[pos.path.size() - 2].second < _ActualDataType::kMaxKeyCount) {
parent_page_guard.template AsMut<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second] =
std::make_pair(new_page_guard.template As<PageType>()
->data.p_data[new_page_guard.template As<PageType>()->data.key_count - 1]
.first,
new_page_id);
} else {
// just set p_n
parent_page_guard.template AsMut<PageType>()->data.p_n = new_page_id;
}
parent_page_guard.template AsMut<PageType>()->data.key_count++;
return;
}
// TODO: process and prepare for next round
assert((pos.path.size() == 2) ==
((parent_page_guard.template As<PageType>()->data.page_status & PageStatusType::ROOT) != 0));
/**
* Step 1: split current page
* Step 2: Check if current page is root. If current page is root, create a new root, update and exit
* Step 3: Otherwise, update parent page, and continue the loop
*/
key_index_pair_t new_entry_backup =
std::make_pair(new_page_guard.template As<PageType>()
->data.p_data[new_page_guard.template As<PageType>()->data.key_count - 1]
.first,
new_page_id);
KeyType key_to_update_backup =
page_guard.template As<PageType>()->data.p_data[page_guard.template As<PageType>()->data.key_count - 1].first;
InsertFixUpLoopPartB(pos, parent_page_guard, new_entry_backup, key_to_update_backup);
}
void InsertFixUpLoopPartB(PositionSignType &pos, BasicPageGuard &page_guard, const key_index_pair_t &new_entry_backup,
const KeyType &key_to_update_backup) {
default_numeric_index_t new_page_id;
auto new_page_guard = std::move(bpm->NewPageGuarded(&new_page_id));
new_page_guard.template AsMut<PageType>()->data.page_status = PageStatusType::INTERNAL;
// Now begin spliting. It is expected that the new page has _ActualDataType::kMinNumberOfKeysForLeaf keys
if (pos.path[pos.path.size() - 2].second - 1 == _ActualDataType::kMaxKeyCount) {
// In this case, first, move the last _ActualDataType::kMinNumberOfKeysForLeaf-1 keys to the new page
memmove(new_page_guard.template AsMut<PageType>()->data.p_data,
page_guard.template As<PageType>()->data.p_data + _ActualDataType::kMaxKeyCount -
(_ActualDataType::kMinNumberOfKeysForLeaf - 1),
(_ActualDataType::kMinNumberOfKeysForLeaf - 1) * sizeof(key_index_pair_t));
new_page_guard.template AsMut<PageType>()->data.p_data[_ActualDataType::kMinNumberOfKeysForLeaf - 1].second =
page_guard.template As<PageType>()->data.p_n;
new_page_guard.template AsMut<PageType>()->data.p_data[_ActualDataType::kMinNumberOfKeysForLeaf - 1].first =
key_to_update_backup;
new_page_guard.template AsMut<PageType>()->data.p_data[_ActualDataType::kMinNumberOfKeysForLeaf].second =
new_entry_backup.second;
new_page_guard.template AsMut<PageType>()->data.key_count = _ActualDataType::kMinNumberOfKeysForLeaf;
page_guard.template AsMut<PageType>()->data.key_count -= _ActualDataType::kMinNumberOfKeysForLeaf - 1;
} else {
page_guard.template AsMut<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second - 1].first =
key_to_update_backup;
// now, we need to "insert" the new_entry_backup to position pos.path[pos.path.size() - 2].second
new_page_guard.template AsMut<PageType>()->data.p_data[_ActualDataType::kMinNumberOfKeysForLeaf].second =
page_guard.template As<PageType>()->data.p_n;
default_numeric_index_t it_dest = _ActualDataType::kMinNumberOfKeysForLeaf - 1;
default_numeric_index_t it_src = _ActualDataType::kMaxKeyCount - 1;
bool has_processed = false;
for (; it_dest != kInvalidNumericIndex; --it_dest) {
if (!has_processed && pos.path[pos.path.size() - 2].second == it_src + 1) {
new_page_guard.template AsMut<PageType>()->data.p_data[it_dest] = new_entry_backup;
has_processed = true;
continue;
}
new_page_guard.template AsMut<PageType>()->data.p_data[it_dest] =
page_guard.template As<PageType>()->data.p_data[it_src];
--it_src;
}
if (pos.path[pos.path.size() - 2].second <=
_ActualDataType::kMaxKeyCount - _ActualDataType::kMinNumberOfKeysForLeaf) {
memmove(page_guard.template AsMut<PageType>()->data.p_data + pos.path[pos.path.size() - 2].second + 1,
page_guard.template As<PageType>()->data.p_data + pos.path[pos.path.size() - 2].second,
((_ActualDataType::kMaxKeyCount - _ActualDataType::kMinNumberOfKeysForLeaf) -
pos.path[pos.path.size() - 2].second) *
sizeof(key_index_pair_t));
page_guard.template AsMut<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second] = new_entry_backup;
}
new_page_guard.template AsMut<PageType>()->data.key_count = _ActualDataType::kMinNumberOfKeysForLeaf;
page_guard.template AsMut<PageType>()->data.key_count -= _ActualDataType::kMinNumberOfKeysForLeaf - 1;
}
// fprintf(stderr, "Evicting page %d\n", (int)pos.path.back().first.PageId());
// fprintf(stderr, "page id of page_guard = %d\n", (int)page_guard.PageId());
pos.path.pop_back();
// fprintf(stderr, "the page id of the res page in pos %d\n", (int)pos.path.back().first.PageId());
if (pos.path.size() == 1) {
// we have split the root page, update and quit
page_guard.template AsMut<PageType>()->data.page_status &= ~PageStatusType::ROOT;
BasicPageGuard new_root_page_guard = bpm->NewPageGuarded(&root_page_id);
new_root_page_guard.AsMut<PageType>()->data.page_status = PageStatusType::ROOT;
new_root_page_guard.AsMut<PageType>()->data.key_count = 1;
new_root_page_guard.AsMut<PageType>()->data.p_data[0] = std::make_pair(
page_guard.template As<PageType>()->data.p_data[page_guard.template As<PageType>()->data.key_count - 1].first,
page_guard.PageId());
new_root_page_guard.AsMut<PageType>()->data.p_data[1] = std::make_pair(KeyType(), new_page_id);
return;
}
auto &parent_page_guard = pos.path[pos.path.size() - 2].first;
InsertFixUpLoopPartA(pos, parent_page_guard, new_page_guard, page_guard, new_page_id);
}
void InsertEntryAt(PositionSignType &pos, const KeyType &key, b_plus_tree_value_index_t value,
bool is_fixing_up_recursive = false) {
// fprintf(stderr, "_ActualDataType::kMaxKeyCount = %d\n", (int)_ActualDataType::kMaxKeyCount);
if (siz == 0) {
// special case for the first entry
BasicPageGuard new_page_guard = bpm->NewPageGuarded(&root_page_id);
new_page_guard.AsMut<PageType>()->data.page_status = PageStatusType::ROOT | PageStatusType::LEAF;
new_page_guard.AsMut<PageType>()->data.key_count = 1;
new_page_guard.AsMut<PageType>()->data.p_data[0] = std::make_pair(key, value);
new_page_guard.AsMut<PageType>()->data.p_n = 0;
return;
}
auto &page_guard = pos.path.back().first;
if (page_guard.template As<PageType>()->data.key_count < _ActualDataType::kMaxKeyCount) {
// case 1: the page has enough space
memmove(page_guard.template AsMut<PageType>()->data.p_data + pos.path.back().second + 1,
page_guard.template As<PageType>()->data.p_data + pos.path.back().second,
(page_guard.template As<PageType>()->data.key_count - pos.path.back().second) * sizeof(key_index_pair_t));
page_guard.template AsMut<PageType>()->data.p_data[pos.path.back().second] = std::make_pair(key, value);
page_guard.template AsMut<PageType>()->data.key_count++;
// fprintf(stderr, "page_guard.template As<PageType>()->data.key_count = %d\n",
// (int)page_guard.template As<PageType>()->data.key_count);
return;
}
// In our case, the tree is not too high, so we do not consider borrowing from siblings, we just split the page.
// We first construct a new page, and then move half of the keys to the new page.
// The check if we split the root page, we just handle it.
// Otherwise, what we need to do is modify the parent page, then "insert" a new key to the parent page
page_id_t new_page_id;
BasicPageGuard new_page_guard = bpm->NewPageGuarded(&new_page_id);
// Then move the last kMinNumberOfKeysForLeaf keys(including newly inserted) to the new page
if (!is_fixing_up_recursive)
new_page_guard.AsMut<PageType>()->data.page_status = PageStatusType::LEAF;
else
new_page_guard.AsMut<PageType>()->data.page_status = PageStatusType::INTERNAL;
new_page_guard.AsMut<PageType>()->data.key_count = _ActualDataType::kMinNumberOfKeysForLeaf;
page_guard.template AsMut<PageType>()->data.key_count -= _ActualDataType::kMinNumberOfKeysForLeaf;
if (!is_fixing_up_recursive)
new_page_guard.AsMut<PageType>()->data.p_n = page_guard.template As<PageType>()->data.p_n;
if (!is_fixing_up_recursive) page_guard.template AsMut<PageType>()->data.p_n = new_page_id;
if (pos.path.back().second <= _ActualDataType::kMaxKeyCount - _ActualDataType::kMinNumberOfKeysForLeaf) {
// the new key is in the first half
memmove(new_page_guard.template AsMut<PageType>()->data.p_data,
page_guard.template As<PageType>()->data.p_data + _ActualDataType::kMaxKeyCount -
_ActualDataType::kMinNumberOfKeysForLeaf,
_ActualDataType::kMinNumberOfKeysForLeaf * sizeof(key_index_pair_t));
memmove(page_guard.template AsMut<PageType>()->data.p_data + pos.path.back().second + 1,
page_guard.template As<PageType>()->data.p_data + pos.path.back().second,
(page_guard.template As<PageType>()->data.key_count - pos.path.back().second) * sizeof(key_index_pair_t));
page_guard.template AsMut<PageType>()->data.p_data[pos.path.back().second] = std::make_pair(key, value);
page_guard.template AsMut<PageType>()->data.key_count++;
} else {
// the new key is in the second half
memmove(
new_page_guard.template AsMut<PageType>()->data.p_data,
page_guard.template As<PageType>()->data.p_data + _ActualDataType::kMaxKeyCount -
_ActualDataType::kMinNumberOfKeysForLeaf + 1,
(pos.path.back().second - (_ActualDataType::kMaxKeyCount - _ActualDataType::kMinNumberOfKeysForLeaf + 1)) *
sizeof(key_index_pair_t));
new_page_guard.template AsMut<PageType>()
->data.p_data[pos.path.back().second -
(_ActualDataType::kMaxKeyCount - _ActualDataType::kMinNumberOfKeysForLeaf + 1)] =
std::make_pair(key, value);
memmove(new_page_guard.template AsMut<PageType>()->data.p_data + pos.path.back().second -
(_ActualDataType::kMaxKeyCount - _ActualDataType::kMinNumberOfKeysForLeaf + 1) + 1,
page_guard.template As<PageType>()->data.p_data + pos.path.back().second,
(_ActualDataType::kMaxKeyCount - pos.path.back().second) * sizeof(key_index_pair_t));
page_guard.template AsMut<PageType>()->data.key_count++;
}
if (page_guard.template As<PageType>()->data.page_status & PageStatusType::ROOT) {
// special case for the root page being splited
page_guard.template AsMut<PageType>()->data.page_status &= ~PageStatusType::ROOT;
BasicPageGuard new_root_page_guard = bpm->NewPageGuarded(&root_page_id);
new_root_page_guard.AsMut<PageType>()->data.page_status = PageStatusType::ROOT;
new_root_page_guard.AsMut<PageType>()->data.key_count = 1;
new_root_page_guard.AsMut<PageType>()->data.p_data[0] = std::make_pair(
page_guard.template As<PageType>()->data.p_data[page_guard.template As<PageType>()->data.key_count - 1].first,
page_guard.PageId());
new_root_page_guard.AsMut<PageType>()->data.p_data[1] = std::make_pair(KeyType(), new_page_id);
// fprintf(stderr, "new_page_guard.AsMut<PageType>()->data.key_count = %d\n",
// (int)new_page_guard.AsMut<PageType>()->data.key_count);
return;
}
assert(pos.path.size() >= 2);
auto &parent_page_guard = pos.path[pos.path.size() - 2].first;
bool is_in_right_skew_path = false;
// if (pos.path[pos.path.size() - 2].second == parent_page_guard.template As<PageType>()->data.key_count) {
// is_in_right_skew_path = true;
// }
if (pos.path.size() == 2 || pos.path[pos.path.size() - 3].second ==
pos.path[pos.path.size() - 3].first.template As<PageType>()->data.key_count) {
is_in_right_skew_path = true;
}
if (is_in_right_skew_path) {
InsertFixUpLoopPartA(pos, parent_page_guard, new_page_guard, page_guard, new_page_id);
return;
}
parent_page_guard.template AsMut<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second].first =
page_guard.template As<PageType>()->data.p_data[page_guard.template As<PageType>()->data.key_count - 1].first;
pos.path[pos.path.size() - 2].second++;
pos.path.pop_back();
// fprintf(stderr, "begin processing recursively\n");
InsertEntryAt(pos,
new_page_guard.template As<PageType>()
->data.p_data[new_page_guard.template As<PageType>()->data.key_count - 1]
.first,
new_page_id, true);
return;
}
void RemoveEntryInRightSkewPath(PositionSignType &pos) {
auto &page_guard = pos.path.back().first;
bool has_enough_keys = false;
if (page_guard.template As<PageType>()->data.key_count > _ActualDataType::kMinNumberOfKeysForLeaf ||
(page_guard.template As<PageType>()->data.page_status & PageStatusType::ROOT) != 0) {
// case 1: the page has enough keys
has_enough_keys = true;
}
if (pos.path.back().second == page_guard.template As<PageType>()->data.key_count) {
// The "entry" to remove is just a past-the-end pointer
page_guard.template AsMut<PageType>()->data.key_count--;
} else {
// The "entry" to remove is a key-val pair
memmove(
page_guard.template AsMut<PageType>()->data.p_data + pos.path.back().second,
page_guard.template As<PageType>()->data.p_data + pos.path.back().second + 1,
(page_guard.template As<PageType>()->data.key_count - pos.path.back().second - 1) * sizeof(key_index_pair_t));
page_guard.template AsMut<PageType>()->data.key_count--;
if (page_guard.template AsMut<PageType>()->data.key_count + 1 < _ActualDataType::kMaxKeyCount) {
page_guard.template AsMut<PageType>()->data.p_data[page_guard.template As<PageType>()->data.key_count].second =
page_guard.template As<PageType>()
->data.p_data[page_guard.template As<PageType>()->data.key_count + 1]
.second;
} else {
page_guard.template AsMut<PageType>()->data.p_data[page_guard.template As<PageType>()->data.key_count].second =
page_guard.template As<PageType>()->data.p_n;
}
}
if (has_enough_keys) {
if (page_guard.template As<PageType>()->data.page_status & PageStatusType::ROOT &&
page_guard.template As<PageType>()->data.key_count == 0) {
// special case for the root page
root_page_id = page_guard.template As<PageType>()->data.p_data[0].second;
BasicPageGuard root_page_guard = bpm->FetchPageBasic(root_page_id);
root_page_guard.template AsMut<PageType>()->data.page_status |= PageStatusType::ROOT;
page_id_t page_to_delete = page_guard.PageId();
pos.path.clear(); // all page_guards are invalid now
bpm->DeletePage(page_to_delete);
return;
}
return;
}
assert(pos.path.size() >= 2);
assert(pos.path[pos.path.size() - 2].second > 0);
page_id_t possible_prev_page_id = 0;
auto &parent_page_guard = pos.path[pos.path.size() - 2].first;
possible_prev_page_id =
parent_page_guard.template As<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second - 1].second;
BasicPageGuard prev_page_guard = std::move(bpm->FetchPageBasic(possible_prev_page_id));
if (prev_page_guard.As<PageType>()->data.key_count > _ActualDataType::kMinNumberOfKeysForLeaf) {
// borrow from prev
// first, set the past-the-end pointer
page_guard.template AsMut<PageType>()
->data.p_data[page_guard.template As<PageType>()->data.key_count + 1]
.second =
page_guard.template As<PageType>()->data.p_data[page_guard.template As<PageType>()->data.key_count].second;
memmove(page_guard.template AsMut<PageType>()->data.p_data + 1, page_guard.template As<PageType>()->data.p_data,
page_guard.template As<PageType>()->data.key_count * sizeof(key_index_pair_t));
page_guard.template AsMut<PageType>()->data.p_data[0] =
prev_page_guard.template As<PageType>()
->data.p_data[prev_page_guard.template As<PageType>()->data.key_count - 1];
page_guard.template AsMut<PageType>()->data.key_count++;
prev_page_guard.template AsMut<PageType>()->data.key_count--;
parent_page_guard.template AsMut<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second - 1].first =
prev_page_guard.template As<PageType>()
->data.p_data[prev_page_guard.template As<PageType>()->data.key_count - 1]
.first;
return;
}
// now we have no choice but to merge self into prev
memmove(prev_page_guard.template AsMut<PageType>()->data.p_data +
prev_page_guard.template As<PageType>()->data.key_count,
page_guard.template As<PageType>()->data.p_data,
page_guard.template As<PageType>()->data.key_count * sizeof(key_index_pair_t));
prev_page_guard.template AsMut<PageType>()->data.key_count += page_guard.template As<PageType>()->data.key_count;
if (page_guard.template As<PageType>()->data.key_count < _ActualDataType::kMaxKeyCount) {
if (prev_page_guard.template As<PageType>()->data.key_count < _ActualDataType::kMaxKeyCount) {
prev_page_guard.template AsMut<PageType>()
->data.p_data[prev_page_guard.template As<PageType>()->data.key_count]
.second =
page_guard.template As<PageType>()->data.p_data[page_guard.template As<PageType>()->data.key_count].second;
} else {
prev_page_guard.template AsMut<PageType>()->data.p_n =
page_guard.template As<PageType>()->data.p_data[page_guard.template As<PageType>()->data.key_count].second;
}
} else {
if (prev_page_guard.template As<PageType>()->data.key_count < _ActualDataType::kMaxKeyCount) {
prev_page_guard.template AsMut<PageType>()
->data.p_data[prev_page_guard.template As<PageType>()->data.key_count]
.second = page_guard.template As<PageType>()->data.p_n;
} else {
prev_page_guard.template AsMut<PageType>()->data.p_n = page_guard.template As<PageType>()->data.p_n;
}
}
parent_page_guard.template AsMut<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second - 1].first =
prev_page_guard.template As<PageType>()
->data.p_data[prev_page_guard.template As<PageType>()->data.key_count - 1]
.first;
pos.path.pop_back(); // page_guard is no longer valid
RemoveEntryInRightSkewPath(pos);
return;
}
void TryUpdateTillRoot(PositionSignType &pos) {
if (pos.path.size() < 2) return;
BasicPageGuard &page_guard = pos.path.back().first;
if (pos.path.back().second != page_guard.template As<PageType>()->data.key_count - 1) return;
BasicPageGuard &parent_page_guard = pos.path[pos.path.size() - 2].first;
if (pos.path[pos.path.size() - 2].second >= parent_page_guard.template As<PageType>()->data.key_count) return;
parent_page_guard.template AsMut<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second].first =
page_guard.template As<PageType>()->data.p_data[page_guard.template As<PageType>()->data.key_count - 1].first;
pos.path.pop_back();
TryUpdateTillRoot(pos);
}
void RemoveEntryAt(PositionSignType &pos, bool is_fixing_up_recursive = false) {
if (siz == 1) {
// special case for the last entry
bpm->DeletePage(root_page_id);
root_page_id = 0;
return;
}
auto &page_guard = pos.path.back().first;
bool has_enough_keys = false;
if (page_guard.template As<PageType>()->data.key_count > _ActualDataType::kMinNumberOfKeysForLeaf ||
(page_guard.template As<PageType>()->data.page_status & PageStatusType::ROOT) != 0) {
// case 1: the page has enough keys
has_enough_keys = true;
}
memmove(
page_guard.template AsMut<PageType>()->data.p_data + pos.path.back().second,
page_guard.template As<PageType>()->data.p_data + pos.path.back().second + 1,
(page_guard.template As<PageType>()->data.key_count - pos.path.back().second - 1) * sizeof(key_index_pair_t));
page_guard.template AsMut<PageType>()->data.key_count--;
bool need_update = false;
if (pos.path.size() >= 2 && page_guard.template AsMut<PageType>()->data.key_count == pos.path.back().second) {
auto &parent_page_guard = pos.path[pos.path.size() - 2].first;
if (pos.path[pos.path.size() - 2].second < parent_page_guard.template As<PageType>()->data.key_count) {
parent_page_guard.template AsMut<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second].first =
page_guard.template As<PageType>()
->data.p_data[page_guard.template As<PageType>()->data.key_count - 1]
.first;
need_update = true;
}
}
if (has_enough_keys) {
if (page_guard.template As<PageType>()->data.page_status & PageStatusType::ROOT &&
page_guard.template As<PageType>()->data.key_count == 0) {
// special case for the root page
root_page_id = page_guard.template As<PageType>()->data.p_data[0].second;
BasicPageGuard root_page_guard = bpm->FetchPageBasic(root_page_id);
root_page_guard.template AsMut<PageType>()->data.page_status |= PageStatusType::ROOT;
page_id_t page_to_delete = page_guard.PageId();
pos.path.clear(); // all page_guards are invalid now
bpm->DeletePage(page_to_delete);
}
if (need_update) {
// now we need to check if we have to update the right bound till the root
pos.path.pop_back();
TryUpdateTillRoot(pos);
}
return;
}
assert(pos.path.size() >= 2);
// First, check if we can borrow from siblings. If we can, we just borrow from siblings. Otherwise, we just merge.
page_id_t possible_prev_page_id = 0, possible_next_page_id = 0;
auto &parent_page_guard = pos.path[pos.path.size() - 2].first;
bool is_in_right_skew_path = false;
// if (pos.path[pos.path.size() - 2].second == parent_page_guard.template As<PageType>()->data.key_count) {
// is_in_right_skew_path = true;
// }
if (pos.path.size() == 2 || pos.path[pos.path.size() - 3].second ==
pos.path[pos.path.size() - 3].first.template As<PageType>()->data.key_count) {
is_in_right_skew_path = true;
}
if (is_in_right_skew_path) {
if (pos.path[pos.path.size() - 2].second < parent_page_guard.template As<PageType>()->data.key_count) {
if (pos.path[pos.path.size() - 2].second + 1 < _ActualDataType::kMaxKeyCount)
possible_next_page_id =
parent_page_guard.template As<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second + 1].second;
else
possible_next_page_id = parent_page_guard.template As<PageType>()->data.p_n;
}
} else {
if (pos.path[pos.path.size() - 2].second < parent_page_guard.template As<PageType>()->data.key_count - 1)
possible_next_page_id =
parent_page_guard.template As<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second + 1].second;
}
if (pos.path[pos.path.size() - 2].second > 0)
possible_prev_page_id =
parent_page_guard.template As<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second - 1].second;
if (possible_prev_page_id != 0) {
BasicPageGuard prev_page_guard = std::move(bpm->FetchPageBasic(possible_prev_page_id));
if (prev_page_guard.As<PageType>()->data.key_count > _ActualDataType::kMinNumberOfKeysForLeaf) {
// borrow from prev
memmove(page_guard.template AsMut<PageType>()->data.p_data + 1, page_guard.template As<PageType>()->data.p_data,
page_guard.template As<PageType>()->data.key_count * sizeof(key_index_pair_t));
page_guard.template AsMut<PageType>()->data.p_data[0] =
prev_page_guard.template As<PageType>()
->data.p_data[prev_page_guard.template As<PageType>()->data.key_count - 1];
page_guard.template AsMut<PageType>()->data.key_count++;
prev_page_guard.template AsMut<PageType>()->data.key_count--;
parent_page_guard.template AsMut<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second - 1].first =
prev_page_guard.template As<PageType>()
->data.p_data[prev_page_guard.template As<PageType>()->data.key_count - 1]
.first;
if (need_update) {
// now we need to check if we have to update the right bound till the root
pos.path.pop_back();
TryUpdateTillRoot(pos);
}
return;
}
}
if (possible_next_page_id != 0) {
BasicPageGuard next_page_guard = std::move(bpm->FetchPageBasic(possible_next_page_id));
if (next_page_guard.As<PageType>()->data.key_count > _ActualDataType::kMinNumberOfKeysForLeaf) {
// borrow from next
page_guard.template AsMut<PageType>()->data.p_data[page_guard.template As<PageType>()->data.key_count] =
next_page_guard.template As<PageType>()->data.p_data[0];
page_guard.template AsMut<PageType>()->data.key_count++;
next_page_guard.template AsMut<PageType>()->data.key_count--;
parent_page_guard.template AsMut<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second].first =
page_guard.template As<PageType>()
->data.p_data[page_guard.template As<PageType>()->data.key_count - 1]
.first;
memmove(next_page_guard.template AsMut<PageType>()->data.p_data,
next_page_guard.template As<PageType>()->data.p_data + 1,
next_page_guard.template As<PageType>()->data.key_count * sizeof(key_index_pair_t));
if (is_fixing_up_recursive &&
pos.path[pos.path.size() - 2].second + 1 == parent_page_guard.template As<PageType>()->data.key_count) {
if (next_page_guard.template As<PageType>()->data.key_count == _ActualDataType::kMaxKeyCount - 1) {
// special process for meaningful p_n
next_page_guard.template AsMut<PageType>()
->data.p_data[next_page_guard.template As<PageType>()->data.key_count]
.second = next_page_guard.template As<PageType>()->data.p_n;
} else {
// special process for past-the-end p_i
next_page_guard.template AsMut<PageType>()
->data.p_data[next_page_guard.template As<PageType>()->data.key_count]
.second = next_page_guard.template As<PageType>()
->data.p_data[next_page_guard.template As<PageType>()->data.key_count + 1]
.second;
}
}
// now we need to check if we have to update the right bound till the root
pos.path.pop_back();
TryUpdateTillRoot(pos);
return;
}
}
if (possible_prev_page_id != 0) {
// merge self into prev
BasicPageGuard prev_page_guard = std::move(bpm->FetchPageBasic(possible_prev_page_id));
prev_page_guard.template AsMut<PageType>()->data.p_n = page_guard.template As<PageType>()->data.p_n;
memmove(prev_page_guard.template AsMut<PageType>()->data.p_data +
prev_page_guard.template As<PageType>()->data.key_count,
page_guard.template As<PageType>()->data.p_data,
page_guard.template As<PageType>()->data.key_count * sizeof(key_index_pair_t));
prev_page_guard.template AsMut<PageType>()->data.key_count += page_guard.template As<PageType>()->data.key_count;
parent_page_guard.template AsMut<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second - 1].first =
prev_page_guard.template As<PageType>()
->data.p_data[prev_page_guard.template As<PageType>()->data.key_count - 1]
.first;
page_id_t current_page_id = page_guard.PageId();
pos.path.pop_back(); // page_guard is no longer valid
bpm->DeletePage(current_page_id);
if (!is_in_right_skew_path) {
// we need to update the parent page
RemoveEntryAt(pos, true);
return;
}
RemoveEntryInRightSkewPath(pos);
return;
}
if (possible_next_page_id != 0) {
// merge next into self
assert(possible_prev_page_id == 0);
BasicPageGuard next_page_guard = std::move(bpm->FetchPageBasic(possible_next_page_id));
if (is_fixing_up_recursive &&
pos.path[pos.path.size() - 2].second + 1 == parent_page_guard.template As<PageType>()->data.key_count) {
// the next page has past-the-end pointer
size_t intended_dest = next_page_guard.template As<PageType>()->data.key_count +
page_guard.template As<PageType>()->data.key_count;
if (intended_dest == _ActualDataType::kMaxKeyCount) {
page_guard.template AsMut<PageType>()->data.p_n =
next_page_guard.template As<PageType>()
->data.p_data[next_page_guard.template As<PageType>()->data.key_count]
.second;
} else {
page_guard.template AsMut<PageType>()->data.p_data[intended_dest].second =
next_page_guard.template As<PageType>()
->data.p_data[next_page_guard.template As<PageType>()->data.key_count]
.second;
}
}
if (!is_fixing_up_recursive) {
// update the p_n
page_guard.template AsMut<PageType>()->data.p_n = next_page_guard.template As<PageType>()->data.p_n;
}
memmove(page_guard.template AsMut<PageType>()->data.p_data + page_guard.template As<PageType>()->data.key_count,
next_page_guard.template As<PageType>()->data.p_data,
next_page_guard.template As<PageType>()->data.key_count * sizeof(key_index_pair_t));
page_guard.template AsMut<PageType>()->data.key_count += next_page_guard.template As<PageType>()->data.key_count;
parent_page_guard.template AsMut<PageType>()->data.p_data[pos.path[pos.path.size() - 2].second].first =
page_guard.template As<PageType>()->data.p_data[page_guard.template As<PageType>()->data.key_count - 1].first;
page_id_t page_id_to_delete = next_page_guard.PageId();
pos.path.pop_back(); // page_guard is no longer valid
pos.path.back().second++;
bpm->DeletePage(page_id_to_delete);
if (!is_in_right_skew_path) {
// we need to update the parent page
RemoveEntryAt(pos, true);
return;
}
RemoveEntryInRightSkewPath(pos);
return;
}
throw std::runtime_error("No sibling found!");
}
public:
// note that for safety, the iterator is not copyable, and the const_iterator is not copyable
class iterator {
BPlusTreeIndexer *domain;
size_t internal_offset;
bool is_end;
WritePageGuard guard;
friend class BPlusTreeIndexer;
public:
const KeyType &GetKey() const {
#ifdef ENABLE_ADVANCED_FEATURE
std::shared_lock<std::shared_mutex> lock_guard(domain->latch);
#endif
return guard.As<PageType>()->data.p_data[internal_offset].first;
}
const b_plus_tree_value_index_t &GetValue() {
#ifdef ENABLE_ADVANCED_FEATURE
std::shared_lock<std::shared_mutex> lock_guard(domain->latch);
#endif
return guard.As<PageType>()->data.p_data[internal_offset].second;
}
bool operator==(iterator &that) {
return domain == that.domain && is_end == that.is_end &&
(is_end || (guard.PageId() == that.guard.PageId() && internal_offset == that.internal_offset));
}
void SetValue(b_plus_tree_value_index_t new_value) {
#ifdef ENABLE_ADVANCED_FEATURE
std::unique_lock<std::shared_mutex> lock_guard(domain->latch);
#endif
guard.AsMut<PageType>()->data.p_data[internal_offset].second = new_value;
}
// only support ++it
iterator &operator++() {
if (is_end) return *this;
internal_offset++;
if (internal_offset == guard.As<PageType>()->data.key_count) {
default_numeric_index_t nxt_page_id = guard.As<PageType>()->data.p_n;
if (nxt_page_id == 0) {
is_end = true;
return *this;
}
guard = domain->bpm->FetchPageWrite(nxt_page_id);
internal_offset = 0;
}
return *this;
}
};
class const_iterator {
BPlusTreeIndexer *domain;
size_t internal_offset;
bool is_end;
ReadPageGuard guard;
friend class BPlusTreeIndexer;
public:
const KeyType &GetKey() {
#ifdef ENABLE_ADVANCED_FEATURE
std::shared_lock<std::shared_mutex> lock_guard(domain->latch);
#endif
return guard.As<PageType>()->data.p_data[internal_offset].first;
}
const b_plus_tree_value_index_t &GetValue() {
#ifdef ENABLE_ADVANCED_FEATURE
std::shared_lock<std::shared_mutex> lock_guard(domain->latch);
#endif
return guard.As<PageType>()->data.p_data[internal_offset].second;
}
bool operator==(const_iterator &that) {
return domain == that.domain && is_end == that.is_end &&
(is_end || (guard.PageId() == that.guard.PageId() && internal_offset == that.internal_offset));
}
// only support ++it
const_iterator &operator++() {
if (is_end) return *this;
internal_offset++;
if (internal_offset == guard.As<PageType>()->data.key_count) {
default_numeric_index_t nxt_page_id = guard.As<PageType>()->data.p_n;
if (nxt_page_id == 0) {
is_end = true;
return *this;
}
guard = domain->bpm->FetchPageRead(nxt_page_id);
internal_offset = 0;
}
return *this;
}
};
// void DfsCheckIndex(page_id_t cur, KeyType right_bound, bool check_right_bound) {
// BasicPageGuard guard = bpm->FetchPageBasic(cur);
// if (check_right_bound) {
// if (guard.template As<PageType>()->data.p_data[guard.template As<PageType>()->data.key_count - 1].first !=
// right_bound) {
// throw std::runtime_error("Index is not sorted!");
// }
// }
// if (guard.template As<PageType>()->data.page_status & PageStatusType::LEAF) {
// return;
// }
// for (int i = 0; i < guard.template As<PageType>()->data.key_count; i++) {
// DfsCheckIndex(guard.template As<PageType>()->data.p_data[i].second,
// guard.template As<PageType>()->data.p_data[i].first, true);
// }
// if (!check_right_bound) {
// int past_the_end_pointer;
// if (guard.template As<PageType>()->data.key_count < _ActualDataType::kMaxKeyCount) {
// past_the_end_pointer =
// guard.template As<PageType>()->data.p_data[guard.template As<PageType>()->data.key_count].second;
// } else {
// past_the_end_pointer = guard.template As<PageType>()->data.p_n;
// }
// DfsCheckIndex(past_the_end_pointer, KeyType(), false);
// }
// }
// void CheckIndex() {
// if (siz == 0) return;
// DfsCheckIndex(root_page_id, KeyType(), false);
// }
BPlusTreeIndexer() = delete;
BPlusTreeIndexer(const BPlusTreeIndexer &) = delete;
BPlusTreeIndexer(BPlusTreeIndexer &&) = delete;
BPlusTreeIndexer &operator=(const BPlusTreeIndexer &) = delete;
BPlusTreeIndexer &operator=(BPlusTreeIndexer &&) = delete;
BPlusTreeIndexer(BufferPoolManager *bpm_) {
bpm = bpm_;
raw_data_memory = bpm->RawDataMemory();
memcpy(&root_page_id, raw_data_memory, sizeof(page_id_t));
memcpy(&siz, raw_data_memory + sizeof(page_id_t), sizeof(bpt_size_t));
}
~BPlusTreeIndexer() { Flush(); }
iterator end() { // Finish Design
iterator res;
res.domain = this;
res.is_end = true;
return res;
}
const_iterator end_const() { // Finish Design
const_iterator res;
res.domain = this;
res.is_end = true;
return res;
}
iterator lower_bound(const KeyType &key) { // Finish Design
#ifdef ENABLE_ADVANCED_FEATURE
std::shared_lock<std::shared_mutex> guard(latch);
#endif
PositionSignType pos(std::move(FindPosition(key)));
iterator res;
res.domain = this;
res.is_end = pos.is_end;
if (res.is_end) return res;
res.guard = bpm->FetchPageWrite(pos.path.back().first.PageId());
res.internal_offset = pos.path.back().second;
return res;
}
const_iterator lower_bound_const(const KeyType &key) { // Finish Design
#ifdef ENABLE_ADVANCED_FEATURE
std::shared_lock<std::shared_mutex> guard(latch);
#endif
PositionSignType pos(std::move(FindPosition(key)));
const_iterator res;
res.domain = this;
res.is_end = pos.is_end;
if (res.is_end) return res;
res.guard = bpm->FetchPageRead(pos.path.back().first.PageId());
res.internal_offset = pos.path.back().second;
return res;
}
b_plus_tree_value_index_t Get(const KeyType &key) { // Finish Design
#ifdef ENABLE_ADVANCED_FEATURE
std::shared_lock<std::shared_mutex> guard(latch);
#endif
auto it = lower_bound_const(key);
if (it == end_const()) return kInvalidValueIndex;
if (key_cmp(key, it.GetKey())) return kInvalidValueIndex;
return it.GetValue();
}
bool Put(const KeyType &key, b_plus_tree_value_index_t value) { // Finish Design
#ifdef ENABLE_ADVANCED_FEATURE
std::unique_lock<std::shared_mutex> guard(latch);
#endif
PositionSignType pos(std::move(FindPosition(key)));
if (!pos.is_end &&
!key_cmp(key, pos.path.back().first.template As<PageType>()->data.p_data[pos.path.back().second].first)) {
pos.path.back().first.template AsMut<PageType>()->data.p_data[pos.path.back().second].second = value;
return false;
}
InsertEntryAt(pos, key, value);
++siz;
return true;
}
bool Remove(const KeyType &key) { // Finish Design
#ifdef ENABLE_ADVANCED_FEATURE
std::unique_lock<std::shared_mutex> guard(latch);
#endif
PositionSignType pos(std::move(FindPosition(key)));
if (pos.is_end) return false;
if (key_cmp(key, pos.path.back().first.template As<PageType>()->data.p_data[pos.path.back().second].first))
return false;
RemoveEntryAt(pos);
--siz;
return true;
}
size_t Size() { return siz; } // Finish Design
void Flush() { // Finish Design
#ifdef ENABLE_ADVANCED_FEATURE
std::unique_lock<std::shared_mutex> guard(latch);
#endif
memcpy(raw_data_memory, &root_page_id, sizeof(page_id_t));
memcpy(raw_data_memory + sizeof(page_id_t), &siz, sizeof(bpt_size_t));
bpm->FlushAllPages();
}
private:
page_id_t root_page_id; // stored in the first 4 (0-3) bytes of RawDatMemory, this directly operates on the buf
// maintained by DiskManager, BufferPoolManager only passes the pointer to it
bpt_size_t siz; // stored in the next 8 (4-11) bytes of RawDatMemory, this directly operates on the buf
// maintained by DiskManager, BufferPoolManager only passes the pointer to it
static KeyComparator key_cmp;
#ifdef ENABLE_ADVANCED_FEATURE
std::shared_mutex latch;
#endif
BufferPoolManager *bpm;
char *raw_data_memory;
};
template <typename KeyType, typename KeyComparator>
KeyComparator BPlusTreeIndexer<KeyType, KeyComparator>::key_cmp = KeyComparator();
#endif // BPT_HPP

View File

@ -0,0 +1,27 @@
#ifndef BPT_PAGE_HPP
#define BPT_PAGE_HPP
#include <utility>
#include "storage/config.h"
template <typename KeyType, size_t kPageSize = 4096>
struct ActualDataType {
typedef std::pair<KeyType, default_numeric_index_t> value_type;
page_id_t p_n;
page_status_t page_status; // root(4) / internal(2) / leaf(1)
in_page_key_count_t key_count;
const static size_t kMaxKeyCount =
(kPageSize - sizeof(page_id_t) - sizeof(page_status_t) - sizeof(in_page_key_count_t)) / sizeof(value_type);
const static size_t kMinNumberOfKeysForLeaf = (kMaxKeyCount + 1) / 2;
value_type p_data[kMaxKeyCount];
static_assert(kMaxKeyCount >= 2, "kMaxKeyCount must be greater than or equal to 2");
};
template <typename KeyType, size_t kPageSize = 4096>
union BPlusTreePage {
inline BPlusTreePage() {}
inline BPlusTreePage &operator=(const BPlusTreePage &that) {
memcpy(this, &that, sizeof(BPlusTreePage));
return *this;
}
ActualDataType<KeyType, kPageSize> data;
char filler[kPageSize];
};
#endif // BPT_PAGE_H

View File

@ -0,0 +1,401 @@
#ifndef BUFFER_POOL_MANAGER_H
#define BUFFER_POOL_MANAGER_H
#include <cstddef>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include "map.hpp"
#include "list.hpp"
#include "storage/config.h"
#include "storage/disk_manager.h"
#include "storage/replacer.h"
class BufferPoolManager;
class Page {
public:
Page();
~Page();
friend BufferPoolManager;
void ResetMemory();
char *GetData();
page_id_t GetPageId();
#ifdef ENABLE_ADVANCED_FEATURE
/** Acquire the page write latch. */
inline void WLatch() { rwlatch_.lock(); }
/** Release the page write latch. */
inline void WUnlatch() { rwlatch_.unlock(); }
/** Acquire the page read latch. */
inline void RLatch() { rwlatch_.lock_shared(); }
/** Release the page read latch. */
inline void RUnlatch() { rwlatch_.unlock_shared(); }
#endif
inline size_t GetPinCount() { return pin_count_; }
private:
#ifdef ENABLE_ADVANCED_FEATURE
std::shared_mutex rwlatch_;
#endif
char *mem;
bool is_dirty_;
size_t pin_count_;
page_id_t page_id_;
};
class BasicPageGuard {
public:
BasicPageGuard() = default;
BasicPageGuard(BufferPoolManager *bpm, Page *page) : bpm_(bpm), page_(page) {}
BasicPageGuard(const BasicPageGuard &) = delete;
auto operator=(const BasicPageGuard &) -> BasicPageGuard & = delete;
/**
* @brief Move constructor for BasicPageGuard
*
* When you call BasicPageGuard(std::move(other_guard)), you
* expect that the new guard will behave exactly like the other
* one. In addition, the old page guard should not be usable. For
* example, it should not be possible to call .Drop() on both page
* guards and have the pin count decrease by 2.
*/
BasicPageGuard(BasicPageGuard &&that) noexcept;
/**
* @brief Drop a page guard
*
* Dropping a page guard should clear all contents
* (so that the page guard is no longer useful), and
* it should tell the BPM that we are done using this page,
* per the specification in the writeup.
*/
void Drop();
/**
* @brief Move assignment for BasicPageGuard
*
* Similar to a move constructor, except that the move
* assignment assumes that BasicPageGuard already has a page
* being guarded. Think carefully about what should happen when
* a guard replaces its held page with a different one, given
* the purpose of a page guard.
*/
auto operator=(BasicPageGuard &&that) noexcept -> BasicPageGuard &;
/**
* @brief Destructor for BasicPageGuard
*
* When a page guard goes out of scope, it should behave as if
* the page guard was dropped.
*/
~BasicPageGuard();
auto PageId() -> page_id_t { return page_->GetPageId(); }
auto GetData() -> const char * { return page_->GetData(); }
template <class T>
auto As() -> const T * {
return reinterpret_cast<const T *>(GetData());
}
auto GetDataMut() -> char * {
is_dirty_ = true;
return page_->GetData();
}
template <class T>
auto AsMut() -> T * {
return reinterpret_cast<T *>(GetDataMut());
}
private:
friend class ReadPageGuard;
friend class WritePageGuard;
BufferPoolManager *bpm_{nullptr};
Page *page_{nullptr};
bool is_dirty_{false};
};
class ReadPageGuard {
public:
ReadPageGuard() = default;
ReadPageGuard(BufferPoolManager *bpm, Page *page) : guard_(bpm, page) {}
ReadPageGuard(const ReadPageGuard &) = delete;
auto operator=(const ReadPageGuard &) -> ReadPageGuard & = delete;
/**
* @brief Move constructor for ReadPageGuard
*
* Very similar to BasicPageGuard. You want to create
* a ReadPageGuard using another ReadPageGuard. Think
* about if there's any way you can make this easier for yourself...
*/
ReadPageGuard(ReadPageGuard &&that) noexcept;
/**
* @brief Move assignment for ReadPageGuard
*
* Very similar to BasicPageGuard. Given another ReadPageGuard,
* replace the contents of this one with that one.
*/
auto operator=(ReadPageGuard &&that) noexcept -> ReadPageGuard &;
/** TODO(P1): Add implementation
*
* @brief Drop a ReadPageGuard
*
* ReadPageGuard's Drop should behave similarly to BasicPageGuard,
* except that ReadPageGuard has an additional resource - the latch!
* However, you should think VERY carefully about in which order you
* want to release these resources.
*/
void Drop();
/**
* @brief Destructor for ReadPageGuard
*
* Just like with BasicPageGuard, this should behave
* as if you were dropping the guard.
*/
~ReadPageGuard();
auto PageId() -> page_id_t { return guard_.PageId(); }
auto GetData() -> const char * { return guard_.GetData(); }
template <class T>
auto As() -> const T * {
return guard_.As<T>();
}
private:
// You may choose to get rid of this and add your own private variables.
BasicPageGuard guard_;
};
class WritePageGuard {
public:
WritePageGuard() = default;
WritePageGuard(BufferPoolManager *bpm, Page *page) : guard_(bpm, page) {}
WritePageGuard(const WritePageGuard &) = delete;
auto operator=(const WritePageGuard &) -> WritePageGuard & = delete;
/** TODO(P1): Add implementation
*
* @brief Move constructor for WritePageGuard
*
* Very similar to BasicPageGuard. You want to create
* a WritePageGuard using another WritePageGuard. Think
* about if there's any way you can make this easier for yourself...
*/
WritePageGuard(WritePageGuard &&that) noexcept;
/** TODO(P1): Add implementation
*
* @brief Move assignment for WritePageGuard
*
* Very similar to BasicPageGuard. Given another WritePageGuard,
* replace the contents of this one with that one.
*/
auto operator=(WritePageGuard &&that) noexcept -> WritePageGuard &;
/** TODO(P1): Add implementation
*
* @brief Drop a WritePageGuard
*
* WritePageGuard's Drop should behave similarly to BasicPageGuard,
* except that WritePageGuard has an additional resource - the latch!
* However, you should think VERY carefully about in which order you
* want to release these resources.
*/
void Drop();
/** TODO(P1): Add implementation
*
* @brief Destructor for WritePageGuard
*
* Just like with BasicPageGuard, this should behave
* as if you were dropping the guard.
*/
~WritePageGuard();
auto PageId() -> page_id_t { return guard_.PageId(); }
auto GetData() -> const char * { return guard_.GetData(); }
template <class T>
auto As() -> const T * {
return guard_.As<T>();
}
auto GetDataMut() -> char * { return guard_.GetDataMut(); }
template <class T>
auto AsMut() -> T * {
return guard_.AsMut<T>();
}
private:
// You may choose to get rid of this and add your own private variables.
BasicPageGuard guard_;
};
class BufferPoolManager {
public:
BufferPoolManager() = delete;
BufferPoolManager(const BufferPoolManager &) = delete;
BufferPoolManager(BufferPoolManager &&) = delete;
explicit BufferPoolManager(size_t pool_size, size_t replacer_k, DiskManager *disk_manager);
BufferPoolManager &operator=(const BufferPoolManager &) = delete;
BufferPoolManager &operator=(BufferPoolManager &&) = delete;
~BufferPoolManager();
inline char *RawDataMemory() { return disk_manager->RawDataMemory(); }
inline size_t RawDatMemorySize() { return disk_manager->RawDatMemorySize(); }
/**
* @brief Allocate a page on disk. Caller should acquire the latch before calling this function.
* @return the id of the allocated page
*/
auto AllocatePage() -> page_id_t;
/**
* @brief Deallocate a page on disk. Caller should acquire the latch before calling this function.
* @param page_id id of the page to deallocate
*/
void DeallocatePage(page_id_t page_id);
/** @brief Return the size (number of frames) of the buffer pool. */
auto GetPoolSize() -> size_t;
/** @brief Return the pointer to all the pages in the buffer pool. */
auto GetPages() -> Page *;
/**
* @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 *;
/**
* TODO(P1): Add implementation
*
* @brief PageGuard wrapper for NewPage
*
* Functionality should be the same as NewPage, except that
* instead of returning a pointer to a page, you return a
* BasicPageGuard structure.
*
* @param[out] page_id, the id of the new page
* @return BasicPageGuard holding a new page
*/
auto NewPageGuarded(page_id_t *page_id) -> BasicPageGuard;
/**
* @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 calling disk_manager_->ReadPage(),
* 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) -> Page *;
/**
* TODO(P1): Add implementation
*
* @brief PageGuard wrappers for FetchPage
*
* Functionality should be the same as FetchPage, except
* that, depending on the function called, a guard is returned.
* If FetchPageRead or FetchPageWrite is called, it is expected that
* the returned page already has a read or write latch held, respectively.
*
* @param page_id, the id of the page to fetch
* @return PageGuard holding the fetched page
*/
auto FetchPageBasic(page_id_t page_id) -> BasicPageGuard;
auto FetchPageRead(page_id_t page_id) -> ReadPageGuard;
auto FetchPageWrite(page_id_t page_id) -> WritePageGuard;
/**
* 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) -> bool;
/**
* @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;
/**
* @brief Flush all the pages in the buffer pool to disk.
*
* @warning The buffer pool manager will automatically call FlushAllPages before being destroyed, but if the disk
* manager is closed, it will do nothing. And Page Guard should be destroyed before Buffer Pool Manager, Buffer Pool
* Manager should be destroyed before Disk Manager.
*/
void FlushAllPages();
/**
* @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;
private:
const size_t pool_size;
const size_t replacer_k;
LRUKReplacer replacer;
DiskManager *disk_manager;
#ifdef ENABLE_ADVANCED_FEATURE
std::mutex latch;
#endif
Page *pages_;
sjtu::map<page_id_t, frame_id_t> page_table_;
sjtu::list<frame_id_t> free_list_;
};
#endif

View File

@ -0,0 +1,20 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <cstddef>
#include <cstdint>
extern const size_t kPageSize;
typedef uint32_t default_numeric_index_t;
typedef default_numeric_index_t page_id_t;
typedef default_numeric_index_t frame_id_t;
typedef default_numeric_index_t b_plus_tree_value_index_t;
extern const default_numeric_index_t kInvalidNumericIndex;
extern const b_plus_tree_value_index_t kInvalidValueIndex;
typedef uint8_t page_status_t;
typedef uint16_t in_page_key_count_t;
typedef uint64_t bpt_size_t;
enum PageStatusType {
LEAF = 1,
INTERNAL = 2,
ROOT = 4,
};
#endif

View File

@ -0,0 +1,45 @@
#ifndef DISK_MANAGER_H
#define DISK_MANAGER_H
#include <cstdio>
#include <string>
#include "storage/config.h"
class DiskManager {
/**
* The Data Structure on Disk:
* [Internal Page] [Page 1] [Page 2] .....
* In Internal Page, the first meta_data_size bytes are used to store
* metadata(first_empty_page_id, current_total_page_count, current_none_empty_page_count), the rest are allocated to
* raw_data_memory.
* When a page is Deallocated, the first sizeof(page_id_t) bytes are used to store the next empty page
* id, then update first_empty_page_id, just like a list. To avoid unnecessary cache, use C style file operation
* instead of fstream. Note that the page_id is the offset of the page in the file, as the first page is internal,
* thus page_id is 1-based. In the list of empty pages, if the there is no next empty page, the value is 0(the same
* for first_empty_page_id).
*/
public:
explicit DiskManager(const std::string &file_path_, bool renew = false);
~DiskManager();
char *RawDataMemory();
size_t RawDatMemorySize();
void FullyFlush();
void Close();
void ReadPage(page_id_t page_id, char *page_data_ptr);
void WritePage(page_id_t page_id, const char *page_data_ptr); // in fact, the page_id is the offest
bool CurrentFileIsNew();
page_id_t AllocNewEmptyPageId();
void DeallocatePage(page_id_t page_id);
size_t CurrentTotalPageCount();
size_t CurrentNoneEmptyPageCount();
private:
std::string file_path;
page_id_t first_empty_page_id;
size_t current_total_page_count;
size_t current_none_empty_page_count;
static const size_t meta_data_size = sizeof(page_id_t) + sizeof(size_t) + sizeof(size_t);
char *raw_data_memory;
FILE *fp;
bool is_new;
char *page_buf;
};
#endif

View File

View File

@ -0,0 +1,77 @@
#ifndef REPLACER_H
#define REPLACER_H
#include <cstddef>
#include <mutex>
#include "storage/config.h"
class LRUKReplacer {
public:
LRUKReplacer() = delete;
LRUKReplacer(const LRUKReplacer &) = delete;
LRUKReplacer(LRUKReplacer &&) = delete;
explicit LRUKReplacer(size_t max_frame_count, size_t k_value);
~LRUKReplacer();
bool TryEvictLeastImportant(frame_id_t &frame_id);
void RecordAccess(frame_id_t frame_id);
void SetEvictable(frame_id_t frame_id, bool evitable);
bool TryEvictExactFrame(frame_id_t frame_id);
LRUKReplacer &operator=(const LRUKReplacer &) = delete;
LRUKReplacer &operator=(LRUKReplacer &&) = delete;
size_t GetCurrentEvitableCount();
private:
struct LRUChainNodeType { // for not has k visit
frame_id_t frame_id;
LRUChainNodeType *prev, *next;
LRUChainNodeType() = delete;
explicit LRUChainNodeType(frame_id_t frame_id, LRUChainNodeType *prev, LRUChainNodeType *next)
: frame_id(frame_id), prev(prev), next(next) {}
};
struct MainChainNodeType { // for has k visit
frame_id_t frame_id;
size_t time_stamp;
MainChainNodeType *prev, *next;
MainChainNodeType *next_self_record;
MainChainNodeType() = delete;
explicit MainChainNodeType(frame_id_t frame_id, size_t time_stamp, MainChainNodeType *prev, MainChainNodeType *next,
MainChainNodeType *next_self_record)
: frame_id(frame_id), time_stamp(time_stamp), prev(prev), next(next), next_self_record(next_self_record) {}
};
template <typename ListNodeType>
inline void RemoveFromList(ListNodeType *node) {
if (node->prev != nullptr) {
node->prev->next = node->next;
}
if (node->next != nullptr) {
node->next->prev = node->prev;
}
}
template <typename ListNodeType>
inline void InsertAt(ListNodeType *node, ListNodeType *prev, ListNodeType *next) {
node->prev = prev;
node->next = next;
if (prev != nullptr) prev->next = node;
if (next != nullptr) next->prev = node;
}
struct LRUKRecord {
LRUKRecord() = default;
bool evitable;
size_t visit_count;
bool active;
MainChainNodeType *head_node_in_main_chain, *tail_node_in_main_chain;
LRUChainNodeType *node_in_LRU_chain;
};
void RemoveWholeFrameFromLRUKChain(MainChainNodeType *first_occurrence_ptr); // remove and delete nodes
MainChainNodeType *AddRecordToMainChain(frame_id_t frame_it, size_t time_stamp,
MainChainNodeType *last_node_in_main_chain);
LRUChainNodeType *LRU_chain_head_guard, *LRU_chain_tail_guard;
MainChainNodeType *LRUK_chain_head_guard, *LRUK_chain_tail_guard;
size_t current_timestamp_{0};
size_t current_evitable_count_{0};
size_t max_frame_count;
size_t k_value;
#ifdef ENABLE_ADVANCED_FEATURE
std::mutex latch;
#endif
LRUKRecord *hash_for_record;
};
#endif

4
storage/src/bpt.cpp Normal file
View File

@ -0,0 +1,4 @@
#include "storage/bpt.hpp"
#include "storage/config.h"
const b_plus_tree_value_index_t kInvalidValueIndex = -1;
const default_numeric_index_t kInvalidNumericIndex = -1;

View File

@ -0,0 +1,302 @@
#include "storage/buffer_pool_manager.h"
#include <cstring>
#include <mutex>
#include "storage/config.h"
Page::Page() : mem(new char[kPageSize]) {}
Page::~Page() { delete[] mem; }
void Page::ResetMemory() { memset(mem, 0, kPageSize); }
char *Page::GetData() { return mem; }
page_id_t Page::GetPageId() { return page_id_; }
BasicPageGuard::BasicPageGuard(BasicPageGuard &&that) noexcept {
bpm_ = that.bpm_;
page_ = that.page_;
is_dirty_ = that.is_dirty_;
that.bpm_ = nullptr;
that.page_ = nullptr;
that.is_dirty_ = false;
}
void BasicPageGuard::Drop() {
if (bpm_ == nullptr || page_ == nullptr) {
return;
}
bpm_->UnpinPage(page_->GetPageId(), is_dirty_);
bpm_ = nullptr;
page_ = nullptr;
is_dirty_ = false;
}
auto BasicPageGuard::operator=(BasicPageGuard &&that) noexcept -> BasicPageGuard & {
if (this == &that) {
return *this;
}
Drop();
bpm_ = that.bpm_;
page_ = that.page_;
is_dirty_ = that.is_dirty_;
that.bpm_ = nullptr;
that.page_ = nullptr;
that.is_dirty_ = false;
return *this;
}
BasicPageGuard::~BasicPageGuard() { Drop(); }; // NOLINT
ReadPageGuard::ReadPageGuard(ReadPageGuard &&that) noexcept : guard_(std::move(that.guard_)) {}
auto ReadPageGuard::operator=(ReadPageGuard &&that) noexcept -> ReadPageGuard & {
if (this == &that) {
return *this;
}
#ifdef ENABLE_ADVANCED_FEATURE
if (guard_.page_ != nullptr) {
guard_.page_->RUnlatch();
}
#endif
guard_ = std::move(that.guard_);
return *this;
}
void ReadPageGuard::Drop() {
#ifdef ENABLE_ADVANCED_FEATURE
if (guard_.page_ != nullptr) {
guard_.page_->RUnlatch();
}
#endif
guard_.Drop();
}
ReadPageGuard::~ReadPageGuard() { Drop(); } // NOLINT
WritePageGuard::WritePageGuard(WritePageGuard &&that) noexcept : guard_(std::move(that.guard_)) {}
auto WritePageGuard::operator=(WritePageGuard &&that) noexcept -> WritePageGuard & {
if (this == &that) {
return *this;
}
#ifdef ENABLE_ADVANCED_FEATURE
if (guard_.page_ != nullptr) {
guard_.page_->WUnlatch();
}
#endif
guard_ = std::move(that.guard_);
return *this;
}
void WritePageGuard::Drop() {
#ifdef ENABLE_ADVANCED_FEATURE
if (guard_.page_ != nullptr) {
guard_.page_->WUnlatch();
}
#endif
guard_.Drop();
}
WritePageGuard::~WritePageGuard() { Drop(); } // NOLINT
BufferPoolManager::BufferPoolManager(size_t pool_size, size_t replacer_k, DiskManager *disk_manager)
: pool_size(pool_size),
replacer_k(replacer_k),
replacer(pool_size, replacer_k),
disk_manager(disk_manager) { // we allocate a consecutive memory space for the buffer pool
pages_ = new Page[pool_size];
// Initially, every page is in the free list.
for (size_t i = 0; i < pool_size; ++i) {
free_list_.push_back(static_cast<frame_id_t>(i));
}
}
BufferPoolManager::~BufferPoolManager() {
FlushAllPages();
delete[] pages_;
}
page_id_t BufferPoolManager::AllocatePage() {
page_id_t page_id = disk_manager->AllocNewEmptyPageId();
return page_id;
}
void BufferPoolManager::DeallocatePage(page_id_t page_id) { disk_manager->DeallocatePage(page_id); }
size_t BufferPoolManager::GetPoolSize() { return pool_size; }
Page *BufferPoolManager::GetPages() { return pages_; }
auto BufferPoolManager::NewPage(page_id_t *page_id) -> Page * {
#ifdef ENABLE_ADVANCED_FEATURE
std::lock_guard<std::mutex> guard(latch);
#endif
if (!free_list_.empty()) {
int internal_page_object_offset = free_list_.front();
free_list_.pop_front();
Page *page = &pages_[internal_page_object_offset];
*page_id = AllocatePage();
page_table_.insert({*page_id, internal_page_object_offset});
page->is_dirty_ = false;
page->page_id_ = *page_id;
page->pin_count_ = 1;
// page->ResetMemory();
replacer.RecordAccess(internal_page_object_offset);
replacer.SetEvictable(internal_page_object_offset, false);
return page;
}
frame_id_t victim_frame_id;
if (!replacer.TryEvictLeastImportant(victim_frame_id)) {
return nullptr;
}
Page *victim_page_ptr = &pages_[victim_frame_id];
if (victim_page_ptr->is_dirty_) {
disk_manager->WritePage(victim_page_ptr->page_id_, victim_page_ptr->GetData());
}
*page_id = AllocatePage();
page_table_.erase(victim_page_ptr->page_id_);
page_table_.insert({*page_id, victim_frame_id});
victim_page_ptr->is_dirty_ = false;
victim_page_ptr->pin_count_ = 1;
victim_page_ptr->page_id_ = *page_id;
victim_page_ptr->ResetMemory();
replacer.RecordAccess(victim_frame_id);
replacer.SetEvictable(victim_frame_id, false);
return victim_page_ptr;
}
auto BufferPoolManager::FetchPage(page_id_t page_id) -> Page * {
#ifdef ENABLE_ADVANCED_FEATURE
std::lock_guard<std::mutex> guard(latch);
#endif
if (page_table_.find(page_id) != page_table_.end()) {
frame_id_t frame_id = page_table_[page_id];
Page *page = &pages_[frame_id];
page->pin_count_++;
replacer.RecordAccess(frame_id);
replacer.SetEvictable(frame_id, false);
return page;
}
if (!free_list_.empty()) {
int internal_page_object_offset = free_list_.front();
free_list_.pop_front();
Page *page = &pages_[internal_page_object_offset];
page_table_.insert({page_id, internal_page_object_offset});
page->is_dirty_ = false;
page->page_id_ = page_id;
page->pin_count_ = 1;
replacer.RecordAccess(internal_page_object_offset);
replacer.SetEvictable(internal_page_object_offset, false);
disk_manager->ReadPage(page_id, page->GetData());
return page;
}
frame_id_t victim_frame_id;
if (!replacer.TryEvictLeastImportant(victim_frame_id)) {
return nullptr;
}
Page *victim_page_ptr = &pages_[victim_frame_id];
if (victim_page_ptr->is_dirty_) {
disk_manager->WritePage(victim_page_ptr->page_id_, victim_page_ptr->GetData());
}
page_table_.erase(victim_page_ptr->page_id_);
page_table_.insert({page_id, victim_frame_id});
victim_page_ptr->is_dirty_ = false;
victim_page_ptr->pin_count_ = 1;
victim_page_ptr->page_id_ = page_id;
replacer.RecordAccess(victim_frame_id);
replacer.SetEvictable(victim_frame_id, false);
disk_manager->ReadPage(page_id, victim_page_ptr->GetData());
return victim_page_ptr;
}
auto BufferPoolManager::UnpinPage(page_id_t page_id, bool is_dirty) -> bool {
#ifdef ENABLE_ADVANCED_FEATURE
std::lock_guard<std::mutex> guard(latch);
#endif
if (page_table_.find(page_id) == page_table_.end()) {
return false;
}
frame_id_t frame_id = page_table_[page_id];
Page *cur_page = &pages_[frame_id];
if (cur_page->pin_count_ <= 0) {
return false;
}
cur_page->pin_count_--;
if (cur_page->pin_count_ == 0) {
replacer.SetEvictable(frame_id, true);
}
if (is_dirty) {
cur_page->is_dirty_ = true;
}
return true;
}
auto BufferPoolManager::FlushPage(page_id_t page_id) -> bool {
#ifdef ENABLE_ADVANCED_FEATURE
std::lock_guard<std::mutex> guard(latch);
#endif
frame_id_t frame_id = page_table_[page_id];
if (page_table_.find(page_id) == page_table_.end()) {
return false;
}
Page *cur_page = &pages_[frame_id];
disk_manager->WritePage(page_id, cur_page->GetData());
cur_page->is_dirty_ = false;
return true;
}
void BufferPoolManager::FlushAllPages() {
for (auto &pair : page_table_) {
FlushPage(pair.first);
}
disk_manager->FullyFlush();
}
auto BufferPoolManager::DeletePage(page_id_t page_id) -> bool {
#ifdef ENABLE_ADVANCED_FEATURE
std::lock_guard<std::mutex> guard(latch);
#endif
if (page_table_.find(page_id) == page_table_.end()) {
return true;
}
frame_id_t frame_id = page_table_[page_id];
Page *page = &pages_[frame_id];
if (page->pin_count_ > 0) {
return false;
}
page_table_.erase(page_id);
replacer.TryEvictExactFrame(frame_id);
free_list_.push_back(frame_id);
page->is_dirty_ = false;
page->pin_count_ = 0;
page->page_id_ = 0;
page->ResetMemory();
DeallocatePage(page_id);
return true;
}
auto BufferPoolManager::FetchPageBasic(page_id_t page_id) -> BasicPageGuard {
Page *page = FetchPage(page_id);
if (page == nullptr) throw std::runtime_error("Buffer Pool is full!");
return {this, page};
}
auto BufferPoolManager::FetchPageRead(page_id_t page_id) -> ReadPageGuard {
Page *page = FetchPage(page_id);
if (page == nullptr) throw std::runtime_error("Buffer Pool is full!");
#ifdef ENABLE_ADVANCED_FEATURE
if (page != nullptr) {
page->RLatch();
}
#endif
return {this, page};
}
auto BufferPoolManager::FetchPageWrite(page_id_t page_id) -> WritePageGuard {
Page *page = FetchPage(page_id);
if (page == nullptr) throw std::runtime_error("Buffer Pool is full!");
#ifdef ENABLE_ADVANCED_FEATURE
if (page != nullptr) {
page->WLatch();
}
#endif
return {this, page};
}
auto BufferPoolManager::NewPageGuarded(page_id_t *page_id) -> BasicPageGuard {
Page *page = NewPage(page_id);
if (page == nullptr) throw std::runtime_error("Buffer Pool is full!");
return {this, page};
}

View File

@ -0,0 +1,108 @@
#include "storage/disk_manager.h"
#include <cstring>
#include <exception>
#include <stdexcept>
const size_t kPageSize = 4096;
DiskManager::DiskManager(const std::string &file_path_, bool renew)
: file_path(file_path_),
first_empty_page_id(0),
current_total_page_count(0),
current_none_empty_page_count(0),
raw_data_memory(nullptr),
fp(nullptr) {
if (renew) remove(file_path.c_str());
fp = fopen(file_path.c_str(), "r+b");
if (fp == nullptr) {
// File doesn't exist, create a new one
fp = fopen(file_path.c_str(), "w+b");
// Initialize internal page
first_empty_page_id = 0;
current_total_page_count = 0;
current_none_empty_page_count = 0;
raw_data_memory = new char[kPageSize - meta_data_size];
memset(raw_data_memory, 0, kPageSize - meta_data_size);
FullyFlush();
is_new = true;
} else {
// File exists, read metadata from internal page
fseek(fp, 0, SEEK_SET);
fread(&first_empty_page_id, sizeof(page_id_t), 1, fp);
fread(&current_total_page_count, sizeof(size_t), 1, fp);
fread(&current_none_empty_page_count, sizeof(size_t), 1, fp);
raw_data_memory = new char[kPageSize - meta_data_size];
fread(raw_data_memory, kPageSize - meta_data_size, 1, fp);
is_new = false;
}
page_buf = new char[kPageSize];
}
DiskManager::~DiskManager() {
Close();
delete[] raw_data_memory;
delete[] page_buf;
}
char *DiskManager::RawDataMemory() { return raw_data_memory; }
size_t DiskManager::RawDatMemorySize() { return kPageSize - meta_data_size; }
void DiskManager::FullyFlush() {
if (fp == nullptr) return;
fseek(fp, 0, SEEK_SET);
fwrite(&first_empty_page_id, sizeof(page_id_t), 1, fp);
fwrite(&current_total_page_count, sizeof(size_t), 1, fp);
fwrite(&current_none_empty_page_count, sizeof(size_t), 1, fp);
fwrite(raw_data_memory, kPageSize - meta_data_size, 1, fp);
fflush(fp);
}
void DiskManager::Close() {
if (fp != nullptr) {
FullyFlush();
fclose(fp);
fp = nullptr;
}
}
void DiskManager::ReadPage(page_id_t page_id, char *page_data_ptr) {
if (fp == nullptr) return;
fseek(fp, page_id * kPageSize, SEEK_SET);
fread(page_data_ptr, kPageSize, 1, fp);
}
void DiskManager::WritePage(page_id_t page_id, const char *page_data_ptr) {
if (fp == nullptr) return;
fseek(fp, page_id * kPageSize, SEEK_SET);
fwrite(page_data_ptr, kPageSize, 1, fp);
}
bool DiskManager::CurrentFileIsNew() { return is_new; }
page_id_t DiskManager::AllocNewEmptyPageId() {
page_id_t new_page_id;
if (first_empty_page_id == 0) {
// No empty page available, append a new page
current_total_page_count++;
new_page_id = current_total_page_count;
fseek(fp, 0, SEEK_END);
fwrite(page_buf, kPageSize, 1, fp);
} else {
new_page_id = first_empty_page_id;
ReadPage(new_page_id, page_buf);
memcpy(&first_empty_page_id, page_buf, sizeof(page_id_t));
}
current_none_empty_page_count++;
return new_page_id;
}
void DiskManager::DeallocatePage(page_id_t page_id) {
// Add the deallocated page to the head of the empty list
memcpy(page_buf, &first_empty_page_id, sizeof(page_id_t));
WritePage(page_id, page_buf);
first_empty_page_id = page_id;
current_none_empty_page_count--;
}
size_t DiskManager::CurrentTotalPageCount() { return current_total_page_count; }
size_t DiskManager::CurrentNoneEmptyPageCount() { return current_none_empty_page_count; }

173
storage/src/replacer.cpp Normal file
View File

@ -0,0 +1,173 @@
#include "storage/replacer.h"
#include <cstddef>
LRUKReplacer::LRUKReplacer(size_t max_frame_count, size_t k_value)
: max_frame_count(max_frame_count), k_value(k_value) {
hash_for_record = new LRUKRecord[max_frame_count];
for (size_t i = 0; i < max_frame_count; i++) {
hash_for_record[i].active = false;
}
LRU_chain_head_guard = new LRUChainNodeType(-1, nullptr, nullptr);
LRU_chain_tail_guard = new LRUChainNodeType(-1, nullptr, nullptr);
LRU_chain_head_guard->next = LRU_chain_tail_guard;
LRU_chain_tail_guard->prev = LRU_chain_head_guard;
LRUK_chain_head_guard = new MainChainNodeType(-1, 0, nullptr, nullptr, nullptr);
LRUK_chain_tail_guard = new MainChainNodeType(-1, 0, nullptr, nullptr, nullptr);
LRUK_chain_head_guard->next = LRUK_chain_tail_guard;
LRUK_chain_tail_guard->prev = LRUK_chain_head_guard;
}
LRUKReplacer::~LRUKReplacer() {
delete[] hash_for_record;
LRUChainNodeType *ptr = LRU_chain_head_guard->next;
while (ptr != LRU_chain_tail_guard) {
LRUChainNodeType *tmp = ptr;
ptr = ptr->next;
delete tmp;
}
MainChainNodeType *ptr2 = LRUK_chain_head_guard->next;
while (ptr2 != LRUK_chain_tail_guard) {
MainChainNodeType *tmp = ptr2;
ptr2 = ptr2->next;
delete tmp;
}
delete LRU_chain_head_guard;
delete LRU_chain_tail_guard;
delete LRUK_chain_head_guard;
delete LRUK_chain_tail_guard;
}
void LRUKReplacer::SetEvictable(frame_id_t frame_id, bool evitable) {
#ifdef ENABLE_ADVANCED_FEATURE
std::lock_guard<std::mutex> guard(latch);
#endif
if (!hash_for_record[frame_id].active) {
return;
}
if (hash_for_record[frame_id].evitable == evitable) {
return;
}
hash_for_record[frame_id].evitable = evitable;
if (evitable) {
current_evitable_count_++;
} else {
current_evitable_count_--;
}
}
void LRUKReplacer::RemoveWholeFrameFromLRUKChain(MainChainNodeType *first_occurrence_ptr) {
if (first_occurrence_ptr == nullptr) return;
MainChainNodeType *tmp;
while (first_occurrence_ptr != nullptr) {
tmp = first_occurrence_ptr;
RemoveFromList(tmp);
first_occurrence_ptr = first_occurrence_ptr->next_self_record;
delete tmp;
}
}
LRUKReplacer::MainChainNodeType *LRUKReplacer::AddRecordToMainChain(frame_id_t frame_id, size_t time_stamp,
MainChainNodeType *last_node_in_main_chain) {
MainChainNodeType *new_node = new LRUKReplacer::MainChainNodeType(frame_id, time_stamp, nullptr, nullptr, nullptr);
if (last_node_in_main_chain != nullptr) last_node_in_main_chain->next_self_record = new_node;
InsertAt(new_node, LRUK_chain_tail_guard->prev, LRUK_chain_tail_guard);
return new_node;
}
bool LRUKReplacer::TryEvictExactFrame(frame_id_t frame_id) {
#ifdef ENABLE_ADVANCED_FEATURE
std::lock_guard<std::mutex> guard(latch);
#endif
if (!hash_for_record[frame_id].active) {
return false;
}
if (!hash_for_record[frame_id].evitable) {
return false;
}
LRUChainNodeType *node = hash_for_record[frame_id].node_in_LRU_chain;
if (node != nullptr) RemoveFromList(node);
delete node;
hash_for_record[frame_id].node_in_LRU_chain = nullptr;
RemoveWholeFrameFromLRUKChain(hash_for_record[frame_id].head_node_in_main_chain);
hash_for_record[frame_id].active = false;
current_evitable_count_--;
return true;
}
bool LRUKReplacer::TryEvictLeastImportant(frame_id_t &frame_id) {
#ifdef ENABLE_ADVANCED_FEATURE
latch.lock();
#endif
if (current_evitable_count_ == 0) {
#ifdef ENABLE_ADVANCED_FEATURE
latch.unlock();
#endif
return false;
}
LRUChainNodeType *node = LRU_chain_head_guard->next;
while (node != LRU_chain_tail_guard) {
frame_id = node->frame_id;
if (hash_for_record[frame_id].evitable) {
#ifdef ENABLE_ADVANCED_FEATURE
latch.unlock();
#endif
return TryEvictExactFrame(frame_id);
}
node = node->next;
}
MainChainNodeType *main_chain_node = LRUK_chain_head_guard->next;
while (main_chain_node != LRUK_chain_tail_guard) {
frame_id = main_chain_node->frame_id;
if (hash_for_record[frame_id].evitable) {
#ifdef ENABLE_ADVANCED_FEATURE
latch.unlock();
#endif
return TryEvictExactFrame(frame_id);
}
main_chain_node = main_chain_node->next;
}
#ifdef ENABLE_ADVANCED_FEATURE
latch.unlock();
#endif
return false;
}
void LRUKReplacer::RecordAccess(frame_id_t frame_id) {
#ifdef ENABLE_ADVANCED_FEATURE
std::lock_guard<std::mutex> guard(latch);
#endif
current_timestamp_++;
if (!hash_for_record[frame_id].active) {
hash_for_record[frame_id].active = true;
hash_for_record[frame_id].evitable = false;
hash_for_record[frame_id].visit_count = 1;
hash_for_record[frame_id].head_node_in_main_chain = hash_for_record[frame_id].tail_node_in_main_chain =
AddRecordToMainChain(frame_id, current_timestamp_, nullptr);
hash_for_record[frame_id].node_in_LRU_chain =
new LRUChainNodeType(frame_id, LRU_chain_tail_guard->prev, LRU_chain_tail_guard);
InsertAt(hash_for_record[frame_id].node_in_LRU_chain, LRU_chain_tail_guard->prev, LRU_chain_tail_guard);
} else {
hash_for_record[frame_id].visit_count++;
MainChainNodeType *last_occurrence_ptr = hash_for_record[frame_id].tail_node_in_main_chain;
MainChainNodeType *new_node_in_main_chain_ptr =
AddRecordToMainChain(frame_id, current_timestamp_, last_occurrence_ptr);
hash_for_record[frame_id].tail_node_in_main_chain = new_node_in_main_chain_ptr;
if (hash_for_record[frame_id].node_in_LRU_chain != nullptr) {
RemoveFromList(hash_for_record[frame_id].node_in_LRU_chain);
delete hash_for_record[frame_id].node_in_LRU_chain;
hash_for_record[frame_id].node_in_LRU_chain = nullptr;
}
if (hash_for_record[frame_id].visit_count < k_value) {
hash_for_record[frame_id].node_in_LRU_chain =
new LRUChainNodeType(frame_id, LRU_chain_tail_guard->prev, LRU_chain_tail_guard);
InsertAt(hash_for_record[frame_id].node_in_LRU_chain, LRU_chain_tail_guard->prev, LRU_chain_tail_guard);
}
if (hash_for_record[frame_id].visit_count > k_value) {
MainChainNodeType *first_occurrence_ptr = hash_for_record[frame_id].head_node_in_main_chain;
RemoveFromList(first_occurrence_ptr);
hash_for_record[frame_id].head_node_in_main_chain = first_occurrence_ptr->next_self_record;
delete first_occurrence_ptr;
}
}
}
size_t LRUKReplacer::GetCurrentEvitableCount() { return current_evitable_count_; }