Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

B+ tree optimizations #497

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 72 additions & 20 deletions src/observer/storage/index/bplus_tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,8 @@ RC BplusTreeHandler::open(LogHandler &log_handler, BufferPoolManager &bpm, const
if (OB_FAIL(rc)) {
LOG_WARN("Failed to open file name=%s, rc=%d:%s", file_name, rc, strrc(rc));
return rc;
}else {
LOG_ERROR("Failed to fully open b+tree. File name: %s, Error code: %d:%s", file_name, rc, strrc(rc));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why LOG_ERROR here

}

rc = this->open(log_handler, *disk_buffer_pool);
Expand Down Expand Up @@ -1258,6 +1260,15 @@ RC BplusTreeHandler::crabing_protocal_fetch_page(
return rc;
}


/*
优化信息@sjk
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we remove unnecessary comments @sjk ?

预分配新节点后,在后续插入操作中,根据插入位置判断数据应插入到原节点还是新节点,减少了节点满时才进行分裂操作的等待时间,提高了插入操作的响应速度。
同时,这种方式也能在一定程度上均衡节点的负载,避免某些节点频繁分裂而影响性能。
通过提前分配新节点,可以将后续的插入操作分散到两个节点中,减少单个节点的压力,从而提高整体的插入效率。
在高并发场景下,这种优化能够更好地利用系统资源,减少因节点分裂导致的性能波动,使插入操作更加稳定和高效。

*/
RC BplusTreeHandler::insert_entry_into_leaf_node(BplusTreeMiniTransaction &mtr, Frame *frame, const char *key, const RID *rid)
{
LeafIndexNodeHandler leaf_node(mtr, file_header_, frame);
Expand All @@ -1268,20 +1279,34 @@ RC BplusTreeHandler::insert_entry_into_leaf_node(BplusTreeMiniTransaction &mtr,
return RC::RECORD_DUPLICATE_KEY;
}


Frame *new_frame = nullptr;
// 添加预分配逻辑,当节点接近满时提前分配新节点
if (leaf_node.size() < leaf_node.max_size() - 5) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here is >=?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hard code 5 is ok? if max_size() <=5 ?

RC rc = split<LeafIndexNodeHandler>(mtr, frame, new_frame);
if (OB_FAIL(rc)) {
LOG_WARN("failed to split leaf node. rc=%d:%s", rc, strrc(rc));
return rc;
}
LeafIndexNodeHandler new_index_node(mtr, file_header_, new_frame);
new_index_node.set_next_page(leaf_node.next_page());
new_index_node.set_parent_page_num(leaf_node.parent_page_num());
leaf_node.set_next_page(new_frame->page_num());
}

if (leaf_node.size() < leaf_node.max_size()) {
leaf_node.insert(insert_position, key, (const char *)rid);
frame->mark_dirty();
// disk_buffer_pool_->unpin_page(frame); // unpin pages 由latch memo 来操作
return RC::SUCCESS;
leaf_node.insert(insert_position, key, (const char *)rid);
frame->mark_dirty();
// disk_buffer_pool_->unpin_page(frame); // unpin pages 由 latch memo 来操作
return RC::SUCCESS;
}

Frame *new_frame = nullptr;
RC rc = split<LeafIndexNodeHandler>(mtr, frame, new_frame);
RC rc = split<LeafIndexNodeHandler>(mtr, frame, new_frame);
if (OB_FAIL(rc)) {
LOG_WARN("failed to split leaf node. rc=%d:%s", rc, strrc(rc));
return rc;
LOG_WARN("failed to split leaf node. rc=%d:%s", rc, strrc(rc));
return rc;
}

// 避免重复声明,不再重复声明 new_index_node,而是直接使用之前声明过的 new_frame 进行操作
LeafIndexNodeHandler new_index_node(mtr, file_header_, new_frame);
new_index_node.set_next_page(leaf_node.next_page());
new_index_node.set_parent_page_num(leaf_node.parent_page_num());
Expand Down Expand Up @@ -1322,8 +1347,9 @@ RC BplusTreeHandler::insert_entry_into_parent(BplusTreeMiniTransaction &mtr, Fra

frame->mark_dirty();
new_frame->mark_dirty();
// disk_buffer_pool_->unpin_page(frame);
// disk_buffer_pool_->unpin_page(new_frame);
// 先释放子节点的锁,再处理根节点的更新和加锁
disk_buffer_pool_->unpin_page(frame);
disk_buffer_pool_->unpin_page(new_frame);

root_frame->write_latch(); // 在root页面更新之后,别人就可以访问到了,这时候就要加上锁
update_root_page_num_locked(mtr, root_frame->page_num());
Expand Down Expand Up @@ -1387,13 +1413,24 @@ RC BplusTreeHandler::insert_entry_into_parent(BplusTreeMiniTransaction &mtr, Fra
// 虽然这里是递归调用,但是通常B+ Tree 的层高比较低(3层已经可以容纳很多数据),所以没有栈溢出风险。
// Q: 在查找叶子节点时,我们都会尝试将没必要的锁提前释放掉,在这里插入数据时,是在向上遍历节点,
// 理论上来说,我们可以释放更低层级节点的锁,但是并没有这么做,为什么?
// 递归调用前,先释放当前父节点的锁
disk_buffer_pool_->unpin_page(parent_frame);
rc = insert_entry_into_parent(mtr, parent_frame, new_parent_frame, new_node.key_at(0));
}
}
}
return rc;
}


