diff --git a/dbms/CMakeLists.txt b/dbms/CMakeLists.txt index fe915359957..197bf281367 100644 --- a/dbms/CMakeLists.txt +++ b/dbms/CMakeLists.txt @@ -83,6 +83,10 @@ if (ENABLE_V3_PAGESTORAGE) add_headers_and_sources(dbms src/Storages/Page/V3/LogFile) endif() add_headers_and_sources(dbms src/Storages/Page/) +if (ENABLE_V3_PAGESTORAGE) + add_headers_and_sources(dbms src/Storages/Page/V3) + add_headers_and_sources(dbms src/Storages/Page/V3/spacemap) +endif() add_headers_and_sources(dbms src/TiDB) add_headers_and_sources(dbms src/Client) add_headers_only(dbms src/Flash/Coprocessor) diff --git a/dbms/src/Storages/CMakeLists.txt b/dbms/src/Storages/CMakeLists.txt index a8915dd8ec9..0d6dcd5c19d 100644 --- a/dbms/src/Storages/CMakeLists.txt +++ b/dbms/src/Storages/CMakeLists.txt @@ -7,6 +7,7 @@ if (ENABLE_TESTS) add_subdirectory (Transaction/tests EXCLUDE_FROM_ALL) add_subdirectory (Page/V2/tests EXCLUDE_FROM_ALL) if (ENABLE_V3_PAGESTORAGE) + add_subdirectory (Page/V3 EXCLUDE_FROM_ALL) add_subdirectory (Page/V3/tests EXCLUDE_FROM_ALL) endif () add_subdirectory (DeltaMerge/tests EXCLUDE_FROM_ALL) diff --git a/dbms/src/Storages/Page/V3/CMakeLists.txt b/dbms/src/Storages/Page/V3/CMakeLists.txt new file mode 100644 index 00000000000..64f39cbdfc1 --- /dev/null +++ b/dbms/src/Storages/Page/V3/CMakeLists.txt @@ -0,0 +1,16 @@ +add_headers_and_sources(page_storage_v3 ./) +add_headers_and_sources(page_storage_v3 ./spacemap) + + +list(APPEND page_storage_v3_sources + ${ClickHouse_SOURCE_DIR}/dbms/src/Server/StorageConfigParser.cpp + ${ClickHouse_SOURCE_DIR}/dbms/src/Storages/Page/PageUtil.cpp + ${ClickHouse_SOURCE_DIR}/dbms/src/Encryption/RateLimiter.cpp +) + +add_library(page_storage_v3 EXCLUDE_FROM_ALL + ${page_storage_v3_headers} ${page_storage_v3_sources} + ${io_base_headers} ${io_base_sources} +) +target_include_directories(page_storage_v3 PUBLIC ${ClickHouse_SOURCE_DIR}/contrib/tiflash-proxy/raftstore-proxy/ffi/src) +target_link_libraries(page_storage_v3 clickhouse_common_io cpptoml kv_client tipb) \ No newline at end of file diff --git a/dbms/src/Storages/Page/V3/spacemap/RBTree.cpp b/dbms/src/Storages/Page/V3/spacemap/RBTree.cpp new file mode 100644 index 00000000000..8efcb911555 --- /dev/null +++ b/dbms/src/Storages/Page/V3/spacemap/RBTree.cpp @@ -0,0 +1,530 @@ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define COLOR_BLACK 1 +#define COLOR_MASK 3 + +static inline struct rb_node * node_parent(struct rb_node * node) +{ + return (struct rb_node *)(node->parent & ~COLOR_MASK); +} + +static inline uintptr_t node_color(struct rb_node * node) +{ + return node->parent & COLOR_BLACK; +} + +static inline void node_set_red(struct rb_node * node) +{ + node->parent &= ~COLOR_BLACK; +} + +static inline void node_set_black(struct rb_node * node) +{ + node->parent |= COLOR_BLACK; +} + +static inline void node_set_parent(struct rb_node * node, struct rb_node * p) +{ + node->parent = (node->parent & COLOR_MASK) | (uintptr_t)p; +} + +static inline void node_set_color(struct rb_node * node, int color) +{ + node->parent = (node->parent & ~COLOR_BLACK) | color; +} + +static void node_rotate_left(struct rb_node * node, struct rb_root * root) +{ + struct rb_node * right = node->node_right; + struct rb_node * parent = node_parent(node); + + if ((node->node_right = right->node_left)) + { + node_set_parent(right->node_left, node); + } + + right->node_left = node; + + node_set_parent(right, parent); + + if (parent) + { + if (node == parent->node_left) + { + parent->node_left = right; + } + else + { + parent->node_right = right; + } + } + else + { + root->rb_node = right; + } + + node_set_parent(node, right); +} + +static void node_rotate_right(struct rb_node * node, struct rb_root * root) +{ + struct rb_node * left = node->node_left; + struct rb_node * parent = node_parent(node); + + if ((node->node_left = left->node_right)) + node_set_parent(left->node_right, node); + left->node_right = node; + + node_set_parent(left, parent); + + if (parent) + { + if (node == parent->node_right) + { + parent->node_right = left; + } + + else + { + parent->node_left = left; + } + } + else + { + root->rb_node = left; + } + node_set_parent(node, left); +} + +static void node_clear_color(struct rb_node * node, struct rb_node * parent, struct rb_root * root) +{ + struct rb_node * other; + + while ((!node || node_color(node)) && node != root->rb_node) + { + if (parent->node_left == node) + { + other = parent->node_right; + if (!node_color(other)) + { + node_set_black(other); + node_set_red(parent); + node_rotate_left(parent, root); + other = parent->node_right; + } + if ((!other->node_left || node_color(other->node_left)) && (!other->node_right || node_color(other->node_right))) + { + node_set_red(other); + node = parent; + parent = node_parent(node); + } + else + { + if (!other->node_right || node_color(other->node_right)) + { + node_set_black(other->node_left); + node_set_red(other); + node_rotate_right(other, root); + other = parent->node_right; + } + node_set_color(other, node_color(parent)); + node_set_black(parent); + node_set_black(other->node_right); + node_rotate_left(parent, root); + node = root->rb_node; + break; + } + } + else + { + other = parent->node_left; + if (!node_color(other)) + { + node_set_black(other); + node_set_red(parent); + node_rotate_right(parent, root); + other = parent->node_left; + } + if ((!other->node_left || node_color(other->node_left)) && (!other->node_right || node_color(other->node_right))) + { + node_set_red(other); + node = parent; + parent = node_parent(node); + } + else + { + if (!other->node_left || node_color(other->node_left)) + { + node_set_black(other->node_right); + node_set_red(other); + node_rotate_left(other, root); + other = parent->node_left; + } + node_set_color(other, node_color(parent)); + node_set_black(parent); + node_set_black(other->node_left); + node_rotate_right(parent, root); + node = root->rb_node; + break; + } + } + } + if (node) + { + node_set_black(node); + } +} + + +void rb_node_insert(struct rb_node * node, struct rb_root * root) +{ + struct rb_node *parent, *gparent; + + while ((parent = node_parent(node)) && !node_color(parent)) + { + gparent = node_parent(parent); + if (parent == gparent->node_left) + { + { + // register + struct rb_node * uncle = gparent->node_right; + if (uncle && !node_color(uncle)) + { + node_set_black(uncle); + node_set_black(parent); + node_set_red(gparent); + node = gparent; + continue; + } + } + + if (parent->node_right == node) + { + // register + struct rb_node * tmp; + node_rotate_left(parent, root); + tmp = parent; + parent = node; + node = tmp; + } + + node_set_black(parent); + node_set_red(gparent); + node_rotate_right(gparent, root); + } + else + { + { + // register + struct rb_node * uncle = gparent->node_left; + if (uncle && !node_color(uncle)) + { + node_set_black(uncle); + node_set_black(parent); + node_set_red(gparent); + node = gparent; + continue; + } + } + + if (parent->node_left == node) + { + // register + struct rb_node * tmp; + node_rotate_right(parent, root); + tmp = parent; + parent = node; + node = tmp; + } + + node_set_black(parent); + node_set_red(gparent); + node_rotate_left(gparent, root); + } + } + + node_set_black(root->rb_node); +} + +void rb_node_remove(struct rb_node * node, struct rb_root * root) +{ + struct rb_node *child, *parent; + int color; + + if (!node->node_left) + { + child = node->node_right; + } + else if (!node->node_right) + { + child = node->node_left; + } + else + { + struct rb_node *old = node, *left; + + node = node->node_right; + while ((left = node->node_left) != NULL) + { + node = left; + } + + if (node_parent(old)) + { + if (node_parent(old)->node_left == old) + { + node_parent(old)->node_left = node; + } + else + { + node_parent(old)->node_right = node; + } + } + else + { + root->rb_node = node; + } + + child = node->node_right; + parent = node_parent(node); + color = node_color(node); + + if (parent == old) + { + parent = node; + } + else + { + if (child) + { + node_set_parent(child, parent); + } + + parent->node_left = child; + node->node_right = old->node_right; + node_set_parent(old->node_right, node); + } + + node->parent = old->parent; + node->node_left = old->node_left; + node_set_parent(old->node_left, node); + + goto check_color; + } + + parent = node_parent(node); + color = node_color(node); + + if (child) + { + node_set_parent(child, parent); + } + + if (parent) + { + if (parent->node_left == node) + { + parent->node_left = child; + } + else + { + parent->node_right = child; + } + } + else + { + root->rb_node = child; + } + +check_color: + if (color == COLOR_BLACK) + { + node_clear_color(child, parent, root); + } +} + +struct rb_node * rb_tree_first(const struct rb_root * root) +{ + struct rb_node * n; + + n = root->rb_node; + if (!n) + { + return NULL; + } + + while (n->node_left) + { + n = n->node_left; + } + + return n; +} + +struct rb_node * rb_tree_last(const struct rb_root * root) +{ + struct rb_node * n; + + n = root->rb_node; + if (!n) + { + return NULL; + } + + while (n->node_right) + { + n = n->node_right; + } + + return n; +} + +struct rb_node * rb_tree_next(struct rb_node * current) +{ + struct rb_node * parent; + + // No more `next` node. + if (node_parent(current) == current) + { + return NULL; + } + + /** + * If tree looks like: + * A + * / \ + * B C + * / \ / + * D E F ... + * / \ + * G H + * + * Then the node is `B` , it should return `G` + * Because `G` will bigger than `B` but small than `E` + */ + if (current->node_right) + { + current = current->node_right; + while (current->node_left) + { + current = current->node_left; + } + + return current; + } + + /** + * In this situation, there are left-node exist. + * And all of left node is smaller than `current` node + * So the `next` node must in parent node,Then we need go up into the parent node. + * If `current` node is the right-node child of its parent, Then it still need go up for upper layer. + * + */ + while ((parent = node_parent(current)) && current == parent->node_right) + { + current = parent; + } + + return parent; +} + +struct rb_node * rb_tree_prev(struct rb_node * current) +{ + struct rb_node * parent; + + // No more `prev` node. + if (node_parent(current) == current) + { + return NULL; + } + + /** + * Same as `rb_tree_next` + * If tree looks like: + * A + * / \ + * B C + * \ / \ + * ... D E F + * / \ + * G H + * Then the node is `C` , it should return `H` + * Because `H` smaller than `C` but small than `E` + */ + if (current->node_left) + { + current = current->node_left; + while (current->node_right) + { + current = current->node_right; + } + return current; + } + + /** + * Same as `rb_tree_next` + * In this situation, there are left-node exist. + * And all of left node is bigger than `current` node + * So the `prev` node must in parent node,Then we need go up into the parent node. + * If `current` node is the left-node child of its parent, Then it still need go up for upper layer. + */ + while ((parent = node_parent(current)) && current == parent->node_left) + { + current = parent; + } + + return parent; +} + +void rb_tree_update_node(struct rb_node * old_node, struct rb_node * new_node, struct rb_root * root) +{ + struct rb_node * parent = node_parent(old_node); + + assert(parent != NULL); + + if (parent) + { + // Update the parent ptr to child + if (old_node == parent->node_left) + { + parent->node_left = new_node; + } + else if (old_node == parent->node_right) + { + parent->node_right = new_node; + } + else + { + assert(false); + } + } + else + { + /** + * No find parent, tree is empty + * Just update the root + */ + root->rb_node = new_node; + } + + // Let the `child` node point to the `new` node + if (old_node->node_left) + { + node_set_parent(old_node->node_left, new_node); + } + + if (old_node->node_right) + { + node_set_parent(old_node->node_right, new_node); + } + + *new_node = *old_node; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/dbms/src/Storages/Page/V3/spacemap/RBTree.h b/dbms/src/Storages/Page/V3/spacemap/RBTree.h new file mode 100644 index 00000000000..4d40e7dad8b --- /dev/null +++ b/dbms/src/Storages/Page/V3/spacemap/RBTree.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct rb_node +{ + /** + * using uintptr_t + * - contain node color + * - contain parent ptr + */ + uintptr_t parent; + struct rb_node * node_right; + struct rb_node * node_left; +} __attribute__((aligned(sizeof(long)))); + +struct rb_root +{ + struct rb_node * rb_node; +}; + +/** + * Insert node into red-black tree + */ +void rb_node_insert(struct rb_node *, struct rb_root *); + +/** + * remove node from red-black tree + */ +void rb_node_remove(struct rb_node *, struct rb_root *); + +/** + * Return the first node of the tree. + * It is a O(n) method + * call the `rb_next` as a iterator + */ +struct rb_node * rb_tree_first(const struct rb_root *); + +/** + * Return the last node of the tree. + * It is a O(n) method + * call the `rb_prev` as a iterator + */ +struct rb_node * rb_tree_last(const struct rb_root *); + +/** + * Return the next node of the tree. + */ +struct rb_node * rb_tree_next(struct rb_node *); + +/** + * Return the prev node of the tree. + */ +struct rb_node * rb_tree_prev(struct rb_node *); + +/** + * Update node into a new node + * - Just replace the position, tree won't rotate + * - Note its own pointer + */ +void rb_tree_update_node(struct rb_node * old_node, struct rb_node * new_node, struct rb_root * root); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMap.cpp b/dbms/src/Storages/Page/V3/spacemap/SpaceMap.cpp new file mode 100644 index 00000000000..ab3d0482298 --- /dev/null +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMap.cpp @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DB +{ +namespace ErrorCodes +{ +extern const int LOGICAL_ERROR; +} // namespace ErrorCodes + +namespace PS::V3 +{ +SpaceMapPtr SpaceMap::createSpaceMap(SpaceMapType type, UInt64 start, UInt64 end) +{ + SpaceMapPtr smap; + switch (type) + { + case SMAP64_RBTREE: + smap = RBTreeSpaceMap::create(start, end); + break; + case SMAP64_STD_MAP: + smap = STDMapSpaceMap::create(start, end); + break; + default: + throw Exception("Invalid type to create spaceMap", ErrorCodes::LOGICAL_ERROR); + } + + if (!smap) + { + throw Exception("Failed create SpaceMap [type=" + typeToString(type) + "]", ErrorCodes::LOGICAL_ERROR); + } + + return smap; +} + +bool SpaceMap::checkSpace(UInt64 block, size_t size) +{ + return (block < start) || (block > end) || (block + size - 1 > end); +} + +void SpaceMap::logStats() +{ + smapStats(); +} + +bool SpaceMap::markFree(UInt64 offset, size_t length) +{ + if (checkSpace(offset, length)) + { + throw Exception("Unmark space out of the limit space.[type=" + typeToString(getType()) + + "] [block=" + DB::toString(offset) + "], [size=" + DB::toString(length) + "]", + ErrorCodes::LOGICAL_ERROR); + } + + return markFreeImpl(offset, length); +} + +bool SpaceMap::markUsed(UInt64 offset, size_t length) +{ + if (checkSpace(offset, length)) + { + throw Exception("Mark space out of the limit space.[type=" + typeToString(getType()) + + "] [block=" + DB::toString(offset) + "], [size=" + DB::toString(length) + "]", + ErrorCodes::LOGICAL_ERROR); + } + + return markUsedImpl(offset, length); +} + +bool SpaceMap::isMarkUsed(UInt64 offset, size_t length) +{ + if (checkSpace(offset, length)) + { + throw Exception("Test space out of the limit space.[type=" + typeToString(getType()) + + "] [block=" + DB::toString(offset) + "], [size=" + DB::toString(length) + "]", + ErrorCodes::LOGICAL_ERROR); + } + + return !isMarkUnused(offset, length); +} + +SpaceMap::SpaceMap(UInt64 start_, UInt64 end_, SpaceMapType type_) + : type(type_) + , start(start_) + , end(end_) + , log(&Poco::Logger::get("SpaceMap")) +{ +} + +} // namespace PS::V3 +} // namespace DB diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMap.h b/dbms/src/Storages/Page/V3/spacemap/SpaceMap.h new file mode 100644 index 00000000000..b4bea9effc8 --- /dev/null +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMap.h @@ -0,0 +1,147 @@ +#pragma once +#include +#include + +namespace DB::PS::V3 +{ +class SpaceMap; +using SpaceMapPtr = std::shared_ptr; +/** + * SpaceMap design doc: + * https://docs.google.com/document/d/1l1GoIV6Rp0GEwuYtToJMKYACmZv6jf4kp1n8JdQidS8/edit#heading=h.pff0nn7vsa6w + * + * SpaceMap have red-black tree/ map implemention. + * Each node on the tree records the information of free data blocks, + * + * The node is composed of `offset` : `size`. Each node sorted according to offset. + * - offset: Record the starting address of the free data segment in the file. + * - size: The length of the space data segment is recorded. + */ +class SpaceMap +{ +public: + enum SpaceMapType + { + SMAP64_INVALID = 0, + SMAP64_RBTREE = 1, + SMAP64_STD_MAP = 2 + }; + + /** + * Create a SpaceMap that manages space address [start, end). + * - type : + * - SMAP64_RBTREE : red-black tree implementation + * - SMAP64_STD_MAP: std::map implementation + * - start : begin of the space + * - end : end if the space + */ + static SpaceMapPtr createSpaceMap(SpaceMapType type, UInt64 start, UInt64 end); + + /** + * Mark a span [offset,offset + length) to be free. + * After this span is marked free, this span may be selected by `searchInsertOffset`. + * + * ret value: + * true: the span is marked as free + * false: the span can not mark as free + */ + bool markFree(UInt64 offset, size_t length); + + /** + * Mark a span [offset,offset + length) to being used. + * After this span is marked used, this span can not be selected by `searchInsertOffset`. + * + * ret value: + * false: This span is marked as used successfully. + * true: This span can not be marked as used. It or some sub spans have been marked as used before. + */ + bool markUsed(UInt64 offset, size_t length); + + /** + * Check a span [offset, offset + length) has been used or not. + * + * ret value: + * true: This span is used, or some sub span is used + * false: All of this span is freed. + */ + bool isMarkUsed(UInt64 offset, size_t length); + + /** + * Search a span that can fit in `size`. + * If such span is found, it will also return a hint of the max capacity available + * in this SpaceMap. + * + * return value is : + * insert_offset : start offset for the inserted space + * max_cap : A hint of the largest available space this SpaceMap can hold. + */ + virtual std::pair searchInsertOffset(size_t size) = 0; + + /** + * Sanity check for correctness + */ + using CheckerFunc = std::function; + virtual bool check(CheckerFunc /*checker*/, size_t /*size*/) + { + return true; + } + + /** + * Log the status of space map + */ + void logStats(); + + SpaceMapType getType() const + { + return type; + } + + static String typeToString(SpaceMapType type) + { + switch (type) + { + case SMAP64_RBTREE: + return "RB-Tree"; + case SMAP64_STD_MAP: + return "STD Map"; + default: + return "Invalid"; + } + } + +protected: + SpaceMap(UInt64 start_, UInt64 end_, SpaceMapType type_); + + virtual ~SpaceMap() = default; + + /* Print space maps status */ + virtual void smapStats() = 0; + + // Return true if space [offset, offset+size) are all free + virtual bool isMarkUnused(UInt64 offset, size_t size) = 0; + + /* Space map mark used/free operators */ + virtual bool markUsedImpl(UInt64 offset, size_t size) = 0; + + virtual bool markFreeImpl(UInt64 offset, size_t size) = 0; + +private: + /* Check the range */ + bool checkSpace(UInt64 offset, size_t num); + +#ifndef DBMS_PUBLIC_GTEST +protected: +#else +public: +#endif + SpaceMapType type = SpaceMapType::SMAP64_INVALID; + + /* The offset range managed by this SpaceMap. The range is [left, right). */ + UInt64 start; + UInt64 end; + + Poco::Logger * log; +}; + + +} // namespace DB::PS::V3 diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.cpp b/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.cpp new file mode 100644 index 00000000000..f86a2f1f245 --- /dev/null +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.cpp @@ -0,0 +1,651 @@ +#include + + +namespace DB::PS::V3 +{ +static bool rb_insert_entry(UInt64 start, UInt64 count, struct rb_private * private_data, Poco::Logger * log); +static bool rb_remove_entry(UInt64 start, UInt64 count, struct rb_private * private_data, Poco::Logger * log); + +static inline void rb_link_node(struct rb_node * node, + struct rb_node * parent, + struct rb_node ** rb_link) +{ + node->parent = (uintptr_t)parent; + node->node_left = nullptr; + node->node_right = nullptr; + + *rb_link = node; +} + +#define ENABLE_DEBUG_IN_RB_TREE 0 + +#if !defined(NDEBUG) && !defined(DBMS_PUBLIC_GTEST) && ENABLE_DEBUG_IN_RB_TREE +// Its local debug info, So don't us LOG +static void rb_tree_debug(struct rb_root * root, const char * method_call) +{ + struct rb_node * node = nullptr; + struct smap_rb_entry * entry; + + node = rb_tree_first(root); + printf("call in %s", method_call); + for (node = rb_tree_first(root); node != nullptr; node = rb_tree_next(node)) + { + entry = node_to_entry(node); + printf(" Space - (%llu -> %llu)\n", entry->start, entry->start + entry->count); + } +} + +#else +#define rb_tree_debug(root, function) \ + do \ + { \ + } while (0) +#endif + + +static void rb_get_new_entry(struct smap_rb_entry ** entry, UInt64 start, UInt64 count) +{ + struct smap_rb_entry * new_entry; + + new_entry = (struct smap_rb_entry *)calloc(1, sizeof(struct smap_rb_entry)); + if (new_entry == nullptr) + { + return; + } + + new_entry->start = start; + new_entry->count = count; + *entry = new_entry; +} + +inline static void rb_free_entry(struct rb_private * private_data, struct smap_rb_entry * entry) +{ + /** + * reset all index + */ + if (private_data->write_index == entry) + { + private_data->write_index = nullptr; + } + + if (private_data->read_index == entry) + { + private_data->read_index = nullptr; + } + + if (private_data->read_index_next == entry) + { + private_data->read_index_next = nullptr; + } + + free(entry); +} + + +static bool rb_insert_entry(UInt64 start, UInt64 count, struct rb_private * private_data, Poco::Logger * log) +{ + struct rb_root * root = &private_data->root; + struct rb_node *parent = nullptr, **n = &root->rb_node; + struct rb_node *new_node, *node, *next; + struct smap_rb_entry * new_entry; + struct smap_rb_entry * entry; + bool retval = true; + + if (count == 0) + { + return false; + } + + private_data->read_index_next = nullptr; + entry = private_data->write_index; + if (entry) + { + if (start >= entry->start && start <= (entry->start + entry->count)) + { + goto got_entry; + } + } + + while (*n) + { + parent = *n; + entry = node_to_entry(parent); + + if (start < entry->start) + { + n = &(*n)->node_left; + } + else if (start > (entry->start + entry->count)) + { + n = &(*n)->node_right; + } + else + { + got_entry: + if ((start + count) <= (entry->start + entry->count)) + { + return false; + } + + if ((entry->start + entry->count) == start) + { + retval = true; + if (parent) + { + auto * _node = rb_tree_next(parent); + if (_node) + { + auto * _entry = node_to_entry(_node); + if (start + count > _entry->start) + { + LOG_WARNING(log, "Marked space free failed. [offset=" << start << ", size=" << count << "], next node is [offset=" << _entry->start << ",size=" << _entry->count << "]"); + return false; + } + } + } + } + else + { + return false; + } + + count += (start - entry->start); + start = entry->start; + new_entry = entry; + new_node = &entry->node; + + goto no_need_insert; + } + } + + rb_get_new_entry(&new_entry, start, count); + + new_node = &new_entry->node; + rb_link_node(new_node, parent, n); + rb_node_insert(new_node, root); + private_data->write_index = new_entry; + + /** + * We need check current node is legal before we merge it. + * If prev/next node exist. Check if they have overlap with the current node. + * Also, We can’t check while doing the merge. + * Because it will cause the original state not to be restored + */ + node = rb_tree_prev(new_node); + if (node) + { + entry = node_to_entry(node); + if (entry->start + entry->count > new_entry->start) + { + LOG_WARNING(log, "Marked space free failed. [offset=" << new_entry->start << ", size=" << new_entry->count << "], prev node is [offset=" << entry->start << ",size=" << entry->count << "]"); + rb_node_remove(new_node, root); + rb_free_entry(private_data, new_entry); + return false; + } + } + + node = rb_tree_next(new_node); + if (node) + { + entry = node_to_entry(node); + if (new_entry->start + new_entry->count > entry->start) + { + LOG_WARNING(log, "Marked space free failed. [offset=" << new_entry->start << ", size=" << new_entry->count << "], next node is [offset=" << entry->start << ",size=" << entry->count << "]"); + rb_node_remove(new_node, root); + rb_free_entry(private_data, new_entry); + return false; + } + } + + + node = rb_tree_prev(new_node); + if (node) + { + entry = node_to_entry(node); + if ((entry->start + entry->count) == start) + { + start = entry->start; + count += entry->count; + rb_node_remove(node, root); + rb_free_entry(private_data, entry); + } + } + +no_need_insert: + // merge entry to the right + for (node = rb_tree_next(new_node); node != nullptr; node = next) + { + next = rb_tree_next(node); + entry = node_to_entry(node); + + if ((entry->start + entry->count) <= start) + { + continue; + } + + // not match + if ((start + count) < entry->start) + break; + + if ((start + count) >= (entry->start + entry->count)) + { + rb_node_remove(node, root); + rb_free_entry(private_data, entry); + continue; + } + else + { + // merge entry + count += ((entry->start + entry->count) - (start + count)); + rb_node_remove(node, root); + rb_free_entry(private_data, entry); + break; + } + } + + new_entry->start = start; + new_entry->count = count; + + return retval; +} + + +static bool rb_remove_entry(UInt64 start, UInt64 count, struct rb_private * private_data, Poco::Logger * log) +{ + struct rb_root * root = &private_data->root; + struct rb_node *parent = nullptr, **n = &root->rb_node; + struct rb_node * node; + struct smap_rb_entry * entry; + UInt64 new_start, new_count; + bool marked = false; + + // Root node have not been init + if (private_data->root.rb_node == nullptr) + { + assert(false); + } + + while (*n) + { + parent = *n; + entry = node_to_entry(parent); + if (start < entry->start) + { + n = &(*n)->node_left; + continue; + } + else if (start >= (entry->start + entry->count)) + { + n = &(*n)->node_right; + continue; + } + + /** + * We got node. + * entry->start < start < (entry->start + entry->count) + */ + + if ((start + count) > (entry->start + entry->count)) + { + LOG_WARNING(log, "Marked space used failed. [offset=" << start << ", size=" << count << "] is bigger than space [offset=" << entry->start << ",size=" << entry->count << "]"); + return false; + } + + if (start < entry->start) + { + LOG_WARNING(log, "Marked space used failed. [offset=" << start << ", size=" << count << "] is less than space [offset=" << entry->start << ",size=" << entry->count << "]"); + return false; + } + + // In the Mid + if ((start > entry->start) && (start + count) < (entry->start + entry->count)) + { + // Split entry + new_start = start + count; + new_count = (entry->start + entry->count) - new_start; + + entry->count = start - entry->start; + + rb_insert_entry(new_start, new_count, private_data, log); + return true; + } + + // Match right + if ((start + count) == (entry->start + entry->count)) + { + entry->count = start - entry->start; + marked = true; + } + + // Left have no count remian. + if (0 == entry->count) + { + parent = rb_tree_next(&entry->node); + rb_node_remove(&entry->node, root); + rb_free_entry(private_data, entry); + break; + } + + if (start == entry->start) + { + entry->start += count; + entry->count -= count; + return true; + } + } + + // Checking the right node + for (; parent != nullptr; parent = node) + { + node = rb_tree_next(parent); + entry = node_to_entry(parent); + if ((entry->start + entry->count) <= start) + continue; + + if ((start + count) < entry->start) + break; + + if ((start + count) > entry->start) + return false; + + // Merge the nearby node + if ((start + count) >= (entry->start + entry->count)) + { + rb_node_remove(parent, root); + rb_free_entry(private_data, entry); + marked = true; + continue; + } + else + { + if (((start + count) - entry->start) == 0 + && (start + count == entry->start)) + { + break; + } + entry->count -= ((start + count) - entry->start); + entry->start = start + count; + marked = true; + break; + } + } + + return marked; +} + +std::shared_ptr RBTreeSpaceMap::create(UInt64 start, UInt64 end) +{ + auto ptr = std::shared_ptr(new RBTreeSpaceMap(start, end)); + + ptr->rb_tree = static_cast(calloc(1, sizeof(struct rb_private))); + if (ptr->rb_tree == nullptr) + { + return nullptr; + } + + ptr->rb_tree->root = { + nullptr, + }; + ptr->rb_tree->read_index = nullptr; + ptr->rb_tree->read_index_next = nullptr; + ptr->rb_tree->write_index = nullptr; + + if (!rb_insert_entry(start, end, ptr->rb_tree, ptr->log)) + { + LOG_ERROR(ptr->log, "Erorr happend, when mark all space free. [start=" << start << "] , [end=" << end << "]"); + free(ptr->rb_tree); + return nullptr; + } + return ptr; +} + +static void rb_free_tree(struct rb_root * root) +{ + struct smap_rb_entry * entry; + struct rb_node *node, *next; + + for (node = rb_tree_first(root); node; node = next) + { + next = rb_tree_next(node); + entry = node_to_entry(node); + rb_node_remove(node, root); + free(entry); + } +} + +void RBTreeSpaceMap::freeSmap() +{ + if (rb_tree) + { + rb_free_tree(&rb_tree->root); + free(rb_tree); + } +} + +void RBTreeSpaceMap::smapStats() +{ + struct rb_node * node = nullptr; + struct smap_rb_entry * entry; + UInt64 count = 0; + UInt64 max_size = 0; + UInt64 min_size = ULONG_MAX; + + if (rb_tree->root.rb_node == nullptr) + { + LOG_ERROR(log, "Tree have not been inited."); + return; + } + + LOG_DEBUG(log, "RB-Tree entries status: "); + for (node = rb_tree_first(&rb_tree->root); node != nullptr; node = rb_tree_next(node)) + { + entry = node_to_entry(node); + LOG_DEBUG(log, " Space: " << count << " start:" << entry->start << " size: " << entry->count); + count++; + if (entry->count > max_size) + { + max_size = entry->count; + } + + if (entry->count < min_size) + { + min_size = entry->count; + } + } +} + +bool RBTreeSpaceMap::isMarkUnused(UInt64 _start, + size_t len) +{ + struct rb_node *parent = nullptr, **n; + struct rb_node *node, *next; + struct smap_rb_entry * entry; + bool retval = false; + + n = &rb_tree->root.rb_node; + _start -= start; + + if (len == 0 || rb_tree->root.rb_node == nullptr) + { + assert(0); + } + + while (*n) + { + parent = *n; + entry = node_to_entry(parent); + if (_start < entry->start) + { + n = &(*n)->node_left; + } + else if (_start >= (entry->start + entry->count)) + { + n = &(*n)->node_right; + } + else + { + // the tree -> entry is not clear + // so just return + return true; + } + } + + node = parent; + while (node) + { + next = rb_tree_next(node); + entry = node_to_entry(node); + node = next; + + if ((entry->start + entry->count) <= _start) + continue; + + /* No more merging */ + if ((_start + len) <= entry->start) + break; + + retval = true; + break; + } + return retval; +} + +std::pair RBTreeSpaceMap::searchInsertOffset(size_t size) +{ + UInt64 offset = UINT64_MAX; + UInt64 max_cap = 0; + struct rb_node * node = nullptr; + struct smap_rb_entry * entry; + + UInt64 _biggest_cap = 0; + UInt64 _biggest_range = 0; + for (node = rb_tree_first(&rb_tree->root); node != nullptr; node = rb_tree_next(node)) + { + entry = node_to_entry(node); + if (entry->count >= size) + { + break; + } + else + { + if (entry->count > _biggest_cap) + { + _biggest_cap = entry->count; + _biggest_range = entry->start; + } + } + } + + // No enough space for insert + if (!node) + { + LOG_ERROR(log, "Not sure why can't found any place to insert.[size=" << size << "] [old biggest_range=" << biggest_range << "] [old biggest_cap=" << biggest_cap << "] [new biggest_range=" << _biggest_range << "] [new biggest_cap=" << _biggest_cap << "]"); + biggest_range = _biggest_range; + biggest_cap = _biggest_cap; + + return std::make_pair(offset, biggest_cap); + } + + // Update return start + offset = entry->start; + + if (entry->count == size) + { + // It is champion, need update + if (entry->start == biggest_range) + { + struct rb_node * old_node = node; + node = rb_tree_next(node); + rb_node_remove(old_node, &rb_tree->root); + rb_free_entry(rb_tree, entry); + // still need update max_cap + } + else // It not champion, just return + { + rb_node_remove(node, &rb_tree->root); + rb_free_entry(rb_tree, entry); + max_cap = biggest_cap; + return std::make_pair(offset, max_cap); + } + } + else // must be entry->count > size + { + // Resize this node, no need update + entry->start += size; + entry->count -= size; + + // It is champion, need update + if (entry->start - size == biggest_range) + { + if (entry->count > _biggest_cap) + { + _biggest_cap = entry->count; + _biggest_range = entry->start; + } + node = rb_tree_next(node); + // still need update max_cap + } + else // It not champion, just return + { + max_cap = biggest_cap; + return std::make_pair(offset, max_cap); + } + } + + for (; node != nullptr; node = rb_tree_next(node)) + { + entry = node_to_entry(node); + if (entry->count > _biggest_cap) + { + _biggest_cap = entry->count; + _biggest_range = entry->start; + } + } + biggest_range = _biggest_range; + biggest_cap = _biggest_cap; + max_cap = biggest_cap; + return std::make_pair(offset, max_cap); +} + +bool RBTreeSpaceMap::markUsedImpl(UInt64 block, size_t size) +{ + bool rc; + + block -= start; + + rc = rb_remove_entry(block, size, rb_tree, log); + rb_tree_debug(&rb_tree->root, __func__); + return rc; +} + +bool RBTreeSpaceMap::markFreeImpl(UInt64 block, size_t size) +{ + bool rc; + + block -= start; + + rc = rb_insert_entry(block, size, rb_tree, log); + rb_tree_debug(&rb_tree->root, __func__); + return rc; +} + +bool RBTreeSpaceMap::check(std::function checker, size_t size) +{ + struct smap_rb_entry * ext; + + size_t idx = 0; + for (struct rb_node * node = rb_tree_first(&rb_tree->root); node != nullptr; node = rb_tree_next(node)) + { + ext = node_to_entry(node); + if (!checker(idx, ext->start, ext->start + ext->count)) + { + return false; + } + idx++; + } + + return idx == size; +} + + +} // namespace DB::PS::V3 diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.h b/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.h new file mode 100644 index 00000000000..146a3208db5 --- /dev/null +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.h @@ -0,0 +1,80 @@ +#pragma once +#include +#include +#include + +#include + +namespace DB +{ +namespace ErrorCodes +{ +extern const int NOT_IMPLEMENTED; +} // namespace ErrorCodes + +namespace PS::V3 +{ +struct smap_rb_entry +{ + struct rb_node node; + UInt64 start; + UInt64 count; +}; + +struct rb_private +{ + struct rb_root root; + // Cache the index for write + struct smap_rb_entry * write_index; + // Cache the index for read + struct smap_rb_entry * read_index; + struct smap_rb_entry * read_index_next; +}; + +// convert rb_node to smap_rb_entry +inline static struct smap_rb_entry * node_to_entry(struct rb_node * node) +{ + return reinterpret_cast(node); +} + +class RBTreeSpaceMap + : public SpaceMap +{ +public: + ~RBTreeSpaceMap() override + { + freeSmap(); + } + + bool check(std::function checker, size_t size) override; + + static std::shared_ptr create(UInt64, UInt64 end); + + std::pair searchInsertOffset(size_t size) override; + +protected: + RBTreeSpaceMap(UInt64 start, UInt64 end) + : SpaceMap(start, end, SMAP64_RBTREE) + { + } + + void freeSmap(); + + void smapStats() override; + + bool isMarkUnused(UInt64 block, size_t num) override; + + bool markUsedImpl(UInt64 block, size_t num) override; + + bool markFreeImpl(UInt64 block, size_t num) override; + +private: + struct rb_private * rb_tree; + UInt64 biggest_range = 0; + UInt64 biggest_cap = 0; +}; + +using RBTreeSpaceMapPtr = std::shared_ptr; + +} // namespace PS::V3 +} // namespace DB diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMapSTDMap.h b/dbms/src/Storages/Page/V3/spacemap/SpaceMapSTDMap.h new file mode 100644 index 00000000000..d74b61c1ec8 --- /dev/null +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMapSTDMap.h @@ -0,0 +1,328 @@ +#pragma once +#include +#include +#include + +#include +#include + +namespace DB +{ +namespace ErrorCodes +{ +extern const int NOT_IMPLEMENTED; +} // namespace ErrorCodes + +namespace PS::V3 +{ +namespace details +{ +// Return an iterator to the last element whose key is less than or equal to `key`. +// If no such element is found, the past-the-end iterator is returned. +template +typename C::const_iterator +findLessEQ(const C & c, const typename C::key_type & key) +{ + auto iter = c.upper_bound(key); // first element > `key` + // Nothing greater than key + if (iter == c.cbegin()) + return c.cend(); + // its prev must be less than or equal to `key` + return --iter; +} + +} // namespace details +class STDMapSpaceMap + : public SpaceMap + , public ext::SharedPtrHelper +{ +public: + ~STDMapSpaceMap() override = default; + + bool check(std::function checker, size_t size) override + { + size_t idx = 0; + for (const auto [offset, length] : free_map) + { + if (!checker(idx, offset, offset + length)) + return false; + idx++; + } + + return idx == size; + } + +protected: + STDMapSpaceMap(UInt64 start, UInt64 end) + : SpaceMap(start, end, SMAP64_STD_MAP) + { + free_map.insert({start, end}); + } + + void smapStats() override + { + UInt64 count = 0; + + LOG_DEBUG(log, "STD-Map entries status: "); + for (auto it = free_map.begin(); it != free_map.end(); it++) + { + LOG_DEBUG(log, " Space: " << count << " start:" << it->first << " size : " << it->second); + count++; + } + } + + bool isMarkUnused(UInt64 offset, size_t length) override + { + auto it = details::findLessEQ(free_map, offset); // first free block <= `offset` + if (it == free_map.end()) + { + // No free blocks <= `offset` + return false; + } + + return (it->first <= offset && (it->first + it->second >= offset + length)); + } + + bool markUsedImpl(UInt64 offset, size_t length) override + { + auto it = details::findLessEQ(free_map, offset); // first free block <= `offset` + if (it == free_map.end()) + { + return false; + } + + // already been marked used + if (it->first + it->second < offset) + { + return false; + } + + if (length > it->second || it->first + it->second < offset + length) + { + LOG_WARNING(log, "Marked space used failed. [offset=" << offset << ", size=" << length << "] is bigger than space [offset=" << it->first << ",size=" << it->second << "]"); + return false; + } + + // match + if (it->first == offset) + { + if (length == it->second) + { + free_map.erase(it); + } + else + { + // Shrink the free block from left + auto shrink_offset = it->first + length; + auto shrink_size = it->second - length; + free_map.erase(it); + free_map[shrink_offset] = shrink_size; + } + } + else if (it->first + it->second == offset + length) + { + // Shrink the free block from right + assert(it->second != length); // should not run into here + free_map[it->first] = it->second - length; + } + else + { + // In the mid, and not match the left or right. + // Split to two space + free_map.insert({offset + length, it->first + it->second - offset - length}); + free_map[it->first] = offset - it->first; + } + + return true; + } + + std::pair searchInsertOffset(size_t size) override + { + UInt64 offset = UINT64_MAX; + UInt64 max_cap = 0; + // The biggest free block capacity and its start offset + UInt64 scan_biggest_cap = 0; + UInt64 scan_biggest_offset = 0; + + auto it = free_map.begin(); + for (; it != free_map.end(); it++) + { + if (it->second >= size) + { + break; + } + // Keep track of the biggest free block we scanned before `it` + if (it->second > scan_biggest_cap) + { + scan_biggest_cap = it->second; + scan_biggest_offset = it->first; + } + } + + // No enough space for insert + if (it == free_map.end()) + { + LOG_ERROR(log, "Not sure why can't found any place to insert. [size=" << size << "] [old biggest_offset=" << hint_biggest_offset << "] [old biggest_cap=" << hint_biggest_cap << "] [new biggest_offset=" << scan_biggest_offset << "] [new biggest_cap=" << scan_biggest_cap << "]"); + hint_biggest_offset = scan_biggest_offset; + hint_biggest_cap = scan_biggest_cap; + + return std::make_pair(offset, hint_biggest_cap); + } + + // Update return start + offset = it->first; + + if (it->second == size) + { + // It is not champion, just return + if (it->first != hint_biggest_offset) + { + free_map.erase(it); + max_cap = hint_biggest_cap; + return std::make_pair(offset, max_cap); + } + + // It is champion, need to update `scan_biggest_cap`, `scan_biggest_offset` + // and scan other free blocks to update `biggest_offset` and `biggest_cap` + it = free_map.erase(it); + } + else + { + // Shrink the free block by `size` + auto k = it->first + size; + auto v = it->second - size; + + it = free_map.erase(it); + it = free_map.insert(/*hint=*/it, {k, v}); // Use the `it` after erased as a hint, should be good for performance + + // It is not champion, just return + if (k - size != hint_biggest_offset) + { + max_cap = hint_biggest_cap; + return std::make_pair(offset, max_cap); + } + + // It is champion, need to update `scan_biggest_cap`, `scan_biggest_offset` + // and scan other free blocks to update `biggest_offset` and `biggest_cap` + if (v > scan_biggest_cap) + { + scan_biggest_cap = v; + scan_biggest_offset = k; + } + } + + for (; it != free_map.end(); it++) + { + if (it->second > scan_biggest_cap) + { + scan_biggest_cap = it->second; + scan_biggest_offset = it->first; + } + } + hint_biggest_offset = scan_biggest_offset; + hint_biggest_cap = scan_biggest_cap; + + return std::make_pair(offset, hint_biggest_cap); + } + + bool markFreeImpl(UInt64 offset, size_t length) override + { + auto it = free_map.find(offset); + + /** + * already unmarked. + * The `offset` won't be mid of free space. + * Because we alloc space from left to right. + */ + if (it != free_map.end()) + { + return true; + } + + bool meanless = false; + std::tie(it, meanless) = free_map.insert({offset, length}); + + auto it_prev = it; + auto it_next = it; + + /** + * We need check current node is legal before we merge it. + * If prev/next node exist. Check if they have overlap with the current node. + * Also, We can’t check while doing the merge. + * Because it will cause the original state not to be restored + */ + if (it != free_map.begin()) + { + it_prev--; + if (it_prev->first + it_prev->second > it->first) + { + LOG_WARNING(log, "Marked space free failed. [offset=" << it->first << ", size=" << it->second << "], prev node is [offset=" << it_prev->first << ",size=" << it_prev->second << "]"); + free_map.erase(it); + return false; + } + } + + it_next++; + if (it_next != free_map.end()) + { + if (it->first + it->second > it_next->first) + { + LOG_WARNING(log, "Marked space free failed. [offset=" << it->first << ", size=" << it->second << "], next node is [offset=" << it_next->first << ",size=" << it_next->second << "]"); + free_map.erase(it); + return false; + } + } + + /** + * Now, we can do merge. + * Restore the prev and next to the origin one. + * Also, we need check begin/end again. + * Because there not cache result. + */ + it_prev = it; + + // Check prev + if (it != free_map.begin()) + { + it_prev--; + // Prev space can merge + if (it_prev->first + it_prev->second == it->first) + { + free_map[it_prev->first] = it->first + it->second - it_prev->first; + free_map.erase(it); + it = it_prev; + } + + // prev can't merge + } + + // Check next + it_next = it; + it_next++; + if (it_next == free_map.end()) + { + return true; + } + + if (it->first + it->second == it_next->first) + { + free_map[it->first] = it_next->first + it_next->second - it->first; + free_map.erase(it_next); + } + // next can't merge + return true; + } + +private: + // Save the of free blocks + std::map free_map; + // Keep a hint track of the biggest free block. Save its biggest capacity and start offset. + // The hint could be invalid after `markSmapUsed` while restoring or `markSmapFree`. + UInt64 hint_biggest_offset = 0; + UInt64 hint_biggest_cap = 0; +}; + +using STDMapSpaceMapPtr = std::shared_ptr; + +} // namespace PS::V3 +} // namespace DB diff --git a/dbms/src/Storages/Page/V3/tests/CMakeLists.txt b/dbms/src/Storages/Page/V3/tests/CMakeLists.txt index e69de29bb2d..fc4e7747c8c 100644 --- a/dbms/src/Storages/Page/V3/tests/CMakeLists.txt +++ b/dbms/src/Storages/Page/V3/tests/CMakeLists.txt @@ -0,0 +1,15 @@ + +# glob all unit tests of PageStorage into gtests_page_storage +macro(grep_gtest_sources BASE_DIR DST_VAR) + # Cold match files that are not in tests/ directories + file(GLOB_RECURSE "${DST_VAR}" RELATIVE "${BASE_DIR}" "gtest*.cpp") +endmacro() + +# attach all dm gtest sources +grep_gtest_sources(${ClickHouse_SOURCE_DIR}/dbms/src/Storages/Page/V3/tests ps_v3_gtest_sources) + +add_executable(gtests_page_storage_v3 ${ps_v3_gtest_sources} ${ClickHouse_SOURCE_DIR}/dbms/src/TestUtils/gtests_dbms_main.cpp) +target_link_libraries(gtests_page_storage_v3 page_storage_v3 gtest_main) +target_compile_options(gtests_page_storage_v3 PRIVATE -Wno-unknown-pragmas) +target_compile_definitions(gtests_page_storage_v3 PRIVATE DBMS_PUBLIC_GTEST) +add_check(gtests_page_storage_v3) \ No newline at end of file diff --git a/dbms/src/Storages/Page/V3/tests/gtest_free_map.cpp b/dbms/src/Storages/Page/V3/tests/gtest_free_map.cpp new file mode 100644 index 00000000000..8123722fda4 --- /dev/null +++ b/dbms/src/Storages/Page/V3/tests/gtest_free_map.cpp @@ -0,0 +1,373 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + + +namespace DB::PS::V3::tests +{ +::testing::AssertionResult MapIterCompare( + const char * lhs_expr, + const char * rhs_expr, + const std::map::const_iterator lhs, + const std::pair rhs) +{ + if (lhs->first == rhs.first && lhs->second == rhs.second) + return ::testing::AssertionSuccess(); + return ::testing::internal::EqFailure( + lhs_expr, + rhs_expr, + fmt::format("{{{},{}}}", lhs->first, lhs->second), + fmt::format("{{{}, {}}}", rhs.first, rhs.second), + false); +} + +#define ASSERT_ITER_EQ(iter, val) ASSERT_PRED_FORMAT2(MapIterCompare, iter, val) + +TEST(STDMapUtil, FindLessEqual) +{ + std::map m0{}; + ASSERT_EQ(details::findLessEQ(m0, 1), m0.end()); + + std::map m1{{1, 1}, {2, 2}, {3, 3}, {6, 6}}; + ASSERT_EQ(details::findLessEQ(m1, 0), m1.end()); + ASSERT_ITER_EQ(details::findLessEQ(m1, 1), std::make_pair(1, 1)); + ASSERT_ITER_EQ(details::findLessEQ(m1, 2), std::make_pair(2, 2)); + ASSERT_ITER_EQ(details::findLessEQ(m1, 3), std::make_pair(3, 3)); + for (int x = 4; x < 20; ++x) + { + if (x < 6) + ASSERT_ITER_EQ(details::findLessEQ(m1, x), std::make_pair(3, 3)); + else + ASSERT_ITER_EQ(details::findLessEQ(m1, x), std::make_pair(6, 6)); + } +} + +struct Range +{ + size_t start; + size_t end; +}; + +class SpaceMapTest + : public testing::TestWithParam +{ +public: + SpaceMapTest() + : test_type(GetParam()) + {} + SpaceMap::SpaceMapType test_type; + +protected: + static SpaceMap::CheckerFunc + genChecker(const Range * ranges, size_t range_size) + { + return [ranges, range_size](size_t idx, UInt64 start, UInt64 end) -> bool { + return idx < range_size && ranges[idx].start == start && ranges[idx].end == end; + }; + }; +}; + +TEST_P(SpaceMapTest, InitAndDestory) +{ + SpaceMapPtr smap = SpaceMap::createSpaceMap(test_type, 0, 100); + + smap->logStats(); +} + + +TEST_P(SpaceMapTest, MarkUnmark) +{ + auto smap = SpaceMap::createSpaceMap(test_type, 0, 100); + + Range ranges[] = {{.start = 0, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges, 1), 1)); + + ASSERT_TRUE(smap->markUsed(50, 1)); + ASSERT_FALSE(smap->markUsed(50, 1)); + + ASSERT_TRUE(smap->isMarkUsed(50, 1)); + ASSERT_FALSE(smap->isMarkUsed(51, 1)); + + Range ranges1[] = {{.start = 0, + .end = 50}, + {.start = 51, + .end = 100}}; + + ASSERT_TRUE(smap->check(genChecker(ranges1, 2), 2)); + + ASSERT_TRUE(smap->markFree(50, 1)); + ASSERT_TRUE(smap->check(genChecker(ranges, 1), 1)); + ASSERT_FALSE(smap->isMarkUsed(50, 1)); +} + +TEST_P(SpaceMapTest, MarkmarkFree) +{ + auto smap = SpaceMap::createSpaceMap(test_type, 0, 100); + + Range ranges[] = {{.start = 0, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges, 1), 1)); + ASSERT_FALSE(smap->isMarkUsed(1, 99)); + + // call `isMarkUsed` with invalid length + ASSERT_THROW({ smap->isMarkUsed(0, 1000); }, DB::Exception); + + ASSERT_TRUE(smap->markUsed(50, 10)); + ASSERT_FALSE(smap->markUsed(50, 10)); + ASSERT_FALSE(smap->markUsed(50, 9)); + ASSERT_FALSE(smap->markUsed(55, 5)); + Range ranges1[] = {{.start = 0, + .end = 50}, + {.start = 60, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges1, 2), 2)); + ASSERT_TRUE(smap->isMarkUsed(51, 5)); + + ASSERT_TRUE(smap->markFree(50, 5)); + Range ranges2[] = {{.start = 0, + .end = 55}, + {.start = 60, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges2, 2), 2)); + ASSERT_TRUE(smap->markFree(55, 5)); + ASSERT_TRUE(smap->check(genChecker(ranges, 1), 1)); +} + +TEST_P(SpaceMapTest, MarkmarkFree2) +{ + auto smap = SpaceMap::createSpaceMap(test_type, 0, 100); + + ASSERT_TRUE(smap->markUsed(50, 20)); + ASSERT_FALSE(smap->markUsed(50, 1)); + ASSERT_FALSE(smap->markUsed(50, 20)); + ASSERT_FALSE(smap->markUsed(55, 15)); + Range ranges1[] = {{.start = 0, + .end = 50}, + {.start = 70, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges1, 2), 2)); + ASSERT_TRUE(smap->isMarkUsed(51, 5)); + + ASSERT_TRUE(smap->markFree(50, 5)); + Range ranges2[] = {{.start = 0, + .end = 55}, + {.start = 70, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges2, 2), 2)); + + ASSERT_TRUE(smap->markFree(60, 5)); + Range ranges3[] = {{.start = 0, + .end = 55}, + {.start = 60, + .end = 65}, + {.start = 70, + .end = 100}}; + + ASSERT_TRUE(smap->check(genChecker(ranges3, 3), 3)); + + ASSERT_TRUE(smap->markFree(65, 5)); + Range ranges4[] = {{.start = 0, + .end = 55}, + {.start = 60, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges4, 2), 2)); + + Range ranges[] = {{.start = 0, + .end = 100}}; + ASSERT_TRUE(smap->markFree(55, 5)); + ASSERT_TRUE(smap->check(genChecker(ranges, 1), 1)); +} + +TEST_P(SpaceMapTest, TestMargins) +{ + auto smap = SpaceMap::createSpaceMap(test_type, 0, 100); + + Range ranges[] = {{.start = 0, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges, 1), 1)); + ASSERT_TRUE(smap->markUsed(50, 10)); + + Range ranges1[] = {{.start = 0, + .end = 50}, + {.start = 60, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges1, 2), 2)); + + ASSERT_TRUE(smap->isMarkUsed(50, 5)); + ASSERT_FALSE(smap->isMarkUsed(60, 1)); + + // Test for two near markUsed + ASSERT_TRUE(smap->markUsed(60, 10)); + Range ranges2[] = {{.start = 0, + .end = 50}, + {.start = 70, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges2, 2), 2)); + + ASSERT_TRUE(smap->markUsed(49, 1)); + Range ranges3[] = {{.start = 0, + .end = 49}, + {.start = 70, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges3, 2), 2)); + + ASSERT_TRUE(smap->markFree(49, 1)); + ASSERT_TRUE(smap->check(genChecker(ranges2, 2), 2)); +} + +TEST_P(SpaceMapTest, TestMargins2) +{ + auto smap = SpaceMap::createSpaceMap(test_type, 0, 100); + Range ranges[] = {{.start = 0, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges, 1), 1)); + ASSERT_TRUE(smap->markUsed(50, 10)); + + // total in marked used range + ASSERT_FALSE(smap->markUsed(50, 1)); + ASSERT_FALSE(smap->markUsed(59, 1)); + ASSERT_FALSE(smap->markUsed(55, 1)); + ASSERT_FALSE(smap->markUsed(55, 5)); + ASSERT_FALSE(smap->markUsed(50, 5)); + + // Right margin in marked used space + // Left margin contain freed space + ASSERT_FALSE(smap->markUsed(45, 10)); + + // Left margin in marked used space + // Right margin contain freed space + ASSERT_FALSE(smap->markUsed(55, 15)); + + // Left margin align with marked used space left margin + // But right margin contain freed space + ASSERT_FALSE(smap->markUsed(50, 20)); + + // Right margin align with marked used space right margin + // But left margin contain freed space + ASSERT_FALSE(smap->markUsed(40, 20)); + + // Left margin in freed space + // Right margin in freed space + // But used space in the middle + ASSERT_FALSE(smap->markUsed(40, 30)); + + + Range ranges1[] = {{.start = 0, + .end = 50}, + {.start = 60, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges1, 2), 2)); + + ASSERT_TRUE(smap->markFree(50, 1)); + + // Mark a space which contain a sub freed space. + ASSERT_FALSE(smap->markFree(50, 2)); + ASSERT_FALSE(smap->markFree(50, 5)); + ASSERT_TRUE(smap->markFree(59, 1)); + + // Left margin in marked used space + // Right margin contain freed space + ASSERT_FALSE(smap->markFree(58, 10)); + + // Right margin in marked used space + // Left margin contain freed space + ASSERT_FALSE(smap->markFree(49, 10)); + smap->logStats(); + // Left margin align with marked used space left margin + // But right margin contain freed space + ASSERT_FALSE(smap->markFree(51, 20)); + smap->logStats(); + // Right margin align with marked used space right margin + // But left margin contain freed space + ASSERT_FALSE(smap->markUsed(40, 19)); + + // Left margin in freed space + // Right margin in freed space + // But used space in the middle + ASSERT_FALSE(smap->markUsed(40, 30)); + + + Range ranges2[] = {{.start = 0, + .end = 51}, + {.start = 59, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges2, 2), 2)); +} + +TEST_P(SpaceMapTest, TestSearch) +{ + auto smap = SpaceMap::createSpaceMap(test_type, 0, 100); + UInt64 offset; + UInt64 max_cap; + Range ranges[] = {{.start = 0, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges, 1), 1)); + ASSERT_TRUE(smap->markUsed(50, 10)); + + std::tie(offset, max_cap) = smap->searchInsertOffset(20); + ASSERT_EQ(offset, 0); + ASSERT_EQ(max_cap, 40); + + Range ranges1[] = {{.start = 20, + .end = 50}, + {.start = 60, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges1, 2), 2)); + + // We can't use `markFree` to restore the map status + // It won't update `max_cap`/`max_offset` which inside space map + // So just recreate a space map + smap = SpaceMap::createSpaceMap(test_type, 0, 100); + ASSERT_TRUE(smap->markUsed(50, 10)); + + std::tie(offset, max_cap) = smap->searchInsertOffset(5); + ASSERT_EQ(offset, 0); + ASSERT_EQ(max_cap, 45); + + Range ranges2[] = {{.start = 5, + .end = 50}, + {.start = 60, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges2, 2), 2)); + + // Test margin + smap = SpaceMap::createSpaceMap(test_type, 0, 100); + ASSERT_TRUE(smap->markUsed(50, 10)); + std::tie(offset, max_cap) = smap->searchInsertOffset(50); + ASSERT_EQ(offset, 0); + ASSERT_EQ(max_cap, 40); + + Range ranges3[] = {{.start = 60, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges3, 1), 1)); + + // Test invalid Size + smap = SpaceMap::createSpaceMap(test_type, 0, 100); + ASSERT_TRUE(smap->markUsed(50, 10)); + std::tie(offset, max_cap) = smap->searchInsertOffset(100); + ASSERT_EQ(offset, UINT64_MAX); + ASSERT_EQ(max_cap, 50); + + // No changed + Range ranges4[] = {{.start = 0, + .end = 50}, + {.start = 60, + .end = 100}}; + ASSERT_TRUE(smap->check(genChecker(ranges4, 2), 2)); +} + +INSTANTIATE_TEST_CASE_P( + Type, + SpaceMapTest, + testing::Values( + SpaceMap::SMAP64_RBTREE, + SpaceMap::SMAP64_STD_MAP)); + +} // namespace DB::PS::V3::tests