Skip to content

Commit

Permalink
SDSTOR-10892 : concurrent mem btree UT
Browse files Browse the repository at this point in the history
  • Loading branch information
shosseinimotlagh committed Sep 15, 2023
1 parent 574c7df commit ba95ef7
Show file tree
Hide file tree
Showing 13 changed files with 783 additions and 38 deletions.
2 changes: 1 addition & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

class HomestoreConan(ConanFile):
name = "homestore"
version = "4.2.1"
version = "4.2.2"

homepage = "https://github.com/eBay/Homestore"
description = "HomeStore Storage Engine"
Expand Down
21 changes: 15 additions & 6 deletions src/include/homestore/btree/btree.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,22 @@ class Btree {

// This workaround of BtreeThreadVariables is needed instead of directly declaring statics
// to overcome the gcc bug, pointer here: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66944
// static BtreeThreadVariables* bt_thread_vars() {
// static thread_local BtreeThreadVariables* s_ptr{nullptr};
// if (s_ptr == nullptr) {
// static thread_local BtreeThreadVariables inst;
// s_ptr = &inst;
// }
// return s_ptr;
// }
static BtreeThreadVariables* bt_thread_vars() {
static thread_local BtreeThreadVariables* s_ptr{nullptr};
if (s_ptr == nullptr) {
static thread_local BtreeThreadVariables inst;
s_ptr = &inst;
}
return s_ptr;
//static boost::fibers::local_fiber <BtreeThreadVariables> *s_ptr{nullptr};
auto this_id( boost::this_fiber::get_id() );
static thread_local std::map<fibers::fiber::id, std::unique_ptr<BtreeThreadVariables>> fiber_map;
if (fiber_map.count(this_id))
return fiber_map[this_id].get();
fiber_map[this_id] = std::make_unique<BtreeThreadVariables>();
return fiber_map[this_id].get();
}

protected:
Expand Down
3 changes: 2 additions & 1 deletion src/include/homestore/btree/btree.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -327,11 +327,12 @@ void Btree< K, V >::print_tree(const std::string& file) const {
to_string(m_root_node_info.bnode_id(), buf);
m_btree_lock.unlock_shared();

BT_LOG(INFO, "Pre order traversal of tree:\n<{}>", buf);
if (!file.empty()) {
std::ofstream o(file);
o.write(buf.c_str(), buf.size());
o.flush();
}else{
BT_LOG(INFO, "Pre order traversal of tree:\n<{}>", buf);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/include/homestore/btree/detail/btree_mutate_impl.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ btree_status_t Btree< K, V >::split_node(const BtreeNodePtr& parent_node, const
}
BT_NODE_LOG(TRACE, parent_node, "Available space for split entry={}", parent_node->available_size(m_bt_cfg));

child_node1->inc_link_version();
// child_node1->inc_link_version();

// Update the existing parent node entry to point to second child ptr.
parent_node->update(parent_ind, child_node2->link_info());
Expand Down
12 changes: 7 additions & 5 deletions src/include/homestore/btree/detail/btree_remove_impl.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ retry:
// We get the trimmed range only for leaf because this is where we will be removing keys. In interior
// nodes, keys are always propogated from the lower nodes.
bool is_inp_key_lesser = false;
K end_key = my_node->min_of(s_cast< const K& >(req.input_range().end_key()), curr_idx, is_inp_key_lesser);
K end_key =
my_node->min_of(s_cast< const K& >(req.input_range().end_key()), curr_idx, is_inp_key_lesser);
bool end_incl = is_inp_key_lesser ? req.input_range().is_end_inclusive() : true;
req.trim_working_range(std::move(end_key), end_incl);

Expand Down Expand Up @@ -312,6 +313,7 @@ btree_status_t Btree< K, V >::check_collapse_root(ReqT& req) {

free_node(root, locktype_t::WRITE, req.m_op_context);
m_root_node_info = child->link_info();
update_new_root_info(m_root_node_info.bnode_id(), m_root_node_info.link_version());
unlock_node(child, locktype_t::WRITE);

// TODO: Have a precommit code here to notify the change in root node id
Expand Down Expand Up @@ -392,9 +394,9 @@ btree_status_t Btree< K, V >::merge_nodes(const BtreeNodePtr& parent_node, const
// First try to see how many entries you can fit in the leftmost node within the balanced size. We are checking
// leftmost node as special case without moving, because that is the only node which is modified in-place and hence
// doing a dry run and if for some reason there is a problem in balancing the nodes, then it is easy to give up.
available_size = static_cast<int32_t> (balanced_size) - leftmost_node->occupied_size(m_bt_cfg);
available_size = static_cast< int32_t >(balanced_size) - leftmost_node->occupied_size(m_bt_cfg);
src_cursor.ith_node = old_nodes.size();
for (uint32_t i{0}; (i < old_nodes.size() && available_size >= 0) ; ++i) {
for (uint32_t i{0}; (i < old_nodes.size() && available_size >= 0); ++i) {
leftmost_src.ith_nodes.push_back(i);
// TODO: check whether value size of the node is greater than available_size? If so nentries is 0. Suppose if a
// node contains one entry and the value size is much bigger than available size
Expand Down Expand Up @@ -468,7 +470,7 @@ btree_status_t Btree< K, V >::merge_nodes(const BtreeNodePtr& parent_node, const
post_merge_size += old_node->get_nth_obj_size(
std::min(leftmost_src.last_node_upto, old_node->total_entries() - 1)); // New leftmost entry
}
post_merge_size -= parent_node->get_nth_obj_size(start_idx); // Previous left entry
post_merge_size -= parent_node->get_nth_obj_size(start_idx); // Previous left entry

for (auto& node : new_nodes) {
if (node->total_entries()) { post_merge_size += node->get_nth_obj_size(node->total_entries() - 1); }
Expand Down Expand Up @@ -526,7 +528,7 @@ btree_status_t Btree< K, V >::merge_nodes(const BtreeNodePtr& parent_node, const
// Finally update the leftmost node with latest key
leftmost_node->set_next_bnode(next_node_id);
if (leftmost_node->total_entries()) {
leftmost_node->inc_link_version();
// leftmost_node->inc_link_version();
parent_node->update(start_idx, leftmost_node->get_last_key< K >(), leftmost_node->link_info());
}

Expand Down
2 changes: 1 addition & 1 deletion src/include/homestore/btree/detail/simple_node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class SimpleNode : public BtreeNode {
}

uint32_t num_entries_by_size(uint32_t start_idx, uint32_t size) const override {
uint32_t possible_entries = (size - 1) / get_nth_obj_size(0) + 1;
uint32_t possible_entries = (size == 0) ? 0 : (size - 1) / get_nth_obj_size(0) + 1;
return std::min(possible_entries, this->total_entries() - start_idx);
}

Expand Down
1 change: 1 addition & 0 deletions src/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ include (${CMAKE_SOURCE_DIR}/cmake/test_mode.cmake)
include_directories (BEFORE ../include/)
include_directories (BEFORE ../lib/)
include_directories (BEFORE .)
add_subdirectory(test_scripts)

can_build_nonio_tests(build_nonio_tests)
if (${build_nonio_tests})
Expand Down
3 changes: 2 additions & 1 deletion src/tests/btree_test_kvs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ static std::normal_distribution<> g_randkeysize_generator{32, 24};
static std::uniform_int_distribution< uint32_t > g_randval_generator{1, 30000};
static std::normal_distribution<> g_randvalsize_generator{32, 24};
// static std::uniform_int_distribution< uint32_t > g_randvalsize_generator{2, g_max_valsize};

static std::mutex g_map_lk;
static std::map< uint32_t, std::shared_ptr< std::string > > g_key_pool;

static constexpr std::array< const char, 62 > alphanum{
Expand Down Expand Up @@ -140,6 +140,7 @@ class TestVarLenKey : public BtreeKey {
}

static std::shared_ptr< std::string > idx_to_key(uint32_t idx) {
std::unique_lock< std::mutex > lk(g_map_lk);
auto it = g_key_pool.find(idx);
if (it == g_key_pool.end()) {
const auto& [it, happened] =
Expand Down
226 changes: 226 additions & 0 deletions src/tests/test_common/range_scheduler.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
/*********************************************************************************
* Modifications Copyright 2017-2019 eBay Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*********************************************************************************/
/*
* Homestore testing binaries shared common definitions, apis and data structures
*
*/

#pragma once

#include <boost/icl/closed_interval.hpp>
#include <boost/icl/interval_set.hpp>
#include <boost/icl/separate_interval_set.hpp>
#include <boost/icl/split_interval_set.hpp>
#include <cassert>
namespace homestore {
using namespace boost::icl;
typedef interval_set< uint32_t > set_t;
typedef set_t::interval_type ival;
using mutex = iomgr::FiberManagerLib::shared_mutex;

class RangeScheduler {
public:
void remove_keys_from_working(uint32_t s, uint32_t e) {
std::unique_lock< mutex > lk(m_set_lock);
remove_from_working(s, e);
}

void put_key(uint32_t key) {
std::unique_lock< mutex > lk(m_set_lock);
add_to_existing(key);
remove_from_working(key);
}

void put_keys(uint32_t start_key, uint32_t end_key) {
std::unique_lock< mutex > lk(m_set_lock);
add_to_existing(start_key, end_key);
remove_from_working(start_key, end_key);
}

void remove_key(uint32_t key) {
std::unique_lock< mutex > lk(m_set_lock);
remove_from_existing(key);
remove_from_working(key);
}

void remove_keys(uint32_t start_key, uint32_t end_key) {
std::unique_lock< mutex > lk(m_set_lock);
remove_from_existing(start_key, end_key);
remove_from_working(start_key, end_key);
}

int pick_random_non_existing_keys(uint32_t n_keys = 1, uint32_t max_range = 0) {
std::unique_lock< mutex > lk(m_set_lock);
uint32_t working_range = max_range <= 0 ? std::numeric_limits< uint32_t >::max() : max_range;
uint32_t num_retry = 0;

auto num_intervals = static_cast< uint32_t >(m_existing_keys.iterative_size());
std::uniform_int_distribution< uint32_t > s_rand_interval_generator{0, num_intervals - 1};
uint32_t start_key = std::numeric_limits< uint32_t >::max();

while (num_retry < max_retries) {
// find a random interval
uint32_t next_lower = working_range;
uint32_t previous_upper = 0;
auto it = m_existing_keys.begin();
// if the selected interval is the last ... check size between this one and the working_range, rand n keys
// in (previous_upper, working_range] = [previous_upper+1, working_range] choose the gap between this upper
// and the next begin. and check the size! rand nkeys in [previous_upper, next_lower]
if (num_intervals != 0) {
uint32_t cur_interval_idx = s_rand_interval_generator(m_re);
std::advance(it, cur_interval_idx);
previous_upper = last(*it) + 1; // to be inclusivelast
it++;
if (it != m_existing_keys.end()) { next_lower = first(*it) - 1; }
}
if ((next_lower + 1) < (n_keys + previous_upper)) { // check < or <=
num_retry++;
continue;
}

// choose randomly n keys in [previous_upper, next_lower]
std::uniform_int_distribution< uint32_t > rand_key_generator{
previous_upper, next_lower - n_keys + 1}; // n_keys or n_keys +- (1)
start_key = rand_key_generator(m_re);
auto found = (m_working_keys & ival::closed(start_key, start_key + n_keys - 1));
if (found.empty()) {
auto validate = m_existing_keys & ival::closed(start_key, start_key + n_keys - 1);
assert(validate.empty());
break;
}
num_retry++;
continue;
}
if (num_retry == max_retries) { return -1; }
// add from working keys and return the start_key;
this->add_to_working(start_key, start_key + n_keys - 1);
assert(start_key + n_keys - 1 <= working_range);
return static_cast< int >(start_key);
}

int pick_random_existing_keys(uint32_t n_keys = 1, uint32_t max_range = 0) {
std::unique_lock< mutex > lk(m_set_lock);
uint32_t working_range = max_range <= 0 ? std::numeric_limits< uint32_t >::max() : max_range;
uint32_t num_retry = 0;

auto num_intervals = static_cast< uint32_t >(m_existing_keys.iterative_size());
// empty keys
if (num_intervals == 0) { return -1; }
std::uniform_int_distribution< uint32_t > s_rand_interval_generator{0, num_intervals - 1};
uint32_t start_key = std::numeric_limits< uint32_t >::max();

while (num_retry < max_retries) {
// find a random interval
auto it = m_existing_keys.begin();
uint32_t cur_interval_idx = s_rand_interval_generator(m_re);
std::advance(it, cur_interval_idx);
uint32_t upper = last(*it);
uint32_t lower = first(*it);
if ((upper + 1) < (n_keys + lower)) {
num_retry++;
continue;
}
// choose randomly n keys in [lower, upper]
std::uniform_int_distribution< uint32_t > rand_key_generator{lower, upper - n_keys + 1};
start_key = rand_key_generator(m_re);
auto found = (m_working_keys & ival::closed(start_key, start_key + n_keys - 1));
if (found.empty()) {
auto validate = m_existing_keys & ival::closed(start_key, start_key + n_keys - 1);
assert(!validate.empty());
break;
}
num_retry++;
continue;
}
if (num_retry == max_retries) { return -1; }
// add from working keys and return the start_key;
this->add_to_working(start_key, start_key + n_keys - 1);
assert(start_key + n_keys - 1 <= working_range);
return static_cast< int >(start_key);
}

int pick_random_non_working_keys(uint32_t n_keys = 1, uint32_t max_range = 0) {
std::unique_lock< mutex > lk(m_set_lock);
uint32_t working_range = max_range <= 0 ? std::numeric_limits< uint32_t >::max() : max_range;
uint32_t num_retry = 0;

auto num_intervals = static_cast< uint32_t >(m_working_keys.iterative_size());
// empty keys
if (num_intervals == 0) { return -1; }
std::uniform_int_distribution< uint32_t > s_rand_interval_generator{0, num_intervals - 1};
uint32_t start_key = std::numeric_limits< uint32_t >::max();

while (num_retry < max_retries) {
// find a random interval
uint32_t next_lower = working_range;
uint32_t previous_upper = 0;
auto it = m_working_keys.begin();
if (num_intervals != 0) {
uint32_t cur_interval_idx = s_rand_interval_generator(m_re);
std::advance(it, cur_interval_idx);
previous_upper = last(*it) + 1; // to be inclusivelast
it++;
if (it != m_working_keys.end()) { next_lower = first(*it) - 1; }
}
if ((next_lower + 1) < (n_keys + previous_upper)) { // check < or <=
num_retry++;
continue;
}

// choose randomly n keys in [previous_upper, next_lower]
std::uniform_int_distribution< uint32_t > rand_key_generator{
previous_upper, next_lower - n_keys + 1}; // n_keys or n_keys +- (1)
start_key = rand_key_generator(m_re);
break;
}
if (num_retry == max_retries) { return -1; }
// add from working keys and return the start_key;
this->add_to_working(start_key, start_key + n_keys - 1);
assert(start_key + n_keys - 1 <= working_range);
return static_cast< int >(start_key);
}

private:
void add_to_existing(uint32_t s) { add_to_existing(s, s); }

void add_to_working(uint32_t s) { add_to_working(s, s); }

void add_to_existing(uint32_t s, uint32_t e) { m_existing_keys += ival::closed(s, e); }

void add_to_working(uint32_t s, uint32_t e) { m_working_keys += ival::closed(s, e); }

void remove_from_existing(uint32_t s, uint32_t e) { m_existing_keys -= ival::closed(s, e); }

void remove_from_existing(uint32_t s) { remove_from_existing(s, s); }

void remove_from_working(uint32_t s) { remove_from_working(s, s); }

void remove_from_working(uint32_t s, uint32_t e) { m_working_keys -= ival::closed(s, e); }

bool is_working(uint32_t cur_key) { return m_working_keys.find(cur_key) != m_working_keys.end(); }

bool is_existing(uint32_t cur_key) { return m_existing_keys.find(cur_key) != m_existing_keys.end(); }

private:
set_t m_existing_keys;
set_t m_working_keys;
mutex m_set_lock;

std::random_device m_rd{};
std::default_random_engine m_re{m_rd()};
const uint32_t max_retries = 5;
};
}; // namespace homestore
Loading

0 comments on commit ba95ef7

Please sign in to comment.