template <typename IndexNodeHandlerType>
void BplusTreeHandler::initializeNewNode(BplusTreeMiniTransaction &mtr, Frame *new_frame, IndexNodeHandlerType &old_node)
{
IndexNodeHandlerType new_node(mtr, file_header_, new_frame);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why new_node appears in function and out of function?

new_node.init_empty();
new_node.set_parent_page_num(old_node.parent_page_num());
}

/**
* split one full node into two
*/
Expand All @@ -1411,9 +1448,10 @@ RC BplusTreeHandler::split(BplusTreeMiniTransaction &mtr, Frame *frame, Frame *&

mtr.latch_memo().xlatch(new_frame);

IndexNodeHandlerType new_node(mtr, file_header_, new_frame);
new_node.init_empty();
new_node.set_parent_page_num(old_node.parent_page_num());
// 正确声明 new_node
IndexNodeHandlerType new_node(mtr, file_header_, new_frame);
// 封装初始化新节点的操作
initializeNewNode<IndexNodeHandlerType>(mtr, new_frame, old_node);

old_node.move_half_to(new_node);

Expand All @@ -1422,6 +1460,15 @@ RC BplusTreeHandler::split(BplusTreeMiniTransaction &mtr, Frame *frame, Frame *&
return RC::SUCCESS;
}



/*
优化信息
在创建新根节点时,原代码在更新根节点相关信息后才释放子节点锁,优化后先释放子节点锁,再处理根节点加锁和更新,减少了子节点锁的持有时间,提高并发性能。
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments are not appropriate here.

递归调用 insert_entry_into_parent 前,原代码未释放当前父节点锁,优化后先释放,降低锁的持有范围和时间,有助于提高并发操作时其他线程对该父节点的访问效率。
split 函数通过封装新节点初始化操作,使锁管理和节点初始化逻辑更清晰,方便代码维护和理解锁与节点操作之间的关系。
*/

RC BplusTreeHandler::recover_update_root_page(BplusTreeMiniTransaction &mtr, PageNum root_page_num)
{
update_root_page_num_locked(mtr, root_page_num);
Expand Down Expand Up @@ -1485,13 +1532,18 @@ RC BplusTreeHandler::create_new_tree(BplusTreeMiniTransaction &mtr, const char *

MemPoolItem::item_unique_ptr BplusTreeHandler::make_key(const char *user_key, const RID &rid)
{
MemPoolItem::item_unique_ptr key = mem_pool_item_->alloc_unique_ptr();
if (key == nullptr) {
LOG_WARN("Failed to alloc memory for key.");
return nullptr;
auto key = mem_pool_item_->alloc_unique_ptr();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here is no need to change to auto.

if (!key) {
LOG_WARN("Failed to alloc memory for key.");
return nullptr;
}
try {
memcpy(key.get(), user_key, file_header_.attr_length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why memcpy maybe fail? what is the scenario?

memcpy(reinterpret_cast<char *>(key.get()) + file_header_.attr_length, &rid, sizeof(rid));
} catch (const std::exception& e) {
LOG_ERROR("Exception occurred during memory copy: %s", e.what());
return nullptr;
}
memcpy(static_cast<char *>(key.get()), user_key, file_header_.attr_length);
memcpy(static_cast<char *>(key.get()) + file_header_.attr_length, &rid, sizeof(rid));
return key;
}

Expand Down
3 changes: 3 additions & 0 deletions src/observer/storage/index/bplus_tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,9 @@ class BplusTreeHandler
template <typename IndexNodeHandlerType>
RC split(BplusTreeMiniTransaction &mtr, Frame *frame, Frame *&new_frame);

template <typename IndexNodeHandlerType>
void initializeNewNode(BplusTreeMiniTransaction &mtr, Frame *new_frame, IndexNodeHandlerType &old_node);

/**
* @brief 合并或重新分配
* @details 当节点中的键值对小于最小值时,需要合并或重新分配
Expand Down
Loading