├── .clang-format ├── .clang-tidy ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ └── enhancement.md ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── conventional-commits.yml │ └── signed-commits.yml ├── .gitignore ├── CMakeLists.txt ├── CMakePresets.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── benchmarks ├── CMakeLists.txt ├── micro-benchmarks │ ├── CMakeLists.txt │ ├── insert_update_bench.cpp │ └── primitive_bench.cpp └── ycsb │ ├── CMakeLists.txt │ ├── README.md │ ├── ycsb-config.flags │ ├── ycsb.cpp │ ├── ycsb.hpp │ ├── ycsb_flags.cpp │ ├── ycsb_lean_store.hpp │ ├── ycsb_rocks_db.hpp │ └── ycsb_wired_tiger.hpp ├── codecov.yml ├── docker ├── Dockerfile ├── README.md └── vcpkg-triplets │ ├── arm64-linux.cmake │ └── x64-linux.cmake ├── docs └── images │ └── Architecture.jpg ├── examples ├── c │ ├── CMakeLists.txt │ ├── README.md │ └── basic_kv_example.c └── cpp │ ├── CMakeLists.txt │ ├── README.md │ └── basic_kv_example.cpp ├── include ├── leanstore-c │ ├── kv_basic.h │ ├── kv_txn.h │ ├── leanstore.h │ ├── perf_counters.h │ └── store_option.h └── leanstore │ ├── btree │ ├── basic_kv.hpp │ ├── chained_tuple.hpp │ ├── core │ │ ├── b_tree_generic.hpp │ │ ├── b_tree_node.hpp │ │ ├── iterator.hpp │ │ ├── pessimistic_exclusive_iterator.hpp │ │ ├── pessimistic_iterator.hpp │ │ └── pessimistic_shared_iterator.hpp │ ├── transaction_kv.hpp │ └── tuple.hpp │ ├── buffer-manager │ ├── async_write_buffer.hpp │ ├── bm_plain_guard.hpp │ ├── buffer_frame.hpp │ ├── buffer_manager.hpp │ ├── free_list.hpp │ ├── guarded_buffer_frame.hpp │ ├── page_evictor.hpp │ ├── partition.hpp │ ├── swip.hpp │ └── tree_registry.hpp │ ├── concurrency │ ├── concurrency_control.hpp │ ├── cr_manager.hpp │ ├── group_committer.hpp │ ├── history_storage.hpp │ ├── logging.hpp │ ├── logging_impl.hpp │ ├── recovery.hpp │ ├── transaction.hpp │ ├── wal_entry.hpp │ ├── wal_payload_handler.hpp │ ├── worker_context.hpp │ └── worker_thread.hpp │ ├── exceptions.hpp │ ├── kv_interface.hpp │ ├── lean_store.hpp │ ├── slice.hpp │ ├── sync │ ├── hybrid_guard.hpp │ ├── hybrid_latch.hpp │ ├── optimistic_guarded.hpp │ └── scoped_hybrid_guard.hpp │ ├── units.hpp │ └── utils │ ├── async_io.hpp │ ├── counter_util.hpp │ ├── debug_flags.hpp │ ├── defer.hpp │ ├── error.hpp │ ├── fnv_hash.hpp │ ├── json_util.hpp │ ├── jump_mu.hpp │ ├── log.hpp │ ├── misc.hpp │ ├── parallelize.hpp │ ├── random_generator.hpp │ ├── result.hpp │ ├── scrambled_zipf_generator.hpp │ ├── user_thread.hpp │ └── zipf_generator.hpp ├── scripts └── build.sh ├── src ├── CMakeLists.txt ├── btree │ ├── basic_kv.cpp │ ├── chained_tuple.cpp │ ├── core │ │ ├── b_tree_generic.cpp │ │ ├── b_tree_node.cpp │ │ ├── b_tree_wal_payload.cpp │ │ └── b_tree_wal_payload.hpp │ ├── transaction_kv.cpp │ └── tuple.cpp ├── buffer-manager │ ├── async_write_buffer.cpp │ ├── buffer_manager.cpp │ ├── page_evictor.cpp │ └── partition.cpp ├── concurrency │ ├── concurrency_control.cpp │ ├── cr_manager.cpp │ ├── group_committer.cpp │ ├── history_storage.cpp │ ├── logging.cpp │ ├── recovery.cpp │ └── worker_context.cpp ├── lean_store.cpp ├── leanstore-c │ ├── leanstore.cpp │ └── store_option.cpp ├── leanstore.pc.in ├── telemetry │ ├── metrics_http_exposer.cpp │ └── metrics_http_exposer.hpp └── utils │ ├── fnv_hash.cpp │ ├── jump_mu.cpp │ ├── log.cpp │ ├── misc.cpp │ ├── parallelize.cpp │ ├── random_generator.cpp │ ├── scrambled_zipf_generator.cpp │ ├── to_json.hpp │ └── zipf_generator.cpp ├── tests ├── CMakeLists.txt ├── abort_test.cpp ├── anomalies_test.cpp ├── btree │ ├── b_tree_generic_test.cpp │ ├── b_tree_wal_payload_test.cpp │ ├── basic_kv_iterator_test.cpp │ └── basic_kv_test.cpp ├── buffer-manager │ ├── async_write_buffer_test.cpp │ └── page_evictor_test.cpp ├── concurrency │ └── wal_entry_test.cpp ├── long_running_tx_test.cpp ├── mvcc_test.cpp ├── optimistic_guarded_test.cpp ├── recovery_test.cpp ├── sync │ └── scoped_hybrid_guard_test.cpp ├── transaction_kv_test.cpp ├── tsan.supp └── tx_kv.hpp ├── vcpkg-configuration.json └── vcpkg.json /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | # BreakBeforeBraces: Linux 4 | # SpaceInEmptyParentheses: 'false' 5 | ColumnLimit: 100 6 | IndentWidth: 2 7 | PointerAlignment: Left 8 | AlwaysBreakTemplateDeclarations: true 9 | 10 | PenaltyReturnTypeOnItsOwnLine: 1000 11 | 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: None 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | EmptyLineBeforeAccessModifier: Always 18 | Cpp11BracedListStyle: true 19 | 20 | # constructor initializers 21 | PackConstructorInitializers: CurrentLine 22 | 23 | # arguments 24 | AlignAfterOpenBracket: Align 25 | QualifierAlignment: Custom 26 | QualifierOrder: ['inline', 'static', 'const', 'volatile', 'type'] 27 | 28 | # include headers 29 | IncludeCategories: 30 | - Regex: '^".*.hpp' 31 | Priority: 2 32 | - Regex: '^("|<)leanstore/*' 33 | Priority: 2 34 | - Regex: '^("|<)(benchmark)/' 35 | Priority: 3 36 | - Regex: '^("|<)(gtest|gmock|gflags|spdlog|glog|rapidjson|rocksdb|gperftools|crc32c|prometheus)/' 37 | Priority: 4 38 | - Regex: '^("|<)(httplib)' 39 | Priority: 4 40 | - Regex: '<[[:alnum:]_/]+>' 41 | Priority: 5 42 | - Regex: '<[[:alnum:]_/.]+>' 43 | Priority: 6 44 | IncludeBlocks: Regroup 45 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: > 2 | -*, 3 | clang-diagnostic-*, 4 | -llvmlibc-restrict-system-libc-headers, 5 | llvm-*, 6 | misc-*, 7 | -misc-const-correctness, 8 | -misc-unused-parameters, 9 | -misc-non-private-member-variables-in-classes, 10 | -misc-no-recursion, 11 | -misc-use-anonymous-namespace, 12 | readability-identifier-naming, 13 | -llvm-header-guard, 14 | -llvm-include-order, 15 | 16 | WarningsAsErrors: "*" 17 | 18 | CheckOptions: 19 | 20 | # function 21 | - key: readability-identifier-naming.FunctionCase 22 | value: CamelCase 23 | - key: readability-identifier-naming.IgnoreMainLikeFunctions 24 | value: 1 25 | 26 | # parameter 27 | - key: readability-identifier-naming.ParameterCase 28 | value: lower_case 29 | 30 | # class 31 | - key: readability-identifier-naming.ClassCase 32 | value: CamelCase 33 | 34 | # public method 35 | - key: readability-identifier-naming.PublicMethodCase 36 | value: CamelCase 37 | 38 | # protected method 39 | - key: readability-identifier-naming.ProtectedMethodCase 40 | value: CamelCase 41 | 42 | # private method 43 | - key: readability-identifier-naming.PrivateMethodCase 44 | value: CamelCase 45 | 46 | # public member 47 | - key: readability-identifier-naming.PublicMemberCase 48 | value: lower_case 49 | - key: readability-identifier-naming.PublicMemberSuffix 50 | value: _ 51 | 52 | # protected member 53 | - key: readability-identifier-naming.ProtectedMemberCase 54 | value: lower_case 55 | - key: readability-identifier-naming.ProtectedMemberSuffix 56 | value: _ 57 | 58 | # private member 59 | - key: readability-identifier-naming.PrivateMemberCase 60 | value: lower_case 61 | - key: readability-identifier-naming.PrivateMemberSuffix 62 | value: _ 63 | 64 | # class member 65 | - key: readability-identifier-naming.ClassMemberCase 66 | value: CamelCase 67 | - key: readability-identifier-naming.ClassMemberPrefix 68 | value: s 69 | 70 | # enum 71 | - key: readability-identifier-naming.EnumCase 72 | value: CamelCase 73 | - key: readability-identifier-naming.EnumConstantCase 74 | value: CamelCase 75 | - key: readability-identifier-naming.EnumConstantPrefix 76 | value: k 77 | 78 | # union 79 | - key: readability-identifier-naming.UnionCase 80 | value: CamelCase 81 | 82 | # variable 83 | - key: readability-identifier-naming.VariableCase 84 | value: lower_case 85 | 86 | # static variable 87 | - key: readability-identifier-naming.StaticVariableCase 88 | value: CamelCase 89 | - key: readability-identifier-naming.StaticVariablePrefix 90 | value: s 91 | 92 | # static const 93 | - key: readability-identifier-naming.StaticConstantCase 94 | value: CamelCase 95 | - key: readability-identifier-naming.StaticConstantPrefix 96 | value: k 97 | 98 | # constexpr 99 | - key: readability-identifier-naming.ConstexprVariableCase 100 | value: CamelCase 101 | - key: readability-identifier-naming.ConstexprVariablePrefix 102 | value: k 103 | 104 | - key: readability-redundant-member-init.IgnoreBaseInCopyConstructors 105 | value: 1 106 | 107 | - key: modernize-use-default-member-init.UseAssignment 108 | value: 1 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug Report" 3 | about: Bug report 4 | --- 5 | 6 | ## Bug Report 7 | 8 | ### Minimal reproduce step 9 | 10 | 11 | 12 | ### Expected result 13 | 14 | ### Actual result 15 | 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Enhancement" 3 | about: Functionality enhancement 4 | --- 5 | 6 | ## Enhancement 7 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## What's changed and how does it work? 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | ut_coverage: 11 | runs-on: ubuntu-24.04 12 | name: Unit Tests - Coverage 13 | steps: 14 | 15 | - name: Check out repository 16 | uses: actions/checkout@v3 17 | 18 | - name: Install dependencies 19 | run: | 20 | cat /etc/lsb-release; 21 | sudo apt update; 22 | sudo apt install -y build-essential git cmake make gcc-13 g++-13; 23 | sudo apt install -y libaio-dev python3-pip cppcheck ninja-build; 24 | sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu; 25 | sudo apt install -y curl zip unzip tar pkg-config; 26 | sudo apt install -y autoconf libtool; 27 | sudo apt install -y clang-format-18; 28 | sudo ln -sf /usr/bin/cpp-13 /usr/bin/cpp; 29 | sudo ln -sf /usr/bin/g++-13 /usr/bin/g++; 30 | sudo ln -sf /usr/bin/gcc-13 /usr/bin/gcc; 31 | sudo ln -sf /usr/bin/gcc-ar-13 /usr/bin/gcc-ar; 32 | sudo ln -sf /usr/bin/gcc-nm-13 /usr/bin/gcc-nm; 33 | sudo ln -sf /usr/bin/gcc-ranlib-13 /usr/bin/gcc-ranlib; 34 | sudo ln -sf /usr/bin/gcov-13 /usr/bin/gcov; 35 | sudo ln -sf /usr/bin/gcov-dump-13 /usr/bin/gcov-dump; 36 | sudo ln -sf /usr/bin/gcov-tool-13 /usr/bin/gcov-tool; 37 | sudo ln -sf /usr/bin/lto-dump-13 /usr/bin/lto-dump; 38 | sudo ln -sf /usr/bin/g++-13 /usr/bin/c++; 39 | sudo ln -sf /usr/bin/gcc-13 /usr/bin/cc; 40 | sudo ln -sf /usr/bin/clang-format-18 /usr/bin/clang-format; 41 | pip3 install gcovr==6.0 --break-system-packages; 42 | sudo git clone https://github.com/Microsoft/vcpkg.git /opt/vcpkg; 43 | echo 'export VCPKG_ROOT=/opt/vcpkg' >> ~/.bashrc; 44 | echo 'export PATH=$VCPKG_ROOT:$PATH' >> ~/.bashrc; 45 | echo 'export VCPKG_FORCE_SYSTEM_BINARIES=1' >> ~/.bashrc; 46 | sudo rm -rf /var/lib/apt/lists/*; 47 | 48 | - name: Config project 49 | run: | 50 | export VCPKG_ROOT=/opt/vcpkg; 51 | export PATH=$VCPKG_ROOT:$PATH; 52 | export VCPKG_FORCE_SYSTEM_BINARIES=1; 53 | which vcpkg; 54 | cmake --preset debug_tsan; 55 | 56 | - name: Cppcheck 57 | run: cppcheck --project=build/debug_tsan/compile_commands.json -i tests --error-exitcode=1 58 | 59 | - name: Check clang-format 60 | run: cmake --build build/debug_tsan --target=check-format 61 | 62 | - name: Build project 63 | run: cmake --build build/debug_tsan -j `nproc` 64 | 65 | - name: Unit test with tsan 66 | run: TSAN_OPTIONS="suppressions=$(pwd)/tests/tsan.supp" ctest --test-dir build/debug_tsan --output-on-failure -j 2 67 | 68 | - name: Generate coverage file 69 | run: gcovr -v -r . --xml-pretty --xml=coverage.xml --exclude 'build/*' --exclude 'tests/*' --exclude 'benchmarks/*' --verbose 70 | 71 | - name: Upload coverage reports to Codecov 72 | uses: codecov/codecov-action@v4 73 | with: 74 | fail_ci_if_error: true 75 | files: coverage.xml 76 | verbose: true 77 | env: 78 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 79 | -------------------------------------------------------------------------------- /.github/workflows/conventional-commits.yml: -------------------------------------------------------------------------------- 1 | name: PR Check 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, edited] 6 | 7 | jobs: 8 | build: 9 | name: Conventional Commits 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Conventional Commits Validation 13 | uses: ytanikin/PRConventionalCommits@1.1.0 14 | with: 15 | task_types: '["feat","fix","perf","chore","revert"]' 16 | add_label: 'false' -------------------------------------------------------------------------------- /.github/workflows/signed-commits.yml: -------------------------------------------------------------------------------- 1 | name: PR Check 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, edited] 6 | 7 | jobs: 8 | check-signed-commits: 9 | name: Signed Commits 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | pull-requests: write 14 | steps: 15 | - name: Signed Commits Validation 16 | uses: 1Password/check-signed-commits-action@v1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .cache 3 | build 4 | Testing 5 | dist 6 | vcpkg_installed 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Project settings 3 | # ------------------------------------------------------------------------------ 4 | 5 | cmake_minimum_required(VERSION 3.10) 6 | project(leanstoredb CXX) 7 | 8 | if (NOT UNIX) 9 | message(SEND_ERROR "unsupported platform") 10 | endif () 11 | 12 | enable_language(ASM) 13 | 14 | set(CMAKE_CXX_STANDARD 23) 15 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 16 | 17 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 18 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -Wextra -rdynamic -fno-omit-frame-pointer -pthread -Wno-error=clobbered") 19 | if (CMAKE_BUILD_TYPE MATCHES Debug) 20 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDEBUG -O0 -g3") 21 | else() 22 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG -O3 -g") 23 | endif() 24 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 25 | add_compile_options(-Wno-vla-cxx-extension) 26 | endif() 27 | 28 | # for clangd 29 | add_compile_definitions(__cpp_concepts=202002L) 30 | 31 | # TODO(jian.z): support compile with clang 32 | # # compile flags for clang 33 | # if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 34 | # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-command-line-argument") 35 | # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} __cpp_concepts=202002L") 36 | # endif() 37 | 38 | # platform specific flags 39 | if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)") 40 | add_compile_options(-mavx2 -mcx16 -m64) 41 | else() 42 | add_compile_options(-march=native) 43 | endif() 44 | 45 | if(ENABLE_COVERAGE) 46 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage") 47 | endif() 48 | 49 | if(ENABLE_ASAN) 50 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") 51 | endif() 52 | 53 | if(ENABLE_TSAN) 54 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") 55 | endif() 56 | 57 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" AND CMAKE_BUILD_TYPE MATCHES Debug) 58 | add_compile_options(-fstandalone-debug) 59 | endif () 60 | 61 | if (APPLE) 62 | LIST(APPEND CMAKE_PREFIX_PATH /usr/local/opt/bison) 63 | LIST(APPEND CMAKE_PREFIX_PATH /usr/local/opt/flex) 64 | endif (APPLE) 65 | 66 | if (CYGWIN) 67 | SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++") 68 | endif (CYGWIN) 69 | 70 | 71 | # ------------------------------------------------------------------------------ 72 | # Dependencies 73 | # ------------------------------------------------------------------------------ 74 | 75 | find_package(RapidJSON CONFIG REQUIRED) 76 | find_package(spdlog CONFIG REQUIRED) 77 | find_package(GTest CONFIG REQUIRED) 78 | find_package(benchmark CONFIG REQUIRED) 79 | find_package(Crc32c CONFIG REQUIRED) 80 | find_package(httplib CONFIG REQUIRED) 81 | find_package(PkgConfig) 82 | pkg_check_modules(libunwind REQUIRED IMPORTED_TARGET GLOBAL libunwind) 83 | 84 | # ------------------------------------------------------------------------------ 85 | # Project build targets 86 | # ------------------------------------------------------------------------------ 87 | 88 | add_subdirectory(src) 89 | add_subdirectory(benchmarks) 90 | 91 | # tests 92 | include(CTest) 93 | add_subdirectory(tests) 94 | 95 | 96 | # --------------------------------------------------------------------------- 97 | # clang-format 98 | # --------------------------------------------------------------------------- 99 | 100 | file(GLOB_RECURSE ALL_SOURCE_FILES 101 | "src/*.cpp" "src/*.hpp" 102 | "include/*.hpp" 103 | "tests/*.cpp" "tests/*.hpp" 104 | "benchmarks/*.cpp" "benchmarks/*.hpp") 105 | 106 | # check-format 107 | add_custom_target(check-format 108 | COMMAND clang-format --style=file --dry-run -Werror -i ${ALL_SOURCE_FILES} 109 | VERBATIM 110 | ) 111 | 112 | # fix format 113 | add_custom_target(format 114 | COMMAND clang-format --style=file -i ${ALL_SOURCE_FILES} 115 | VERBATIM 116 | ) 117 | 118 | 119 | # --------------------------------------------------------------------------- 120 | # clang-tidy 121 | # --------------------------------------------------------------------------- 122 | 123 | # clang-tidy 124 | add_custom_target(check-tidy 125 | COMMAND clang-tidy -p=${CMAKE_BINARY_DIR} --config-file=${CMAKE_SOURCE_DIR}/.clang-tidy ${ALL_SOURCE_FILES} -extra-arg='--std=c++2b' 126 | COMMENT "Running Clang-Tidy" 127 | VERBATIM 128 | ) 129 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "configurePresets": [ 4 | { 5 | "name": "base", 6 | "hidden": true, 7 | "cacheVariables": { 8 | "CMAKE_C_COMPILER": "gcc", 9 | "CMAKE_CXX_COMPILER": "g++", 10 | "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", 11 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", 12 | "BUILD_SHARED_LIBS": "ON", 13 | "ENABLE_PROFILING": "ON", 14 | "ENABLE_PERF_COUNTERS": "ON" 15 | } 16 | }, 17 | { 18 | "name": "base_debug", 19 | "inherits": "base", 20 | "hidden": true, 21 | "cacheVariables": { 22 | "CMAKE_BUILD_TYPE": "Debug" 23 | } 24 | }, 25 | { 26 | "name": "base_release", 27 | "inherits": "base", 28 | "hidden": true, 29 | "cacheVariables": { 30 | "CMAKE_BUILD_TYPE": "Release" 31 | } 32 | }, 33 | { 34 | "name": "debug", 35 | "inherits": "base_debug", 36 | "binaryDir": "${sourceDir}/build/debug", 37 | "cacheVariables": { 38 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/dist/debug", 39 | "ENABLE_COVERAGE": "ON" 40 | } 41 | }, 42 | { 43 | "name": "debug_tsan", 44 | "inherits": "base_debug", 45 | "binaryDir": "${sourceDir}/build/debug_tsan", 46 | "cacheVariables": { 47 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/dist/debug_tsan", 48 | "ENABLE_TSAN": "ON", 49 | "ENABLE_COVERAGE": "ON" 50 | } 51 | }, 52 | { 53 | "name": "release", 54 | "inherits": "base_release", 55 | "binaryDir": "${sourceDir}/build/release", 56 | "cacheVariables": { 57 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/dist/release" 58 | } 59 | }, 60 | { 61 | "name": "performance", 62 | "inherits": "base_release", 63 | "binaryDir": "${sourceDir}/build/performance", 64 | "cacheVariables": { 65 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/dist/performance", 66 | "ENABLE_ROCKSDB": "ON", 67 | "ENABLE_WIRED_TIGER": "ON" 68 | } 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Get Started 2 | 3 | ### Setup VS Code and Docker development environment 4 | 5 | LeanStore requires libaio, c++23. It's recommended to develop the project inside 6 | a docker container for convenience, which can be built from this [Dockerfile][0]. 7 | 8 | For VS Code, it's recommended to install the ["C/C++ Extension Pack"][1] and 9 | ["clangd"][2] plugins. 10 | 11 | ### Get the code, build and run 12 | 13 | ```sh 14 | git clone git@github.com:zz-jason/leanstore.git 15 | cd leanstore 16 | ``` 17 | 18 | [vcpkg][3] is used to manage dependencies, all the dependencies can be found in 19 | [vcpkg.json][5]. [CMakePresets.json][4] is used to manage common build configs. 20 | You can choose one of these cmake presets when open the project in VS Code. To 21 | compile LeanStore in debug mode: 22 | 23 | ```sh 24 | cmake --preset debug 25 | cmake --build build/debug 26 | ``` 27 | 28 | To run unittests in the debug mode: 29 | 30 | ```sh 31 | ctest --test-dir build/debug 32 | ``` 33 | 34 | To check and fix code formatting with `clang-format-18`: 35 | 36 | ```sh 37 | # check format 38 | cmake --build build/debug --target=check-format 39 | 40 | # fix format 41 | cmake --build build/debug --target=format 42 | ``` 43 | 44 | To run simple ycsb benchmarks: 45 | 46 | ```sh 47 | ./build/debug/benchmarks/ycsb/ycsb \ 48 | --ycsb_threads=8 \ 49 | --ycsb_record_count=100000 \ 50 | --ycsb_workload=c \ 51 | --ycsb_run_for_seconds=600 \ 52 | --ycsb_target=basickv 53 | ``` 54 | 55 | ### Commit and submit a pull request 56 | 57 | [Conventional Commits][6] is used for pull request titles, all the available 58 | types can be found in [conventional-commits.yml][7]. 59 | 60 | [0]: ./docker/Dockerfile 61 | [1]: https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools-extension-pack 62 | [2]: https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd 63 | [3]: https://github.com/microsoft/vcpkg 64 | [4]: ./CMakePresets.json 65 | [5]: ./vcpkg.json 66 | [6]: https://www.conventionalcommits.org/en/v1.0.0/ 67 | [7]: ./.github/workflows/conventional-commits.yml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021, FSU Jena 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI][9]][10] 2 | [![codecov][3]][4] 3 | [![Join Slack][11]][12] 4 | 5 | # LeanStore 6 | 7 | LeanStore is a larger-than-memory database, optimized for NVMe SSD and 8 | multi-core CPU, achieves performance close to in-memory systems without having 9 | to keep all data in memory. 10 | 11 |
12 | 13 |
14 | 15 | ## Getting started 16 | 17 | [vcpkg][13] is used to manage third-party libraries, please install it before 18 | building the project. It's highly recommended to develop the project inside a 19 | docker container, which can be built from this [Dockerfile][5]: 20 | 21 | ```sh 22 | cmake --preset debug 23 | cmake --build build/debug -j `nproc` 24 | ctest --test-dir build/debug 25 | ``` 26 | 27 | ## Contributing 28 | 29 | Contributions are welcomed and greatly appreciated! See [CONTRIBUTING.md][6] for 30 | setting up development environment and contributing. 31 | 32 | You can also join the [slack workspace][12] to discuss any questions or ideas. 33 | 34 | ## License 35 | 36 | LeanStore is under the [MIT License][7]. 37 | 38 | ## Acknowledgments 39 | 40 | Thanks for the LeanStore authors and the [leanstore/leanstore][8] project. 41 | 42 | [3]: https://codecov.io/github/zz-jason/leanstore/graph/badge.svg?token=MBS1H361JJ 43 | [4]: https://codecov.io/github/zz-jason/leanstore 44 | [5]: ./docker/Dockerfile 45 | [6]: ./CONTRIBUTING.md 46 | [7]: ./LICENSE 47 | [8]: http://github.com/leanstore/leanstore 48 | [9]: https://github.com/zz-jason/leanstore/actions/workflows/ci.yml/badge.svg 49 | [10]: https://github.com/zz-jason/leanstore/actions/workflows/ci.yml 50 | [11]: https://img.shields.io/badge/Join-Slack-blue.svg?logo=slack 51 | [12]: https://join.slack.com/t/leanstoreworkspace/shared_invite/zt-2o69igywh-yTheoWxjYnD5j3bAFN34Qg 52 | [13]: https://github.com/microsoft/vcpkg 53 | -------------------------------------------------------------------------------- /benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(micro-benchmarks) 2 | add_subdirectory(ycsb) 3 | -------------------------------------------------------------------------------- /benchmarks/micro-benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(PrimitiveBench primitive_bench.cpp) 2 | add_executable(InsertUpdateBench insert_update_bench.cpp) 3 | 4 | target_link_libraries(PrimitiveBench benchmark::benchmark benchmark::benchmark_main leanstore atomic) 5 | target_link_libraries(InsertUpdateBench benchmark::benchmark benchmark::benchmark_main GTest::gtest leanstore atomic) 6 | -------------------------------------------------------------------------------- /benchmarks/micro-benchmarks/insert_update_bench.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore-c/store_option.h" 2 | #include "leanstore/btree/basic_kv.hpp" 3 | #include "leanstore/btree/transaction_kv.hpp" 4 | #include "leanstore/buffer-manager/buffer_manager.hpp" 5 | #include "leanstore/concurrency/cr_manager.hpp" 6 | #include "leanstore/lean_store.hpp" 7 | #include "leanstore/utils/random_generator.hpp" 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | namespace leanstore::test { 19 | 20 | static void BenchUpdateInsert(benchmark::State& state) { 21 | std::filesystem::path dir_path = "/tmp/InsertUpdateBench"; 22 | std::filesystem::remove_all(dir_path); 23 | std::filesystem::create_directories(dir_path); 24 | 25 | StoreOption* option = CreateStoreOption("/tmp/leanstore/InsertUpdateBench"); 26 | option->create_from_scratch_ = true; 27 | option->worker_threads_ = 4; 28 | auto sLeanStore = std::make_unique(option); 29 | 30 | storage::btree::TransactionKV* btree; 31 | 32 | // create leanstore btree for table records 33 | const auto* btree_name = "testTree1"; 34 | sLeanStore->ExecSync(0, [&]() { 35 | auto res = sLeanStore->CreateTransactionKV(btree_name); 36 | EXPECT_TRUE(res); 37 | EXPECT_NE(res.value(), nullptr); 38 | btree = res.value(); 39 | }); 40 | 41 | std::unordered_set dedup; 42 | for (auto _ : state) { 43 | sLeanStore->ExecSync(0, [&]() { 44 | cr::WorkerContext::My().StartTx(); 45 | std::string key; 46 | std::string val; 47 | for (size_t i = 0; i < 16; i++) { 48 | key = utils::RandomGenerator::RandAlphString(24); 49 | val = utils::RandomGenerator::RandAlphString(128); 50 | btree->Insert(Slice((const uint8_t*)key.data(), key.size()), 51 | Slice((const uint8_t*)val.data(), val.size())); 52 | } 53 | cr::WorkerContext::My().CommitTx(); 54 | }); 55 | } 56 | 57 | sLeanStore->ExecSync(0, [&]() { 58 | cr::WorkerContext::My().StartTx(); 59 | SCOPED_DEFER(cr::WorkerContext::My().CommitTx()); 60 | sLeanStore->DropTransactionKV(btree_name); 61 | }); 62 | } 63 | 64 | BENCHMARK(BenchUpdateInsert); 65 | 66 | } // namespace leanstore::test 67 | 68 | BENCHMARK_MAIN(); -------------------------------------------------------------------------------- /benchmarks/micro-benchmarks/primitive_bench.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/buffer-manager/buffer_frame.hpp" 2 | #include "leanstore/sync/optimistic_guarded.hpp" 3 | #include "leanstore/utils/misc.hpp" 4 | #include "leanstore/utils/random_generator.hpp" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace leanstore; 16 | using namespace leanstore::utils; 17 | using namespace leanstore::storage; 18 | 19 | static void BenchU8ToPage(benchmark::State& state) { 20 | auto page_size = 4 * 1024; 21 | AlignedBuffer<512> aligned_buffer(page_size * 4); 22 | auto* buf = aligned_buffer.Get(); 23 | auto i = 1; 24 | for (auto _ : state) { 25 | reinterpret_cast(&buf[i * page_size])->gsn_ = 1; 26 | } 27 | } 28 | 29 | static void BenchPageDirectly(benchmark::State& state) { 30 | Page pages[4]; 31 | auto i = 1; 32 | for (auto _ : state) { 33 | pages[i].gsn_ = 1; 34 | } 35 | } 36 | 37 | static void BenchStdArray(benchmark::State& state) { 38 | static std::array, 8> sTmpArray; 39 | for (auto _ : state) { 40 | for (auto counter = 0; counter < 1000; counter++) { 41 | auto i = RandomGenerator::Rand(0, 8); 42 | auto val = RandomGenerator::Rand((uint64_t)0, std::numeric_limits::max()); 43 | sTmpArray[i].store(val, std::memory_order_release); 44 | } 45 | } 46 | } 47 | 48 | static void BenchVecArray(benchmark::State& state) { 49 | static std::vector> sTmpArray2(8); 50 | for (auto _ : state) { 51 | for (auto counter = 0; counter < 1000; counter++) { 52 | auto i = RandomGenerator::Rand(0, 8); 53 | auto val = RandomGenerator::Rand((uint64_t)0, std::numeric_limits::max()); 54 | sTmpArray2[i].store(val, std::memory_order_release); 55 | } 56 | } 57 | } 58 | 59 | static void BenchRawArray(benchmark::State& state) { 60 | static std::unique_ptr[]> sTmpArray3 = 61 | std::make_unique[]>(8); 62 | for (auto _ : state) { 63 | for (auto counter = 0; counter < 1000; counter++) { 64 | auto i = RandomGenerator::Rand(0, 8); 65 | auto val = RandomGenerator::Rand((uint64_t)0, std::numeric_limits::max()); 66 | sTmpArray3[i].store(val, std::memory_order_release); 67 | } 68 | } 69 | } 70 | 71 | struct TestPayload { 72 | uint64_t val1_; 73 | uint64_t val2_; 74 | 75 | TestPayload() = default; 76 | 77 | inline static TestPayload New(uint64_t val1 = 0, uint64_t val2 = 0) { 78 | TestPayload tmp; 79 | tmp.val1_ = val1; 80 | tmp.val2_ = val2; 81 | return tmp; 82 | } 83 | }; 84 | 85 | static void BenchSwmrOptimisticGuard(benchmark::State& state) { 86 | OptimisticGuarded guarded_value(TestPayload::New(0, 0)); 87 | 88 | for (auto _ : state) { 89 | if (state.thread_index() == 0) { 90 | if (auto i = RandomGenerator::Rand(0, 20); i == 0) { 91 | guarded_value.Set( 92 | TestPayload::New(RandomGenerator::Rand(0, 100), RandomGenerator::Rand(100, 200))); 93 | } 94 | } else { 95 | [[maybe_unused]] TestPayload copied_value; 96 | [[maybe_unused]] auto version = guarded_value.Get(copied_value); 97 | } 98 | } 99 | } 100 | 101 | static void BenchSwmrAtomicValue(benchmark::State& state) { 102 | std::atomic atomic_value(TestPayload::New(0, 0)); 103 | 104 | for (auto _ : state) { 105 | if (state.thread_index() == 0) { 106 | if (auto i = RandomGenerator::Rand(0, 20); i == 0) { 107 | atomic_value.store( 108 | TestPayload::New(RandomGenerator::Rand(0, 100), RandomGenerator::Rand(100, 200)), 109 | std::memory_order_release); 110 | } 111 | } else { 112 | atomic_value.load(); 113 | } 114 | } 115 | } 116 | 117 | BENCHMARK(BenchSwmrOptimisticGuard)->Threads(8); 118 | BENCHMARK(BenchSwmrAtomicValue)->Threads(8); 119 | BENCHMARK(BenchU8ToPage); 120 | BENCHMARK(BenchPageDirectly); 121 | BENCHMARK(BenchStdArray); 122 | BENCHMARK(BenchVecArray); 123 | BENCHMARK(BenchRawArray); 124 | 125 | BENCHMARK_MAIN(); -------------------------------------------------------------------------------- /benchmarks/ycsb/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------- 2 | # Set build target and dependencies for the lib 3 | # --------------------------------------------------------------------------- 4 | 5 | set(YCSB_SRC 6 | ycsb.cpp 7 | ycsb_flags.cpp 8 | ) 9 | 10 | find_package(gflags CONFIG REQUIRED) 11 | set(YCSB_DEPS 12 | leanstore 13 | gflags::gflags 14 | ) 15 | 16 | option(ENABLE_ROCKSDB "Enable benchmarking rocksdb" OFF) 17 | if(ENABLE_ROCKSDB) 18 | find_package(RocksDB CONFIG REQUIRED) 19 | list(APPEND YCSB_DEPS RocksDB::rocksdb) 20 | endif(ENABLE_ROCKSDB) 21 | 22 | option(ENABLE_WIRED_TIGER "Enable benchmarking wiredtiger" OFF) 23 | if(ENABLE_WIRED_TIGER) 24 | #TODO(zz-jason): use pre-installed wiredtiger 25 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 26 | link_directories("/root/code/wiredtiger/dist/debug/lib") 27 | else() 28 | link_directories("/root/code/wiredtiger/dist/release/lib") 29 | endif() 30 | list(APPEND YCSB_DEPS wiredtiger) 31 | endif(ENABLE_WIRED_TIGER) 32 | 33 | find_package(PkgConfig REQUIRED) 34 | pkg_check_modules(PKG_PROFILER IMPORTED_TARGET libprofiler) 35 | pkg_check_modules(PKG_TCMALLOC IMPORTED_TARGET libtcmalloc) 36 | if (PKG_PROFILER_FOUND) 37 | list(APPEND YCSB_DEPS PkgConfig::PKG_PROFILER) 38 | endif (PKG_PROFILER_FOUND) 39 | if (PKG_TCMALLOC_FOUND) 40 | message(STATUS "libtcmalloc found: ${PKG_TCMALLOC}") 41 | list(APPEND YCSB_DEPS PkgConfig::PKG_TCMALLOC) 42 | endif (PKG_TCMALLOC_FOUND) 43 | 44 | add_executable(ycsb ${YCSB_SRC}) 45 | target_link_libraries(ycsb PRIVATE ${YCSB_DEPS}) 46 | 47 | if(ENABLE_ROCKSDB) 48 | target_compile_definitions(ycsb PUBLIC ENABLE_ROCKSDB) 49 | endif(ENABLE_ROCKSDB) 50 | 51 | if(ENABLE_WIRED_TIGER) 52 | target_compile_definitions(ycsb PUBLIC ENABLE_WIRED_TIGER) 53 | target_include_directories(ycsb PUBLIC "/root/code/wiredtiger/dist/debug/include") 54 | endif(ENABLE_WIRED_TIGER) 55 | 56 | # include dirs 57 | target_include_directories(ycsb PUBLIC ${CMAKE_SOURCE_DIR}/include) 58 | target_include_directories(ycsb PUBLIC ${CMAKE_SOURCE_DIR}/benchmarks) 59 | target_include_directories(ycsb PRIVATE ${CMAKE_CURRENT_LIST_DIR}) -------------------------------------------------------------------------------- /benchmarks/ycsb/README.md: -------------------------------------------------------------------------------- 1 | # YCSB Benchmark 2 | 3 | ## Build With RocksDB 4 | 5 | 1. Add `rocksdb` to `$CMAKE_SOURCE_DIR/vcpkg.json` 6 | 7 | 2. Build the project: 8 | 9 | ```sh 10 | cmake --preset=performance 11 | cmake --build build/performance -j `nproc` 12 | ``` 13 | 14 | ## Run YCSB 15 | 16 | For convenience, a `ycsb-config.flags` file is provided to configure the YCSB 17 | benchmark. You can modify the parameters in this file or override them with 18 | command line arguments according to your needs 19 | 20 | ```sh 21 | # load data 22 | ./build/performance/benchmarks/ycsb/ycsb -flagfile=benchmarks/ycsb/ycsb-config.flags -ycsb_cmd=load 23 | 24 | # run benchmark 25 | ./build/performance/benchmarks/ycsb/ycsb -flagfile=benchmarks/ycsb/ycsb-config.flags -ycsb_cmd=run 26 | ``` 27 | 28 | ## Profile 29 | 30 | Get the cpu profile: 31 | 32 | ```sh 33 | curl -G "127.0.0.1:8080/profile" > cpu.prof 34 | ``` 35 | 36 | View the profile result: 37 | 38 | ```sh 39 | pprof -http 0.0.0.0:4000 build/release/benchmarks/ycsb/ycsb cpu.prof 40 | ``` 41 | 42 | Then open the browser at **127.0.0.1:4000** to view the cpu profile. 43 | -------------------------------------------------------------------------------- /benchmarks/ycsb/ycsb-config.flags: -------------------------------------------------------------------------------- 1 | --ycsb_data_dir=/root/code/leanstore/deploy/ycsb 2 | --ycsb_key_size=16 3 | --ycsb_val_size=200 4 | --ycsb_record_count=1000000 5 | --ycsb_mem_gb=1 6 | --ycsb_run_for_seconds=30 7 | --ycsb_target=basickv 8 | --ycsb_workload=c 9 | --ycsb_threads=4 10 | --ycsb_cmd=run 11 | -------------------------------------------------------------------------------- /benchmarks/ycsb/ycsb.cpp: -------------------------------------------------------------------------------- 1 | #include "ycsb.hpp" 2 | 3 | #include "leanstore/utils/defer.hpp" 4 | #include "leanstore/utils/log.hpp" 5 | #include "ycsb_lean_store.hpp" 6 | #include "ycsb_rocks_db.hpp" 7 | #include "ycsb_wired_tiger.hpp" 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // For data preparation 17 | static std::string kCmdLoad = "load"; 18 | static std::string kCmdRun = "run"; 19 | static std::string kTargetTransactionKv = "transactionkv"; 20 | static std::string kTargetBasicKv = "basickv"; 21 | static std::string kTargetRocksDb = "rocksdb"; 22 | static std::string kWiredTiger = "wiredtiger"; 23 | 24 | int main(int argc, char** argv) { 25 | gflags::SetUsageMessage("Ycsb Benchmark"); 26 | gflags::ParseCommandLineFlags(&argc, &argv, true); 27 | 28 | // Transform ycsb_target to lowercase 29 | std::transform(FLAGS_ycsb_target.begin(), FLAGS_ycsb_target.end(), FLAGS_ycsb_target.begin(), 30 | [](unsigned char c) { return std::tolower(c); }); 31 | 32 | // Transform ycsb_cmd to lowercase 33 | std::transform(FLAGS_ycsb_cmd.begin(), FLAGS_ycsb_cmd.end(), FLAGS_ycsb_cmd.begin(), 34 | [](unsigned char c) { return std::tolower(c); }); 35 | 36 | // Transform ycsb_workload to lowercase 37 | std::transform(FLAGS_ycsb_workload.begin(), FLAGS_ycsb_workload.end(), 38 | FLAGS_ycsb_workload.begin(), [](unsigned char c) { return std::tolower(c); }); 39 | 40 | if (FLAGS_ycsb_key_size < 8) { 41 | leanstore::Log::Fatal("Key size must be >= 8"); 42 | } 43 | 44 | leanstore::ycsb::YcsbExecutor* executor = nullptr; 45 | SCOPED_DEFER(if (executor != nullptr) { delete executor; }); 46 | 47 | if (FLAGS_ycsb_target == kTargetTransactionKv || FLAGS_ycsb_target == kTargetBasicKv) { 48 | bool bench_transaction_kv = FLAGS_ycsb_target == kTargetTransactionKv; 49 | bool create_from_scratch = FLAGS_ycsb_cmd == kCmdLoad; 50 | executor = new leanstore::ycsb::YcsbLeanStore(bench_transaction_kv, create_from_scratch); 51 | } else if (FLAGS_ycsb_target == kTargetRocksDb) { 52 | executor = new leanstore::ycsb::YcsbRocksDb(); 53 | } else if (FLAGS_ycsb_target == kWiredTiger) { 54 | executor = new leanstore::ycsb::YcsbWiredTiger(); 55 | } 56 | 57 | if (executor == nullptr) { 58 | leanstore::Log::Fatal(std::format("Unknown target: {}", FLAGS_ycsb_target)); 59 | } 60 | 61 | if (FLAGS_ycsb_cmd == kCmdLoad) { 62 | executor->HandleCmdLoad(); 63 | return 0; 64 | } 65 | 66 | if (FLAGS_ycsb_cmd == kCmdRun) { 67 | executor->HandleCmdRun(); 68 | return 0; 69 | } 70 | 71 | leanstore::Log::Fatal(std::format("Unknown command: {}", FLAGS_ycsb_cmd)); 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /benchmarks/ycsb/ycsb.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/utils/log.hpp" 4 | #include "leanstore/utils/scrambled_zipf_generator.hpp" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | // For the benchmark driver 20 | DECLARE_string(ycsb_target); 21 | DECLARE_string(ycsb_cmd); 22 | DECLARE_string(ycsb_workload); 23 | DECLARE_uint32(ycsb_threads); 24 | DECLARE_uint64(ycsb_mem_gb); 25 | DECLARE_uint64(ycsb_run_for_seconds); 26 | 27 | // For the data preparation 28 | DECLARE_string(ycsb_data_dir); 29 | DECLARE_uint64(ycsb_key_size); 30 | DECLARE_uint64(ycsb_val_size); 31 | DECLARE_uint64(ycsb_record_count); 32 | DECLARE_double(ycsb_zipf_factor); 33 | 34 | namespace leanstore::ycsb { 35 | 36 | enum class Distrubition : uint8_t { 37 | kUniform = 0, 38 | kZipf = 1, 39 | kLatest = 2, 40 | }; 41 | 42 | enum class Workload : uint8_t { 43 | kA = 0, 44 | kB = 1, 45 | kC = 2, 46 | kD = 3, 47 | kE = 4, 48 | kF = 5, 49 | }; 50 | 51 | struct WorkloadSpec { 52 | double read_proportion_; 53 | double update_proportion_; 54 | double scan_proportion_; 55 | double insert_proportion_; 56 | }; 57 | 58 | class YcsbExecutor { 59 | public: 60 | virtual ~YcsbExecutor() = default; 61 | 62 | virtual void HandleCmdLoad() { 63 | } 64 | 65 | virtual void HandleCmdRun() { 66 | } 67 | 68 | protected: 69 | void print_tps_summary(uint64_t report_period, uint64_t run_for_seconds, uint64_t num_threads, 70 | std::vector>& thread_committed, 71 | std::vector>& thread_aborted) { 72 | for (uint64_t i = 0; i < run_for_seconds; i += report_period) { 73 | sleep(report_period); 74 | auto committed = 0; 75 | auto aborted = 0; 76 | for (auto& c : thread_committed) { 77 | committed += c.exchange(0); 78 | } 79 | for (auto& a : thread_aborted) { 80 | aborted += a.exchange(0); 81 | } 82 | print_tps(num_threads, i, committed, aborted, report_period); 83 | } 84 | } 85 | 86 | private: 87 | void print_tps(uint64_t num_threads, uint64_t time_elasped_sec, uint64_t committed, 88 | uint64_t aborted, uint64_t report_period) { 89 | auto abort_rate = (aborted) * 1.0 / (committed + aborted); 90 | auto summary = 91 | std::format("[{} thds] [{}s] [tps={:.2f}] [committed={}] " 92 | "[conflicted={}] [conflict rate={:.2f}]", 93 | num_threads, time_elasped_sec, (committed + aborted) * 1.0 / report_period, 94 | committed, aborted, abort_rate); 95 | std::cout << summary << std::endl; 96 | } 97 | }; 98 | 99 | // Generate workload spec from workload type 100 | inline WorkloadSpec GetWorkloadSpec(Workload workload) { 101 | switch (workload) { 102 | case Workload::kA: 103 | return {0.5, 0.5, 0.0, 0.0}; 104 | case Workload::kB: 105 | return {0.95, 0.05, 0.0, 0.0}; 106 | case Workload::kC: 107 | return {1.0, 0.0, 0.0, 0.0}; 108 | case Workload::kD: 109 | return {0.95, 0.0, 0.0, 0.05}; 110 | case Workload::kE: 111 | return {0.0, 0.0, 0.95, 0.05}; 112 | case Workload::kF: 113 | return {0.5, 0.0, 0.0, 0.5}; 114 | default: 115 | Log::Fatal("Unknown workload: {}", static_cast(workload)); 116 | } 117 | return {}; 118 | } 119 | 120 | inline double CalculateTps(std::chrono::high_resolution_clock::time_point begin, 121 | std::chrono::high_resolution_clock::time_point end, 122 | uint64_t num_operations) { 123 | // calculate secondas elaspsed 124 | auto sec = std::chrono::duration_cast(end - begin).count() / 1000.0; 125 | return num_operations / sec; 126 | } 127 | 128 | inline void GenKey(uint64_t key, uint8_t* key_buf) { 129 | auto key_str = std::to_string(key); 130 | auto prefix_size = 131 | FLAGS_ycsb_key_size - key_str.size() > 0 ? FLAGS_ycsb_key_size - key_str.size() : 0; 132 | std::memset(key_buf, 'k', prefix_size); 133 | std::memcpy(key_buf + prefix_size, key_str.data(), key_str.size()); 134 | } 135 | 136 | inline void GenYcsbKey(utils::ScrambledZipfGenerator& zipf_random, uint8_t* key_buf) { 137 | GenKey(zipf_random.rand(), key_buf); 138 | } 139 | 140 | } // namespace leanstore::ycsb -------------------------------------------------------------------------------- /benchmarks/ycsb/ycsb_flags.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // For the benchmark driver 4 | DEFINE_string(ycsb_target, "leanstore", 5 | "Ycsb target, available: unordered_map, leanstore, rocksdb, leveldb"); 6 | DEFINE_string(ycsb_cmd, "run", "Ycsb command, available: run, load"); 7 | DEFINE_string(ycsb_workload, "a", "Ycsb workload, available: a, b, c, d, e, f"); 8 | DEFINE_uint32(ycsb_threads, 4, "Worker threads"); 9 | DEFINE_uint64(ycsb_mem_gb, 1, "Max memory in GB to use"); 10 | DEFINE_uint64(ycsb_run_for_seconds, 300, "Run the benchmark for x seconds"); 11 | 12 | // For the data preparation 13 | DEFINE_string(ycsb_data_dir, "/tmp/ycsb", "Ycsb data dir"); 14 | DEFINE_uint64(ycsb_key_size, 16, "Key size in bytes"); 15 | DEFINE_uint64(ycsb_val_size, 120, "Value size in bytes"); 16 | DEFINE_uint64(ycsb_record_count, 10000, "The number of records to insert"); 17 | DEFINE_double(ycsb_zipf_factor, 0.99, "Zipf factor, 0 means uniform distribution"); 18 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | round: down 3 | range: 30..100 4 | precision: 2 5 | status: 6 | patch: 7 | default: 8 | target: 60% 9 | threshold: 1% 10 | base: auto 11 | project: 12 | default: 13 | target: 57% 14 | threshold: 1% 15 | base: auto 16 | 17 | ignore: 18 | - "benchmarks" 19 | - "build" 20 | - "dist" 21 | - "docker" 22 | - "docs" 23 | - "examples" 24 | - "scripts" 25 | - "tests" 26 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | 3 | ################################################################################ 4 | # install prerequisted libriaries 5 | ################################################################################ 6 | RUN apt-get update && apt-get install -y \ 7 | && rm -rf /var/lib/apt/lists/* 8 | 9 | RUN apt-get update && apt-get install -y \ 10 | build-essential g++-aarch64-linux-gnu gcc-aarch64-linux-gnu \ 11 | && rm -rf /var/lib/apt/lists/* 12 | 13 | RUN apt-get update && apt-get install -y \ 14 | git cmake make gcc g++ ninja-build \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | RUN apt-get update && apt-get install -y \ 18 | libaio-dev python3-pip cppcheck \ 19 | && rm -rf /var/lib/apt/lists/* 20 | 21 | 22 | # install vcpkg and its prerequisites 23 | RUN apt-get update && apt-get install -y \ 24 | curl zip unzip tar pkg-config \ 25 | && rm -rf /var/lib/apt/lists/* 26 | 27 | RUN git clone https://github.com/Microsoft/vcpkg.git /opt/vcpkg \ 28 | && echo 'export VCPKG_ROOT=/opt/vcpkg' >> ~/.bashrc \ 29 | && echo 'export PATH=$VCPKG_ROOT:$PATH' >> ~/.bashrc \ 30 | && echo 'export VCPKG_FORCE_SYSTEM_BINARIES=1' >> ~/.bashrc 31 | 32 | RUN mv /opt/vcpkg/triplets/x64-linux.cmake /opt/vcpkg/triplets/x64-linux-static.cmake 33 | COPY vcpkg-triplets/x64-linux.cmake /opt/vcpkg/triplets/x64-linux.cmake 34 | COPY vcpkg-triplets/arm64-linux.cmake /opt/vcpkg/triplets/arm64-linux.cmake 35 | 36 | RUN apt-get update && apt-get install -y \ 37 | autoconf libtool golang graphviz sysstat \ 38 | && rm -rf /var/lib/apt/lists/* 39 | 40 | 41 | ################################################################################ 42 | # other settings 43 | ################################################################################ 44 | USER root 45 | WORKDIR /root 46 | CMD ["/usr/bin/bash"] 47 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | ## LeanStore Build Image 2 | 3 | This is the Dockerfile to construct the build image for LeanStore. 4 | 5 | ## How to use 6 | 7 | Build the docker image: 8 | 9 | ```sh 10 | docker build -t leanstore-dev . 11 | ``` 12 | 13 | Run a container based on the image: 14 | 15 | ```sh 16 | docker run -it --privileged --network=host -v /path/to/leanstore/on/host:/path/to/leanstore/on/container leanstore-dev bash 17 | ``` 18 | 19 | Build and test LeanStore in the running container: 20 | 21 | ```sh 22 | cd /path/to/leanstore/on/container 23 | cmake --preset debug 24 | cmake --build build/debug -j `nproc` 25 | ctest --test-dir build/debug 26 | ``` 27 | 28 | -------------------------------------------------------------------------------- /docker/vcpkg-triplets/arm64-linux.cmake: -------------------------------------------------------------------------------- 1 | set(VCPKG_TARGET_ARCHITECTURE arm64) 2 | set(VCPKG_CRT_LINKAGE dynamic) 3 | set(VCPKG_LIBRARY_LINKAGE dynamic) 4 | 5 | set(VCPKG_CMAKE_SYSTEM_NAME Linux) 6 | 7 | -------------------------------------------------------------------------------- /docker/vcpkg-triplets/x64-linux.cmake: -------------------------------------------------------------------------------- 1 | set(VCPKG_TARGET_ARCHITECTURE x64) 2 | set(VCPKG_CRT_LINKAGE dynamic) 3 | set(VCPKG_LIBRARY_LINKAGE dynamic) 4 | 5 | set(VCPKG_CMAKE_SYSTEM_NAME Linux) 6 | 7 | -------------------------------------------------------------------------------- /docs/images/Architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zz-jason/leanstore/1b97886e73e41ae58f4beac49a80e087b15b3545/docs/images/Architecture.jpg -------------------------------------------------------------------------------- /examples/c/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Example for using the library 3 | # ------------------------------------------------------------------------------ 4 | 5 | cmake_minimum_required(VERSION 3.10) 6 | project(leanstore-examples C) 7 | 8 | # Set gcc standard to c11 9 | set(CMAKE_C_STANDARD 11) 10 | 11 | # Set the leanstore include directory 12 | option(LEANSTORE_INCLUDE_DIR "Path to the leanstore include directory" "") 13 | message(STATUS "LEANSTORE_INCLUDE_DIR: ${LEANSTORE_INCLUDE_DIR}") 14 | 15 | # Set the leanstore library directory, add it to the linker search path 16 | option(LEANSTORE_LIBRARY_DIR "Path to the leanstore library directory" "") 17 | message(STATUS "LEANSTORE_LIBRARY_DIR: ${LEANSTORE_LIBRARY_DIR}") 18 | link_directories(${LEANSTORE_LIBRARY_DIR}) 19 | 20 | # Rules to build BasicKvExample 21 | add_executable(BasicKvExample BasicKvExample.c) 22 | target_link_libraries(BasicKvExample leanstore) 23 | target_include_directories(BasicKvExample PUBLIC ${LEANSTORE_INCLUDE_DIR}) 24 | -------------------------------------------------------------------------------- /examples/c/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## 1. Build the leanstore library 4 | 5 | ```sh 6 | cd $LEANSTORE_HOME 7 | cmake --preset=debug 8 | cmake --build build/debug -j `nproc` 9 | cmake --install build/debug 10 | ``` 11 | 12 | The leanstore library should be found in `$LEANSTORE/dist/debug` after the above commands. 13 | 14 | ## 2. Build the example 15 | 16 | ```sh 17 | cd $LEANSTORE_HOME/examples/c 18 | 19 | # generate build files with leanstore library 20 | cmake -B build -S . \ 21 | -DLEANSTORE_INCLUDE_DIR=$LEANSTORE_HOME/dist/debug/include \ 22 | -DLEANSTORE_LIBRARY_DIR=$LEANSTORE_HOME/dist/debug/lib 23 | 24 | # build the example 25 | cmake --build build -j `nproc` 26 | 27 | # run the example 28 | ./build/BasicKvExample 29 | ``` 30 | 31 | Or you can directly build the example: 32 | 33 | ```sh 34 | cd $LEANSTORE_HOME/examples/c 35 | 36 | # build with leanstore library 37 | gcc -o basickv-example BasicKvExample.c \ 38 | -L$LEANSTORE_HOME/dist/debug/lib -lleanstore -lstdc++ \ 39 | -I$LEANSTORE_HOME/dist/debug/include 40 | 41 | # run with LD_LIBRARY_PATH set to the leanstore library path 42 | LD_LIBRARY_PATH=$LEANSTORE_HOME/dist/debug/lib ./basickv-example 43 | ``` -------------------------------------------------------------------------------- /examples/c/basic_kv_example.c: -------------------------------------------------------------------------------- 1 | #include "leanstore-c/kv_basic.h" 2 | #include "leanstore-c/leanstore.h" 3 | #include "leanstore-c/store_option.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | int main() { 11 | struct StoreOption* option = CreateStoreOption("/tmp/leanstore/examples/BasicKvExample"); 12 | option->create_from_scratch_ = 1; 13 | option->worker_threads_ = 2; 14 | option->enable_bulk_insert_ = 0; 15 | option->enable_eager_gc_ = 1; 16 | LeanStoreHandle* store_handle = CreateLeanStore(option); 17 | BasicKvHandle* kv_handle = CreateBasicKv(store_handle, 0, "testTree1"); 18 | if (kv_handle == NULL) { 19 | DestroyStoreOption(option); 20 | printf("create basic kv failed\n"); 21 | return -1; 22 | } 23 | 24 | // key-value pair 1 25 | StringSlice key_slice; 26 | key_slice.data_ = "Hello"; 27 | key_slice.size_ = strlen(key_slice.data_); 28 | 29 | StringSlice val_slice; 30 | val_slice.data_ = "World"; 31 | val_slice.size_ = strlen(val_slice.data_); 32 | 33 | // key-value pair 2 34 | StringSlice key_slice2; 35 | key_slice2.data_ = "Hello2"; 36 | key_slice2.size_ = strlen(key_slice2.data_); 37 | 38 | StringSlice val_slice2; 39 | val_slice2.data_ = "World2"; 40 | val_slice2.size_ = strlen(val_slice2.data_); 41 | 42 | { 43 | // insert a key value 44 | if (!BasicKvInsert(kv_handle, 0, key_slice, val_slice)) { 45 | printf("insert value failed, key=%.*s, val=%.*s\n", (int)key_slice.size_, key_slice.data_, 46 | (int)val_slice.size_, val_slice.data_); 47 | return -1; 48 | } 49 | } 50 | 51 | // lookup a key 52 | { 53 | String* val = CreateString(nullptr, 0); 54 | bool found = BasicKvLookup(kv_handle, 1, key_slice, &val); 55 | if (!found) { 56 | printf("lookup value failed, value may not exist, key=%.*s\n", (int)key_slice.size_, 57 | key_slice.data_); 58 | DestroyString(val); 59 | return -1; 60 | } 61 | printf("%.*s, %.*s\n", (int)key_slice.size_, key_slice.data_, (int)val->size_, val->data_); 62 | DestroyString(val); 63 | } 64 | 65 | // insert more key-values 66 | { 67 | if (!BasicKvInsert(kv_handle, 0, key_slice2, val_slice2)) { 68 | printf("insert value failed, key=%.*s, val=%.*s\n", (int)key_slice2.size_, key_slice2.data_, 69 | (int)val_slice2.size_, val_slice2.data_); 70 | return -1; 71 | } 72 | } 73 | 74 | // assending iteration 75 | { 76 | BasicKvIterHandle* iter_handle = CreateBasicKvIter(kv_handle); 77 | if (iter_handle == NULL) { 78 | printf("create iterator failed\n"); 79 | return -1; 80 | } 81 | 82 | for (BasicKvIterSeekToFirst(iter_handle, 0); BasicKvIterValid(iter_handle); 83 | BasicKvIterNext(iter_handle, 0)) { 84 | StringSlice key = BasicKvIterKey(iter_handle); 85 | StringSlice val = BasicKvIterVal(iter_handle); 86 | printf("%.*s, %.*s\n", (int)key.size_, key.data_, (int)val.size_, val.data_); 87 | } 88 | 89 | // destroy the iterator 90 | DestroyBasicKvIter(iter_handle); 91 | } 92 | 93 | // descending iteration 94 | { 95 | BasicKvIterHandle* iter_handle = CreateBasicKvIter(kv_handle); 96 | if (iter_handle == NULL) { 97 | printf("create iterator failed\n"); 98 | return -1; 99 | } 100 | 101 | for (BasicKvIterSeekToLast(iter_handle, 0); BasicKvIterValid(iter_handle); 102 | BasicKvIterPrev(iter_handle, 0)) { 103 | StringSlice key = BasicKvIterKey(iter_handle); 104 | StringSlice val = BasicKvIterVal(iter_handle); 105 | printf("%.*s, %.*s\n", (int)key.size_, key.data_, (int)val.size_, val.data_); 106 | } 107 | 108 | // destroy the iterator 109 | DestroyBasicKvIter(iter_handle); 110 | } 111 | 112 | // remove key-values 113 | { 114 | if (!BasicKvRemove(kv_handle, 0, key_slice)) { 115 | printf("remove value failed, key=%.*s\n", (int)key_slice.size_, key_slice.data_); 116 | return -1; 117 | } 118 | 119 | if (!BasicKvRemove(kv_handle, 0, key_slice2)) { 120 | printf("remove value failed, key=%.*s\n", (int)key_slice2.size_, key_slice2.data_); 121 | return -1; 122 | } 123 | } 124 | 125 | // cleanup the basic kv handle 126 | DestroyBasicKv(kv_handle); 127 | 128 | // cleanup the store handle 129 | DestroyLeanStore(store_handle); 130 | } -------------------------------------------------------------------------------- /examples/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Example for using the library 3 | # ------------------------------------------------------------------------------ 4 | 5 | cmake_minimum_required(VERSION 3.10) 6 | project(leanstore-examples CXX) 7 | 8 | # Set gcc standard to c++23 9 | set(CMAKE_CXX_STANDARD 23) 10 | 11 | # Set the leanstore include directory 12 | option(LEANSTORE_INCLUDE_DIR "Path to the leanstore include directory" "") 13 | message(STATUS "LEANSTORE_INCLUDE_DIR: ${LEANSTORE_INCLUDE_DIR}") 14 | 15 | # Set the leanstore library directory, add it to the linker search path 16 | option(LEANSTORE_LIBRARY_DIR "Path to the leanstore library directory" "") 17 | message(STATUS "LEANSTORE_LIBRARY_DIR: ${LEANSTORE_LIBRARY_DIR}") 18 | link_directories(${LEANSTORE_LIBRARY_DIR}) 19 | 20 | # Rules to build BasicKvExample 21 | add_executable(BasicKvExample BasicKvExample.cpp) 22 | target_link_libraries(BasicKvExample leanstore) 23 | target_include_directories(BasicKvExample PUBLIC ${LEANSTORE_INCLUDE_DIR}) 24 | -------------------------------------------------------------------------------- /examples/cpp/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## 1. Build the leanstore library 4 | 5 | ```sh 6 | cd $LEANSTORE_HOME 7 | cmake --preset=debug 8 | cmake --build build/debug -j `nproc` 9 | cmake --install build/debug 10 | ``` 11 | 12 | The leanstore library should be found in `$LEANSTORE/dist/debug` after the above commands. 13 | 14 | ## 2. Build the example 15 | 16 | ```sh 17 | cd $LEANSTORE_HOME/examples/cpp 18 | 19 | # generate build files with leanstore library 20 | cmake -B build -S . \ 21 | -DLEANSTORE_INCLUDE_DIR=$LEANSTORE_HOME/dist/debug/include \ 22 | -DLEANSTORE_LIBRARY_DIR=$LEANSTORE_HOME/dist/debug/lib 23 | 24 | # build the example 25 | cmake --build build -j `nproc` 26 | 27 | # run the example 28 | ./build/BasicKvExample 29 | ``` -------------------------------------------------------------------------------- /examples/cpp/basic_kv_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | using leanstore::LeanStore; 10 | 11 | int main() { 12 | // create store option 13 | StoreOption* option = CreateStoreOption("/tmp/leanstore/examples/BasicKvExample"); 14 | option->create_from_scratch_ = true; 15 | option->worker_threads_ = 2; 16 | option->enable_bulk_insert_ = false; 17 | option->enable_eager_gc_ = true; 18 | 19 | // create store 20 | auto res = LeanStore::Open(option); 21 | if (!res) { 22 | DestroyStoreOption(option); 23 | std::cerr << "open store failed: " << res.error().ToString() << std::endl; 24 | return 1; 25 | } 26 | std::unique_ptr store = std::move(res.value()); 27 | 28 | // create btree 29 | std::string btree_name = "testTree1"; 30 | leanstore::storage::btree::BasicKV* btree = nullptr; 31 | store->ExecSync(0, [&]() { 32 | auto res = store->CreateBasicKv(btree_name); 33 | if (!res) { 34 | std::cerr << "create btree failed: " << res.error().ToString() << std::endl; 35 | } 36 | btree = res.value(); 37 | }); 38 | 39 | // insert value 40 | store->ExecSync(0, [&]() { 41 | std::string key("hello"), val("world"); 42 | auto ret = btree->Insert(key, val); 43 | if (ret != leanstore::OpCode::kOK) { 44 | std::cerr << "insert value failed: " << leanstore::ToString(ret) << std::endl; 45 | } 46 | }); 47 | 48 | // lookup value 49 | store->ExecSync(0, [&]() { 50 | std::string copied_value; 51 | std::string key("hello"); 52 | auto copy_value_out = [&](leanstore::Slice val) { copied_value = val.ToString(); }; 53 | auto ret = btree->Lookup(key, copy_value_out); 54 | if (ret != leanstore::OpCode::kOK) { 55 | std::cerr << "lookup value failed: " << leanstore::ToString(ret) << std::endl; 56 | } 57 | std::cout << key << ", " << copied_value << std::endl; 58 | }); 59 | 60 | return 0; 61 | } -------------------------------------------------------------------------------- /include/leanstore-c/kv_txn.h: -------------------------------------------------------------------------------- 1 | #ifndef LEANSTORE_C_KV_TXN_H 2 | #define LEANSTORE_C_KV_TXN_H 3 | 4 | #include "leanstore-c/leanstore.h" 5 | 6 | #include 7 | #include 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | //------------------------------------------------------------------------------ 14 | // TxnKv API 15 | //------------------------------------------------------------------------------ 16 | 17 | typedef struct TxnKvHandle TxnKvHandle; 18 | 19 | /// Create a basic key-value store in a leanstore instance at workerId 20 | /// @return the basic key-value store handle, or nullptr if the creation fails. The handle should be 21 | /// destroyed by the caller with DestroyTxnKv() 22 | TxnKvHandle* CreateTxnKv(LeanStoreHandle* handle, uint64_t worker_id, const char* btree_name); 23 | 24 | /// Destroy the basic key-value store handle 25 | void DestroyTxnKv(TxnKvHandle* handle); 26 | 27 | /// Insert a key-value pair into a basic key-value store at workerId 28 | /// @return true if the insert is successful, false otherwise 29 | bool TxnKvInsert(TxnKvHandle* handle, uint64_t worker_id, StringSlice key, StringSlice val); 30 | 31 | /// Lookup a key in a basic key-value store at workerId 32 | /// @return whether the value exists, The input val is untouched if the key is not found 33 | bool TxnKvLookup(TxnKvHandle* handle, uint64_t worker_id, StringSlice key, String** val); 34 | 35 | /// Remove a key in a basic key-value store at workerId 36 | /// @return true if the key is found and removed, false otherwise 37 | bool TxnKvRemove(TxnKvHandle* handle, uint64_t worker_id, StringSlice key); 38 | 39 | /// Get the size of a basic key-value store at workerId 40 | /// @return the number of entries in the basic key-value store 41 | uint64_t TxnKvNumEntries(TxnKvHandle* handle, uint64_t worker_id); 42 | 43 | #ifdef __cplusplus 44 | } 45 | #endif 46 | 47 | #endif // LEANSTORE_C_KV_TXN_H -------------------------------------------------------------------------------- /include/leanstore-c/leanstore.h: -------------------------------------------------------------------------------- 1 | #ifndef LEANSTORE_C_H 2 | #define LEANSTORE_C_H 3 | 4 | #include "leanstore-c/store_option.h" 5 | 6 | #include 7 | #include 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | //------------------------------------------------------------------------------ 14 | // String API 15 | //------------------------------------------------------------------------------ 16 | 17 | /// String is a data structure that holds an owned bytes buffer 18 | typedef struct String { 19 | /// The owned data pointer 20 | char* data_; 21 | 22 | /// The size of the data 23 | uint64_t size_; 24 | 25 | /// The capacity of the data 26 | uint64_t capacity_; 27 | } String; 28 | 29 | /// Creates a new string, copying the data from the given buffer to the new string 30 | /// @param data the data buffer 31 | /// @param size the size of the data buffer 32 | /// @return the new string, which should be destroyed by the caller with DestroyString() 33 | String* CreateString(const char* data, uint64_t size); 34 | 35 | /// Destroys a string 36 | void DestroyString(String* str); 37 | 38 | //------------------------------------------------------------------------------ 39 | // StringSlice API 40 | //------------------------------------------------------------------------------ 41 | 42 | /// StringSlice is a read-only data structure that holds a slice of a bytes buffer 43 | typedef struct StringSlice { 44 | /// The read-only data pointer 45 | const char* data_; 46 | 47 | /// The size of the data 48 | uint64_t size_; 49 | } StringSlice; 50 | 51 | //------------------------------------------------------------------------------ 52 | // LeanStore API 53 | //------------------------------------------------------------------------------ 54 | 55 | typedef struct LeanStoreHandle LeanStoreHandle; 56 | 57 | /// Create and init a leanstore instance 58 | LeanStoreHandle* CreateLeanStore(StoreOption* option); 59 | 60 | /// Deinit and destroy a leanstore instance 61 | void DestroyLeanStore(LeanStoreHandle* handle); 62 | 63 | //------------------------------------------------------------------------------ 64 | // Interfaces for metrics 65 | //------------------------------------------------------------------------------ 66 | 67 | /// Start the global http metrics exposer 68 | void StartMetricsHttpExposer(int32_t port); 69 | 70 | /// Stop the global http metrics exposer 71 | void StopMetricsHttpExposer(); 72 | 73 | #ifdef __cplusplus 74 | } 75 | #endif 76 | 77 | #endif // LEANSTORE_C_H -------------------------------------------------------------------------------- /include/leanstore-c/perf_counters.h: -------------------------------------------------------------------------------- 1 | #ifndef LEANSTORE_C_PERF_COUNTERS_H 2 | #define LEANSTORE_C_PERF_COUNTERS_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | /// The counter type. 11 | typedef atomic_ullong CounterType; 12 | 13 | /// The performance counters for each worker. 14 | typedef struct PerfCounters { 15 | 16 | // --------------------------------------------------------------------------- 17 | // Transaction related counters 18 | // --------------------------------------------------------------------------- 19 | 20 | /// The number of transactions committed. 21 | CounterType tx_committed_; 22 | 23 | /// The number of transactions commit wait. 24 | CounterType tx_commit_wait_; 25 | 26 | /// The number of transactions aborted. 27 | CounterType tx_aborted_; 28 | 29 | //// The number of transactions with remote dependencies. 30 | CounterType tx_with_remote_dependencies_; 31 | 32 | /// The number of transactions without remote dependencies. 33 | CounterType tx_without_remote_dependencies_; 34 | 35 | /// The number of short running transactions. 36 | CounterType tx_short_running_; 37 | 38 | /// The number of long running transactions. 39 | CounterType tx_long_running_; 40 | 41 | // --------------------------------------------------------------------------- 42 | // MVCC concurrency control related counters 43 | // --------------------------------------------------------------------------- 44 | 45 | /// The number of LCB query executed. 46 | CounterType lcb_executed_; 47 | 48 | /// The total latency of LCB query in nanoseconds. 49 | CounterType lcb_total_lat_ns_; 50 | 51 | // --------------------------------------------------------------------------- 52 | // MVCC garbage collection related counters 53 | // --------------------------------------------------------------------------- 54 | 55 | /// The number of MVCC garbage collection executed. 56 | CounterType gc_executed_; 57 | 58 | /// The total latency of MVCC garbage collection in nanoseconds. 59 | CounterType gc_total_lat_ns_; 60 | 61 | // --------------------------------------------------------------------------- 62 | // Contention split related counters 63 | // --------------------------------------------------------------------------- 64 | 65 | /// The number of contention split succeed. 66 | CounterType contention_split_succeed_; 67 | 68 | /// The number of contention split failed. 69 | CounterType contention_split_failed_; 70 | 71 | /// The number of normal split succeed. 72 | CounterType split_succeed_; 73 | 74 | /// The number of normal split failed. 75 | CounterType split_failed_; 76 | 77 | } PerfCounters; 78 | 79 | #ifdef __cplusplus 80 | } 81 | #endif 82 | 83 | #endif // LEANSTORE_C_PERF_COUNTERS_H -------------------------------------------------------------------------------- /include/leanstore/btree/basic_kv.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/btree/core/b_tree_generic.hpp" 4 | #include "leanstore/kv_interface.hpp" 5 | 6 | namespace leanstore { 7 | 8 | class LeanStore; 9 | 10 | } // namespace leanstore 11 | 12 | namespace leanstore::storage::btree { 13 | 14 | class BasicKV : public KVInterface, public BTreeGeneric { 15 | public: 16 | BasicKV() { 17 | tree_type_ = BTreeType::kBasicKV; 18 | } 19 | 20 | virtual OpCode Lookup(Slice key, ValCallback val_callback) override; 21 | 22 | virtual OpCode Insert(Slice key, Slice val) override; 23 | 24 | virtual OpCode UpdatePartial(Slice key, MutValCallback update_call_back, 25 | UpdateDesc& update_desc) override; 26 | 27 | virtual OpCode Remove(Slice key) override; 28 | 29 | virtual OpCode ScanAsc(Slice start_key, ScanCallback callback) override; 30 | 31 | virtual OpCode ScanDesc(Slice start_key, ScanCallback callback) override; 32 | 33 | virtual OpCode PrefixLookup(Slice, PrefixLookupCallback callback) override; 34 | 35 | virtual OpCode PrefixLookupForPrev(Slice key, PrefixLookupCallback callback) override; 36 | 37 | virtual OpCode RangeRemove(Slice start_key, Slice end_key, bool page_used) override; 38 | 39 | virtual uint64_t CountEntries() override; 40 | 41 | bool IsRangeEmpty(Slice start_key, Slice end_key); 42 | 43 | static Result Create(leanstore::LeanStore* store, const std::string& tree_name, 44 | BTreeConfig config); 45 | 46 | /// Copy the slots from the value to the buffer. 47 | /// 48 | /// @param[in] updateDesc The update descriptor which contains the slots to 49 | /// update. 50 | /// @param[in] value The value to copy the slots from. 51 | /// @param[out] buffer The buffer to copy the slots to. 52 | static void CopyToBuffer(const UpdateDesc& update_desc, const uint8_t* value, uint8_t* buffer) { 53 | uint64_t buffer_offset = 0; 54 | for (uint64_t i = 0; i < update_desc.num_slots_; i++) { 55 | const auto& slot = update_desc.update_slots_[i]; 56 | std::memcpy(buffer + buffer_offset, value + slot.offset_, slot.size_); 57 | buffer_offset += slot.size_; 58 | } 59 | } 60 | 61 | /// Update the slots in the value with data in the buffer. 62 | /// 63 | /// @param[in] updateDesc The update descriptor which contains the slots to 64 | /// update. 65 | /// @param[in] buffer The buffer to copy the slots from. 66 | /// @param[out] value The value to update the slots in. 67 | static void CopyToValue(const UpdateDesc& update_desc, const uint8_t* buffer, uint8_t* value) { 68 | uint64_t buffer_offset = 0; 69 | for (uint64_t i = 0; i < update_desc.num_slots_; i++) { 70 | const auto& slot = update_desc.update_slots_[i]; 71 | std::memcpy(value + slot.offset_, buffer + buffer_offset, slot.size_); 72 | buffer_offset += slot.size_; 73 | } 74 | } 75 | 76 | static void XorToBuffer(const UpdateDesc& update_desc, const uint8_t* value, uint8_t* buffer) { 77 | uint64_t buffer_offset = 0; 78 | for (uint64_t i = 0; i < update_desc.num_slots_; i++) { 79 | const auto& slot = update_desc.update_slots_[i]; 80 | for (uint64_t j = 0; j < slot.size_; j++) { 81 | buffer[buffer_offset + j] ^= value[slot.offset_ + j]; 82 | } 83 | buffer_offset += slot.size_; 84 | } 85 | } 86 | 87 | static void XorToValue(const UpdateDesc& update_desc, const uint8_t* buffer, uint8_t* value) { 88 | uint64_t buffer_offset = 0; 89 | for (uint64_t i = 0; i < update_desc.num_slots_; i++) { 90 | const auto& slot = update_desc.update_slots_[i]; 91 | for (uint64_t j = 0; j < slot.size_; j++) { 92 | value[slot.offset_ + j] ^= buffer[buffer_offset + j]; 93 | } 94 | buffer_offset += slot.size_; 95 | } 96 | } 97 | 98 | private: 99 | OpCode lookup_optimistic(Slice key, ValCallback val_callback); 100 | OpCode lookup_pessimistic(Slice key, ValCallback val_callback); 101 | }; 102 | 103 | } // namespace leanstore::storage::btree 104 | -------------------------------------------------------------------------------- /include/leanstore/btree/chained_tuple.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/btree/basic_kv.hpp" 4 | #include "leanstore/btree/core/pessimistic_exclusive_iterator.hpp" 5 | #include "leanstore/concurrency/cr_manager.hpp" 6 | #include "leanstore/concurrency/worker_context.hpp" 7 | #include "leanstore/units.hpp" 8 | #include "tuple.hpp" 9 | 10 | namespace leanstore::storage::btree { 11 | 12 | /// History versions of chained tuple are stored in the history tree of the 13 | /// current worker thread. 14 | /// Chained: only scheduled gc. 15 | class __attribute__((packed)) ChainedTuple : public Tuple { 16 | public: 17 | uint16_t total_updates_ = 0; 18 | 19 | uint16_t oldest_tx_ = 0; 20 | 21 | uint8_t is_tombstone_ = 1; 22 | 23 | // latest version in-place 24 | uint8_t payload_[]; 25 | 26 | public: 27 | /// Construct a ChainedTuple, copy the value to its payload 28 | /// 29 | /// NOTE: Payload space should be allocated in advance. This constructor is 30 | /// usually called by a placmenet new operator. 31 | ChainedTuple(WORKERID worker_id, TXID tx_id, Slice val) 32 | : Tuple(TupleFormat::kChained, worker_id, tx_id), 33 | is_tombstone_(false) { 34 | std::memcpy(payload_, val.data(), val.size()); 35 | } 36 | 37 | ChainedTuple(WORKERID worker_id, TXID tx_id, COMMANDID command_id, Slice val) 38 | : Tuple(TupleFormat::kChained, worker_id, tx_id, command_id), 39 | is_tombstone_(false) { 40 | std::memcpy(payload_, val.data(), val.size()); 41 | } 42 | 43 | /// Construct a ChainedTuple from an existing FatTuple, the new ChainedTuple 44 | /// may share the same space with the input FatTuple, so std::memmove is 45 | /// used to handle the overlap bytes. 46 | /// 47 | /// NOTE: This constructor is usually called by a placmenet new operator on 48 | /// the address of the FatTuple 49 | ChainedTuple(FatTuple& old_fat_tuple) 50 | : Tuple(TupleFormat::kChained, old_fat_tuple.worker_id_, old_fat_tuple.tx_id_, 51 | old_fat_tuple.command_id_), 52 | is_tombstone_(false) { 53 | std::memmove(payload_, old_fat_tuple.payload_, old_fat_tuple.val_size_); 54 | } 55 | 56 | public: 57 | inline Slice GetValue(size_t size) const { 58 | return Slice(payload_, size); 59 | } 60 | 61 | std::tuple GetVisibleTuple(Slice payload, ValCallback callback) const; 62 | 63 | void UpdateStats() { 64 | if (cr::WorkerContext::My().cc_.VisibleForAll(tx_id_) || 65 | oldest_tx_ != 66 | static_cast( 67 | cr::WorkerContext::My().store_->crmanager_->global_wmk_info_.oldest_active_tx_ & 68 | 0xFFFF)) { 69 | oldest_tx_ = 0; 70 | total_updates_ = 0; 71 | return; 72 | } 73 | total_updates_++; 74 | } 75 | 76 | bool ShouldConvertToFatTuple() { 77 | bool command_valid = command_id_ != kInvalidCommandid; 78 | bool has_long_running_olap = 79 | cr::WorkerContext::My().store_->crmanager_->global_wmk_info_.HasActiveLongRunningTx(); 80 | bool frequently_updated = 81 | total_updates_ > cr::WorkerContext::My().store_->store_option_->worker_threads_; 82 | bool recent_updated_by_others = 83 | worker_id_ != cr::WorkerContext::My().worker_id_ || tx_id_ != cr::ActiveTx().start_ts_; 84 | return command_valid && has_long_running_olap && recent_updated_by_others && frequently_updated; 85 | } 86 | 87 | void Update(PessimisticExclusiveIterator& x_iter, Slice key, MutValCallback update_call_back, 88 | UpdateDesc& update_desc); 89 | 90 | public: 91 | inline static const ChainedTuple* From(const uint8_t* buffer) { 92 | return reinterpret_cast(buffer); 93 | } 94 | 95 | inline static ChainedTuple* From(uint8_t* buffer) { 96 | return reinterpret_cast(buffer); 97 | } 98 | }; 99 | 100 | } // namespace leanstore::storage::btree -------------------------------------------------------------------------------- /include/leanstore/btree/core/iterator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/slice.hpp" 4 | 5 | namespace leanstore::storage::btree { 6 | 7 | class Iterator { 8 | public: 9 | //------------------------------------------------------------------------------------------------ 10 | // Constructor and Destructor 11 | //------------------------------------------------------------------------------------------------ 12 | virtual ~Iterator() = default; 13 | 14 | //------------------------------------------------------------------------------------------------ 15 | // Interfaces for exact key seeking 16 | //------------------------------------------------------------------------------------------------ 17 | 18 | /// Seek to the position of the key which = the given key 19 | virtual void SeekToEqual(Slice key) = 0; 20 | 21 | //------------------------------------------------------------------------------------------------ 22 | // Interfaces for ascending iteration 23 | //------------------------------------------------------------------------------------------------ 24 | 25 | /// Seek to the position of the first key 26 | virtual void SeekToFirst() = 0; 27 | 28 | /// Seek to the position of the first key which >= the given key 29 | virtual void SeekToFirstGreaterEqual(Slice key) = 0; 30 | 31 | /// Whether a next key exists in the tree 32 | /// @return true if the next key exists, false otherwise 33 | virtual bool HasNext() = 0; 34 | 35 | /// Iterate to the next key in the tree 36 | virtual void Next() = 0; 37 | 38 | //------------------------------------------------------------------------------------------------ 39 | // Interfaces for descending iteration 40 | //------------------------------------------------------------------------------------------------ 41 | 42 | /// Seek to the position of the last key 43 | virtual void SeekToLast() = 0; 44 | 45 | /// Seek to the position of the last key which <= the given key 46 | virtual void SeekToLastLessEqual(Slice key) = 0; 47 | 48 | /// Whether a previous key exists in the tree 49 | /// @return true if the previous key exists, false otherwise 50 | virtual bool HasPrev() = 0; 51 | 52 | /// Iterate to the previous key in the tree 53 | virtual void Prev() = 0; 54 | 55 | //------------------------------------------------------------------------------------------------ 56 | // Interfaces for accessing the current iterator position 57 | //------------------------------------------------------------------------------------------------ 58 | 59 | /// Whether the iterator is valid 60 | /// @return true if the iterator is pointing to a valid key-value pair, false otherwise 61 | virtual bool Valid() = 0; 62 | 63 | /// Get the key of the current iterator position, the key is read-only 64 | virtual Slice Key() = 0; 65 | 66 | /// Get the value of the current iterator position, the value is read-only 67 | virtual Slice Val() = 0; 68 | }; 69 | 70 | } // namespace leanstore::storage::btree 71 | -------------------------------------------------------------------------------- /include/leanstore/btree/core/pessimistic_shared_iterator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pessimistic_iterator.hpp" 4 | 5 | namespace leanstore::storage::btree { 6 | 7 | class PessimisticSharedIterator : public PessimisticIterator { 8 | public: 9 | PessimisticSharedIterator(BTreeGeneric& btree) 10 | : PessimisticIterator(btree, LatchMode::kPessimisticShared) { 11 | } 12 | }; 13 | 14 | } // namespace leanstore::storage::btree 15 | -------------------------------------------------------------------------------- /include/leanstore/btree/transaction_kv.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/btree/basic_kv.hpp" 4 | #include "leanstore/btree/chained_tuple.hpp" 5 | #include "leanstore/btree/core/b_tree_generic.hpp" 6 | #include "leanstore/btree/core/pessimistic_exclusive_iterator.hpp" 7 | #include "leanstore/btree/tuple.hpp" 8 | #include "leanstore/buffer-manager/guarded_buffer_frame.hpp" 9 | #include "leanstore/concurrency/worker_context.hpp" 10 | #include "leanstore/kv_interface.hpp" 11 | #include "leanstore/units.hpp" 12 | #include "leanstore/utils/result.hpp" 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | /// forward declarations 19 | namespace leanstore { 20 | 21 | class LeanStore; 22 | 23 | } // namespace leanstore 24 | 25 | /// forward declarations 26 | namespace leanstore::storage::btree { 27 | 28 | class WalTxInsert; 29 | class WalTxUpdate; 30 | class WalTxRemove; 31 | 32 | } // namespace leanstore::storage::btree 33 | 34 | namespace leanstore::storage::btree { 35 | 36 | // Assumptions made in this implementation: 37 | // 1. We don't insert an already removed key 38 | // 2. Secondary Versions contain delta 39 | // 40 | // Keep in mind that garbage collection may leave pages completely empty 41 | // Missing points: FatTuple::remove, garbage leaves can escape from us 42 | class TransactionKV : public BasicKV { 43 | public: 44 | /// Graveyard to store removed tuples for long-running transactions. 45 | BasicKV* graveyard_; 46 | 47 | TransactionKV() { 48 | tree_type_ = BTreeType::kTransactionKV; 49 | } 50 | 51 | OpCode Lookup(Slice key, ValCallback val_callback) override; 52 | 53 | OpCode ScanAsc(Slice start_key, ScanCallback) override; 54 | 55 | OpCode ScanDesc(Slice start_key, ScanCallback) override; 56 | 57 | OpCode Insert(Slice key, Slice val) override; 58 | 59 | OpCode UpdatePartial(Slice key, MutValCallback update_call_back, 60 | UpdateDesc& update_desc) override; 61 | 62 | OpCode Remove(Slice key) override; 63 | 64 | void Init(leanstore::LeanStore* store, TREEID tree_id, BTreeConfig config, BasicKV* graveyard); 65 | 66 | SpaceCheckResult CheckSpaceUtilization(BufferFrame& bf) override; 67 | 68 | // This undo implementation works only for rollback and not for undo 69 | // operations during recovery 70 | void undo(const uint8_t* wal_entry_ptr, const uint64_t) override; 71 | 72 | void GarbageCollect(const uint8_t* entry_ptr, WORKERID version_worker_id, TXID version_tx_id, 73 | bool called_before) override; 74 | 75 | void unlock(const uint8_t* wal_entry_ptr) override; 76 | 77 | private: 78 | OpCode lookup_optimistic(Slice key, ValCallback val_callback); 79 | 80 | template 81 | OpCode scan4ShortRunningTx(Slice key, ScanCallback callback); 82 | 83 | template 84 | OpCode scan4LongRunningTx(Slice key, ScanCallback callback); 85 | 86 | inline static bool trigger_page_wise_garbage_collection( 87 | GuardedBufferFrame& guarded_node) { 88 | return guarded_node->has_garbage_; 89 | } 90 | 91 | std::tuple get_visible_tuple(Slice payload, ValCallback callback); 92 | 93 | void insert_after_remove(PessimisticExclusiveIterator& x_iter, Slice key, Slice val); 94 | 95 | void undo_last_insert(const WalTxInsert* wal_insert); 96 | 97 | void undo_last_update(const WalTxUpdate* wal_update); 98 | 99 | void undo_last_remove(const WalTxRemove* wal_remove); 100 | 101 | public: 102 | static Result Create(leanstore::LeanStore* store, const std::string& tree_name, 103 | BTreeConfig config, BasicKV* graveyard); 104 | 105 | inline static void InsertToNode(GuardedBufferFrame& guarded_node, Slice key, Slice val, 106 | WORKERID worker_id, TXID tx_start_ts, int32_t& slot_id) { 107 | auto total_val_size = sizeof(ChainedTuple) + val.size(); 108 | slot_id = guarded_node->InsertDoNotCopyPayload(key, total_val_size, slot_id); 109 | auto* tuple_addr = guarded_node->ValData(slot_id); 110 | new (tuple_addr) ChainedTuple(worker_id, tx_start_ts, val); 111 | } 112 | 113 | inline static uint64_t ConvertToFatTupleThreshold() { 114 | return cr::WorkerContext::My().store_->store_option_->worker_threads_; 115 | } 116 | 117 | /// Updates the value stored in FatTuple. The former newest version value is 118 | /// moved to the tail. 119 | /// @return false to fallback to chained mode 120 | static bool UpdateInFatTuple(PessimisticExclusiveIterator& x_iter, Slice key, 121 | MutValCallback update_call_back, UpdateDesc& update_desc); 122 | }; 123 | 124 | } // namespace leanstore::storage::btree 125 | -------------------------------------------------------------------------------- /include/leanstore/buffer-manager/async_write_buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/buffer-manager/buffer_frame.hpp" 4 | #include "leanstore/units.hpp" 5 | #include "leanstore/utils/async_io.hpp" 6 | #include "leanstore/utils/misc.hpp" 7 | #include "leanstore/utils/result.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace leanstore::storage { 16 | 17 | /// A batched asynchronous writer for buffer frames. It batches writes to the 18 | /// disk to reduce the number of syscalls. 19 | /// Typical usage: 20 | /// 21 | /// AsyncWriteBuffer writeBuffer(fd, pageSize, maxBatchSize); 22 | /// while (!IsFull()) { 23 | /// writeBuffer.Add(bf, pageId); 24 | /// } 25 | /// writeBuffer.SubmitAll(); 26 | /// writeBuffer.WaitAll(); 27 | /// writeBuffer.IterateFlushedBfs([](BufferFrame& flushedBf, uint64_t 28 | /// flushedPsn) { 29 | /// // do something with flushedBf 30 | /// }, numFlushedBfs); 31 | /// 32 | class AsyncWriteBuffer { 33 | private: 34 | struct WriteCommand { 35 | const BufferFrame* bf_; 36 | PID page_id_; 37 | 38 | void Reset(const BufferFrame* bf, PID page_id) { 39 | bf_ = bf; 40 | page_id_ = page_id; 41 | } 42 | }; 43 | 44 | int fd_; 45 | uint64_t page_size_; 46 | utils::AsyncIo aio_; 47 | 48 | utils::AlignedBuffer<512> write_buffer_; 49 | std::vector write_commands_; 50 | 51 | public: 52 | AsyncWriteBuffer(int fd, uint64_t page_size, uint64_t max_batch_size); 53 | 54 | ~AsyncWriteBuffer(); 55 | 56 | /// Check if the write buffer is full 57 | bool IsFull(); 58 | 59 | /// Add a buffer frame to the write buffer: 60 | /// - record the buffer frame to write commands for later use 61 | /// - copy the page content in buffer frame to the write buffer 62 | /// - prepare the io request 63 | void Add(const BufferFrame& bf); 64 | 65 | /// Submit the write buffer to the AIO context to be written to the disk 66 | Result SubmitAll(); 67 | 68 | /// Wait for the IO request to complete 69 | Result WaitAll(); 70 | 71 | uint64_t GetPendingRequests() { 72 | return aio_.GetNumRequests(); 73 | } 74 | 75 | void IterateFlushedBfs( 76 | std::function callback, 77 | uint64_t num_flushed_bfs); 78 | 79 | private: 80 | void* copy_to_buffer(const Page* page, size_t slot) { 81 | void* dest = get_write_buffer(slot); 82 | std::memcpy(dest, page, page_size_); 83 | return dest; 84 | } 85 | 86 | uint8_t* get_write_buffer(size_t slot) { 87 | return &write_buffer_.Get()[slot * page_size_]; 88 | } 89 | }; 90 | 91 | } // namespace leanstore::storage 92 | -------------------------------------------------------------------------------- /include/leanstore/buffer-manager/bm_plain_guard.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/sync/hybrid_guard.hpp" 4 | #include "leanstore/utils/jump_mu.hpp" 5 | 6 | namespace leanstore { 7 | namespace storage { 8 | 9 | // The following guards are primarily designed for buffer management use cases 10 | // This implies that the guards never block (sleep), they immediately jump 11 | // instead. 12 | class BMOptimisticGuard; 13 | class BMExclusiveGuard; 14 | template 15 | class GuardedBufferFrame; 16 | 17 | class BMOptimisticGuard { 18 | friend class BMExclusiveGuard; 19 | template 20 | friend class GuardedBufferFrame; 21 | template 22 | friend class ExclusiveGuardedBufferFrame; 23 | 24 | public: 25 | HybridGuard guard_; 26 | 27 | BMOptimisticGuard(HybridLatch& lock) : guard_(&lock) { 28 | guard_.ToOptimisticOrJump(); 29 | } 30 | 31 | BMOptimisticGuard() = delete; 32 | BMOptimisticGuard(BMOptimisticGuard& other) = delete; // copy constructor 33 | // move constructor 34 | BMOptimisticGuard(BMOptimisticGuard&& other) : guard_(std::move(other.guard_)) { 35 | } 36 | BMOptimisticGuard& operator=(BMOptimisticGuard& other) = delete; 37 | BMOptimisticGuard& operator=(BMOptimisticGuard&& other) { 38 | guard_ = std::move(other.guard_); 39 | return *this; 40 | } 41 | 42 | inline void JumpIfModifiedByOthers() { 43 | guard_.JumpIfModifiedByOthers(); 44 | } 45 | }; 46 | 47 | class BMExclusiveGuard { 48 | private: 49 | BMOptimisticGuard& optimistic_guard_; // our basis 50 | 51 | public: 52 | BMExclusiveGuard(BMOptimisticGuard& optimistic_guard) : optimistic_guard_(optimistic_guard) { 53 | optimistic_guard_.guard_.TryToExclusiveMayJump(); 54 | JUMPMU_PUSH_BACK_DESTRUCTOR_BEFORE_JUMP(); 55 | } 56 | 57 | JUMPMU_DEFINE_DESTRUCTOR_BEFORE_JUMP(BMExclusiveGuard) 58 | 59 | ~BMExclusiveGuard() { 60 | optimistic_guard_.guard_.Unlock(); 61 | JUMPMU_POP_BACK_DESTRUCTOR_BEFORE_JUMP(); 62 | } 63 | }; 64 | 65 | class BMExclusiveUpgradeIfNeeded { 66 | private: 67 | HybridGuard& guard_; 68 | 69 | const bool was_exclusive_; 70 | 71 | public: 72 | BMExclusiveUpgradeIfNeeded(HybridGuard& guard) 73 | : guard_(guard), 74 | was_exclusive_(guard.state_ == GuardState::kPessimisticExclusive) { 75 | guard_.TryToExclusiveMayJump(); 76 | JUMPMU_PUSH_BACK_DESTRUCTOR_BEFORE_JUMP(); 77 | } 78 | 79 | JUMPMU_DEFINE_DESTRUCTOR_BEFORE_JUMP(BMExclusiveUpgradeIfNeeded) 80 | 81 | ~BMExclusiveUpgradeIfNeeded() { 82 | if (!was_exclusive_) { 83 | guard_.Unlock(); 84 | } 85 | JUMPMU_POP_BACK_DESTRUCTOR_BEFORE_JUMP() 86 | } 87 | }; 88 | 89 | class BMSharedGuard { 90 | private: 91 | BMOptimisticGuard& optimistic_guard_; // our basis 92 | 93 | public: 94 | BMSharedGuard(BMOptimisticGuard& optimistic_guard) : optimistic_guard_(optimistic_guard) { 95 | optimistic_guard_.guard_.TryToSharedMayJump(); 96 | JUMPMU_PUSH_BACK_DESTRUCTOR_BEFORE_JUMP(); 97 | } 98 | 99 | JUMPMU_DEFINE_DESTRUCTOR_BEFORE_JUMP(BMSharedGuard) 100 | 101 | ~BMSharedGuard() { 102 | optimistic_guard_.guard_.Unlock(); 103 | JUMPMU_POP_BACK_DESTRUCTOR_BEFORE_JUMP() 104 | } 105 | }; 106 | 107 | } // namespace storage 108 | } // namespace leanstore 109 | -------------------------------------------------------------------------------- /include/leanstore/buffer-manager/free_list.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/buffer-manager/buffer_frame.hpp" 4 | #include "leanstore/utils/log.hpp" 5 | 6 | #include 7 | 8 | namespace leanstore::storage { 9 | 10 | class FreeList { 11 | public: 12 | std::mutex mutex_; 13 | BufferFrame* head_ = nullptr; 14 | std::atomic size_ = 0; 15 | 16 | public: 17 | void PushFront(BufferFrame& bf); 18 | 19 | void PushFront(BufferFrame* head, BufferFrame* tail, uint64_t size); 20 | 21 | BufferFrame& PopFrontMayJump(); 22 | }; 23 | 24 | inline void FreeList::PushFront(BufferFrame& bf) { 25 | LS_DCHECK(bf.header_.state_ == State::kFree); 26 | LS_DCHECK(!bf.header_.latch_.IsLockedExclusively()); 27 | 28 | JumpScoped> guard(mutex_); 29 | bf.header_.next_free_bf_ = head_; 30 | head_ = &bf; 31 | size_++; 32 | } 33 | 34 | inline void FreeList::PushFront(BufferFrame* head, BufferFrame* tail, uint64_t size) { 35 | JumpScoped> guard(mutex_); 36 | tail->header_.next_free_bf_ = head_; 37 | head_ = head; 38 | size_ += size; 39 | } 40 | 41 | inline BufferFrame& FreeList::PopFrontMayJump() { 42 | JumpScoped> guard(mutex_); 43 | BufferFrame* free_bf = head_; 44 | if (head_ == nullptr) { 45 | jumpmu::Jump(); 46 | } else { 47 | head_ = head_->header_.next_free_bf_; 48 | size_--; 49 | LS_DCHECK(free_bf->header_.state_ == State::kFree); 50 | } 51 | return *free_bf; 52 | } 53 | 54 | } // namespace leanstore::storage 55 | -------------------------------------------------------------------------------- /include/leanstore/buffer-manager/partition.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/buffer-manager/buffer_frame.hpp" 4 | #include "leanstore/buffer-manager/free_list.hpp" 5 | #include "leanstore/units.hpp" 6 | #include "leanstore/utils/misc.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace leanstore::storage { 13 | 14 | struct IOFrame { 15 | enum class State : uint8_t { 16 | kReading = 0, 17 | kReady = 1, 18 | kToDelete = 2, 19 | kUndefined = 3 // for debugging 20 | }; 21 | 22 | std::mutex mutex_; 23 | 24 | State state_ = State::kUndefined; 25 | 26 | BufferFrame* bf_ = nullptr; 27 | 28 | /// Everything in CIOFrame is protected by partition lock except the following 29 | /// counter which is decremented outside to determine whether it is time to 30 | /// remove it 31 | std::atomic num_readers_ = 0; 32 | }; 33 | 34 | struct HashTable { 35 | struct Entry { 36 | uint64_t key_; 37 | 38 | Entry* next_; 39 | 40 | IOFrame value_; 41 | 42 | Entry(uint64_t key); 43 | }; 44 | 45 | struct Handler { 46 | Entry** holder_; 47 | 48 | operator bool() const { 49 | return holder_ != nullptr; 50 | } 51 | 52 | IOFrame& Frame() const { 53 | assert(holder_ != nullptr); 54 | return *reinterpret_cast(&((*holder_)->value_)); 55 | } 56 | }; 57 | 58 | uint64_t mask_; 59 | 60 | Entry** entries_; 61 | 62 | uint64_t HashKey(uint64_t k); 63 | 64 | IOFrame& Insert(uint64_t key); 65 | 66 | Handler Lookup(uint64_t key); 67 | 68 | void Remove(Handler& handler); 69 | 70 | void Remove(uint64_t key); 71 | 72 | bool Has(uint64_t key); // for debugging 73 | 74 | HashTable(uint64_t size_in_bits); 75 | }; 76 | 77 | /// The I/O partition for the underlying pages. Page read/write operations are 78 | /// dispatched to partitions based on the page id. 79 | class Partition { 80 | public: 81 | /// Protects the concurrent access to inflight_ios_. 82 | std::mutex inflight_iomutex_; 83 | 84 | /// Stores all the inflight IOs in the partition. 85 | HashTable inflight_ios_; 86 | 87 | /// The maximum number of free buffer frames in the partition. 88 | const uint64_t free_bfs_limit_; 89 | 90 | /// Stores all the free buffer frames in the partition. 91 | FreeList free_bf_list_; 92 | 93 | /// Protects the concurrent access to reclaimed_page_ids_. 94 | std::mutex reclaimed_page_ids_mutex_; 95 | 96 | /// Stores all the reclaimed page ids in the partition. Page id is reclaimed 97 | /// when the page is removed. The reclaimed page id can be reused when a new 98 | /// page is allocated. 99 | std::vector reclaimed_page_ids_; 100 | 101 | /// The next page id to be allocated. 102 | uint64_t next_page_id_; 103 | 104 | /// The distance between two consecutive allocated page ids. 105 | const uint64_t page_id_distance_; 106 | 107 | public: 108 | Partition(uint64_t first_page_id, uint64_t page_id_distance, uint64_t free_bfs_limit) 109 | : inflight_ios_(utils::GetBitsNeeded(free_bfs_limit)), 110 | free_bfs_limit_(free_bfs_limit), 111 | next_page_id_(first_page_id), 112 | page_id_distance_(page_id_distance) { 113 | } 114 | 115 | /// Whether the partition needs more free buffer frames. 116 | bool NeedMoreFreeBfs() { 117 | return free_bf_list_.size_ < free_bfs_limit_; 118 | } 119 | 120 | /// Allocates a new page id. 121 | PID NextPageId() { 122 | std::unique_lock guard(reclaimed_page_ids_mutex_); 123 | if (reclaimed_page_ids_.size()) { 124 | const uint64_t page_id = reclaimed_page_ids_.back(); 125 | reclaimed_page_ids_.pop_back(); 126 | return page_id; 127 | } 128 | 129 | const uint64_t page_id = next_page_id_; 130 | next_page_id_ += page_id_distance_; 131 | return page_id; 132 | } 133 | 134 | /// Reclaims a freed page id. 135 | void ReclaimPageId(PID page_id) { 136 | std::unique_lock guard(reclaimed_page_ids_mutex_); 137 | reclaimed_page_ids_.push_back(page_id); 138 | } 139 | 140 | /// How many pages have been allocated. 141 | uint64_t NumAllocatedPages() { 142 | return next_page_id_ / page_id_distance_; 143 | } 144 | 145 | /// How many pages have been reclaimed. 146 | uint64_t NumReclaimedPages() { 147 | std::unique_lock guard(reclaimed_page_ids_mutex_); 148 | return reclaimed_page_ids_.size(); 149 | } 150 | }; 151 | 152 | } // namespace leanstore::storage 153 | -------------------------------------------------------------------------------- /include/leanstore/buffer-manager/swip.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/buffer-manager/buffer_frame.hpp" 4 | #include "leanstore/units.hpp" 5 | #include "leanstore/utils/log.hpp" 6 | 7 | namespace leanstore::storage { 8 | 9 | class BufferFrame; 10 | 11 | /// Swip represents either the page id or the pointer to the buffer frame which contains the page. 12 | /// It can be the following 3 stats: 13 | /// - EVICTED: page id, the most most significant bit is 1 which marks the swip as "EVICTED". 14 | /// - COOL: buffer frame pointer, the 2nd most most significant bit is 1 which marks it as "COOL". 15 | /// - HOT: buffer frame pointer, the 2nd most most significant bit is 0 which marks it as "HOT". 16 | /// 17 | /// i.e. 18 | /// - 1xxxxxxxxxxxx EVICTED, page id 19 | /// - 01xxxxxxxxxxx COOL, buffer frame pointer 20 | /// - 00xxxxxxxxxxx HOT, buffer frame pointer 21 | class Swip { 22 | public: 23 | /// The actual data 24 | union { 25 | uint64_t page_id_; 26 | BufferFrame* bf_; 27 | }; 28 | 29 | /// Create an empty swip. 30 | Swip() : page_id_(0) { 31 | } 32 | 33 | /// Create an swip pointing to the buffer frame. 34 | Swip(BufferFrame* bf) : bf_(bf) { 35 | } 36 | 37 | /// Whether two swip is equal. 38 | bool operator==(const Swip& other) const { 39 | return (Raw() == other.Raw()); 40 | } 41 | 42 | bool IsHot() { 43 | return (page_id_ & (sEvictedBit | sCoolBit)) == 0; 44 | } 45 | 46 | bool IsCool() { 47 | return page_id_ & sCoolBit; 48 | } 49 | 50 | bool IsEvicted() { 51 | return page_id_ & sEvictedBit; 52 | } 53 | 54 | /// Whether this swip points to nothing, the memory pointer is nullptr 55 | bool IsEmpty() { 56 | return page_id_ == 0; 57 | } 58 | 59 | uint64_t AsPageId() { 60 | LS_DCHECK(IsEvicted()); 61 | return page_id_ & sEvictedMask; 62 | } 63 | 64 | /// Return the underlying buffer frame from a hot buffer frame. 65 | BufferFrame& AsBufferFrame() { 66 | LS_DCHECK(IsHot()); 67 | return *bf_; 68 | } 69 | 70 | /// Return the underlying buffer frame from a cool buffer frame. 71 | BufferFrame& AsBufferFrameMasked() { 72 | return *reinterpret_cast(page_id_ & sHotMask); 73 | } 74 | 75 | uint64_t Raw() const { 76 | return page_id_; 77 | } 78 | 79 | void MarkHOT(BufferFrame* bf) { 80 | this->bf_ = bf; 81 | } 82 | 83 | void MarkHOT() { 84 | LS_DCHECK(IsCool()); 85 | this->page_id_ = page_id_ & ~sCoolBit; 86 | } 87 | 88 | void Cool() { 89 | this->page_id_ = page_id_ | sCoolBit; 90 | } 91 | 92 | void Evict(PID page_id) { 93 | this->page_id_ = page_id | sEvictedBit; 94 | } 95 | 96 | private: 97 | static const uint64_t sEvictedBit = uint64_t(1) << 63; 98 | static const uint64_t sEvictedMask = ~(uint64_t(1) << 63); 99 | static const uint64_t sCoolBit = uint64_t(1) << 62; 100 | static const uint64_t sCoolMask = ~(uint64_t(1) << 62); 101 | static const uint64_t sHotMask = ~(uint64_t(3) << 62); 102 | 103 | static_assert(sEvictedBit == 0x8000000000000000, ""); 104 | static_assert(sEvictedMask == 0x7FFFFFFFFFFFFFFF, ""); 105 | static_assert(sHotMask == 0x3FFFFFFFFFFFFFFF, ""); 106 | }; 107 | 108 | } // namespace leanstore::storage 109 | -------------------------------------------------------------------------------- /include/leanstore/concurrency/cr_manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/concurrency/worker_context.hpp" 4 | #include "leanstore/concurrency/worker_thread.hpp" 5 | #include "leanstore/units.hpp" 6 | 7 | #include 8 | #include 9 | 10 | namespace leanstore { 11 | 12 | class LeanStore; 13 | 14 | namespace cr { 15 | 16 | struct WaterMarkInfo; 17 | class GroupCommitter; 18 | 19 | /// Manages a fixed number of worker threads and group committer threads. 20 | class CRManager { 21 | public: 22 | /// The LeanStore instance. 23 | leanstore::LeanStore* store_; 24 | 25 | /// All the worker threads 26 | std::vector> worker_threads_; 27 | 28 | /// All the thread-local worker references 29 | std::vector worker_ctxs_; 30 | 31 | WaterMarkInfo global_wmk_info_; 32 | 33 | /// The group committer thread, created and started if WAL is enabled when the 34 | /// CRManager instance is created. 35 | /// 36 | /// NOTE: It should be created after all the worker threads are created and 37 | /// started. 38 | std::unique_ptr group_committer_; 39 | 40 | public: 41 | CRManager(leanstore::LeanStore* store); 42 | 43 | ~CRManager(); 44 | 45 | public: 46 | // State Serialization 47 | StringMap Serialize(); 48 | 49 | /// Deserialize the state of the CRManager from a StringMap. 50 | void Deserialize(StringMap map); 51 | 52 | /// Stop all the worker threads and the group committer thread. 53 | void Stop(); 54 | 55 | private: 56 | void setup_history_storage4_each_worker(); 57 | }; 58 | 59 | } // namespace cr 60 | } // namespace leanstore 61 | -------------------------------------------------------------------------------- /include/leanstore/concurrency/group_committer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/lean_store.hpp" 4 | #include "leanstore/units.hpp" 5 | #include "leanstore/utils/async_io.hpp" 6 | #include "leanstore/utils/user_thread.hpp" 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace leanstore::cr { 17 | 18 | class WorkerContext; 19 | class WalFlushReq; 20 | 21 | /// The group committer thread is responsible for committing transactions in batches. It collects 22 | /// wal records from all the worker threads, writes them to the wal file with libaio, and determines 23 | /// the commitable transactions based on the min flushed GSN and min flushed transaction ID. 24 | class GroupCommitter : public leanstore::utils::UserThread { 25 | public: 26 | leanstore::LeanStore* store_; 27 | 28 | /// File descriptor of the underlying WAL file. 29 | const int32_t wal_fd_; 30 | 31 | /// Start file offset of the next WalEntry. 32 | uint64_t wal_size_; 33 | 34 | /// The minimum flushed system transaction ID among all worker threads. User transactions whose 35 | /// max observed system transaction ID not larger than it can be committed safely. 36 | std::atomic global_min_flushed_sys_tx_; 37 | 38 | /// All the workers. 39 | std::vector& worker_ctxs_; 40 | 41 | /// The libaio wrapper. 42 | utils::AsyncIo aio_; 43 | 44 | public: 45 | GroupCommitter(leanstore::LeanStore* store, int32_t wal_fd, std::vector& workers, 46 | int cpu) 47 | : UserThread(store, "GroupCommitter", cpu), 48 | store_(store), 49 | wal_fd_(wal_fd), 50 | wal_size_(0), 51 | global_min_flushed_sys_tx_(0), 52 | worker_ctxs_(workers), 53 | aio_(workers.size() * 2 + 2) { 54 | } 55 | 56 | virtual ~GroupCommitter() override = default; 57 | 58 | protected: 59 | virtual void run_impl() override; 60 | 61 | private: 62 | /// Phase 1: collect wal records from all the worker threads. Collected wal records are written to 63 | /// libaio IOCBs. 64 | /// 65 | /// @param[out] minFlushedSysTx the min flushed system transaction ID 66 | /// @param[out] minFlushedUsrTx the min flushed user transaction ID 67 | /// @param[out] numRfaTxs number of transactions without dependency 68 | /// @param[out] walFlushReqCopies snapshot of the flush requests 69 | void collect_wal_records(TXID& min_flushed_sys_tx, TXID& min_flushed_usr_tx, 70 | std::vector& num_rfa_txs, 71 | std::vector& wal_flush_req_copies); 72 | 73 | /// Phase 2: write all the collected wal records to the wal file with libaio. 74 | void flush_wal_records(); 75 | 76 | /// Phase 3: determine the commitable transactions based on minFlushedGSN and minFlushedTxId. 77 | /// 78 | /// @param[in] minFlushedSysTx the min flushed system transaction ID 79 | /// @param[in] minFlushedUsrTx the min flushed user transaction ID 80 | /// @param[in] numRfaTxs number of transactions without dependency 81 | /// @param[in] walFlushReqCopies snapshot of the flush requests 82 | void determine_commitable_tx(TXID min_flushed_sys_tx, TXID min_flushed_usr_tx, 83 | const std::vector& num_rfa_txs, 84 | const std::vector& wal_flush_req_copies); 85 | 86 | /// Append a wal entry to libaio IOCBs. 87 | /// 88 | /// @param[in] buf the wal entry buffer 89 | /// @param[in] lower the begin offset of the wal entry in the buffer 90 | /// @param[in] upper the end offset of the wal entry in the buffer 91 | void append(uint8_t* buf, uint64_t lower, uint64_t upper); 92 | }; 93 | 94 | } // namespace leanstore::cr 95 | -------------------------------------------------------------------------------- /include/leanstore/concurrency/history_storage.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/units.hpp" 4 | 5 | #include 6 | #include 7 | 8 | namespace leanstore::storage::btree { 9 | class BasicKV; 10 | } // namespace leanstore::storage::btree 11 | 12 | namespace leanstore::storage { 13 | class BufferFrame; 14 | } // namespace leanstore::storage 15 | 16 | namespace leanstore::cr { 17 | 18 | using RemoveVersionCallback = 19 | std::function; 21 | 22 | struct __attribute__((packed)) VersionMeta { 23 | bool called_before_ = false; 24 | 25 | TREEID tree_id_; 26 | 27 | uint8_t payload_[]; 28 | 29 | public: 30 | inline static const VersionMeta* From(const uint8_t* buffer) { 31 | return reinterpret_cast(buffer); 32 | } 33 | 34 | inline static VersionMeta* From(uint8_t* buffer) { 35 | return reinterpret_cast(buffer); 36 | } 37 | }; 38 | 39 | class HistoryStorage { 40 | private: 41 | struct alignas(64) Session { 42 | leanstore::storage::BufferFrame* rightmost_bf_ = nullptr; 43 | 44 | uint64_t rightmost_version_ = 0; 45 | 46 | int64_t rightmost_pos_ = -1; 47 | 48 | leanstore::storage::BufferFrame* left_most_bf_ = nullptr; 49 | 50 | uint64_t left_most_version_ = 0; 51 | 52 | TXID last_tx_id_ = 0; 53 | }; 54 | 55 | /// Stores all the update versions generated by the current worker thread. 56 | leanstore::storage::btree::BasicKV* update_index_; 57 | 58 | /// Stores all the remove versions generated by the current worker thread. 59 | leanstore::storage::btree::BasicKV* remove_index_; 60 | 61 | Session update_session_; 62 | 63 | Session remove_session_; 64 | 65 | public: 66 | HistoryStorage() : update_index_(nullptr), remove_index_(nullptr) { 67 | } 68 | 69 | ~HistoryStorage() = default; 70 | 71 | leanstore::storage::btree::BasicKV* GetUpdateIndex() { 72 | return update_index_; 73 | } 74 | 75 | void SetUpdateIndex(leanstore::storage::btree::BasicKV* update_index) { 76 | update_index_ = update_index; 77 | } 78 | 79 | leanstore::storage::btree::BasicKV* GetRemoveIndex() { 80 | return remove_index_; 81 | } 82 | 83 | void SetRemoveIndex(leanstore::storage::btree::BasicKV* remove_index) { 84 | remove_index_ = remove_index; 85 | } 86 | 87 | void PutVersion(TXID tx_id, COMMANDID command_id, TREEID tree_id, bool is_remove, 88 | uint64_t payload_length, std::function cb, 89 | bool same_thread = true); 90 | 91 | bool GetVersion(TXID newer_tx_id, COMMANDID newer_command_id, const bool is_remove_command, 92 | std::function cb); 93 | 94 | void PurgeVersions(TXID from_tx_id, TXID to_tx_id, RemoveVersionCallback cb, 95 | const uint64_t limit); 96 | 97 | void VisitRemovedVersions(TXID from_tx_id, TXID to_tx_id, RemoveVersionCallback cb); 98 | }; 99 | 100 | } // namespace leanstore::cr 101 | -------------------------------------------------------------------------------- /include/leanstore/concurrency/logging_impl.hpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/concurrency/logging.hpp" 2 | #include "leanstore/concurrency/wal_entry.hpp" 3 | #include "leanstore/concurrency/wal_payload_handler.hpp" 4 | #include "leanstore/concurrency/worker_context.hpp" 5 | #include "leanstore/units.hpp" 6 | #include "leanstore/utils/defer.hpp" 7 | 8 | namespace leanstore::cr { 9 | 10 | template 11 | WalPayloadHandler Logging::ReserveWALEntryComplex(uint64_t payload_size, PID page_id, LID psn, 12 | TREEID tree_id, Args&&... args) { 13 | // write transaction start on demand 14 | auto prev_lsn = prev_lsn_; 15 | if (!ActiveTx().has_wrote_) { 16 | // no prevLsn for the first wal entry in a transaction 17 | prev_lsn = 0; 18 | ActiveTx().has_wrote_ = true; 19 | } 20 | 21 | // update prev lsn in the end 22 | SCOPED_DEFER(prev_lsn_ = active_walentry_complex_->lsn_); 23 | 24 | auto entry_lsn = lsn_clock_++; 25 | auto* entry_ptr = wal_buffer_ + wal_buffered_; 26 | auto entry_size = sizeof(WalEntryComplex) + payload_size; 27 | ReserveContiguousBuffer(entry_size); 28 | 29 | active_walentry_complex_ = new (entry_ptr) 30 | WalEntryComplex(entry_lsn, prev_lsn, entry_size, WorkerContext::My().worker_id_, 31 | ActiveTx().start_ts_, psn, page_id, tree_id); 32 | 33 | auto* payload_ptr = active_walentry_complex_->payload_; 34 | auto wal_payload = new (payload_ptr) T(std::forward(args)...); 35 | return {wal_payload, entry_size}; 36 | } 37 | 38 | } // namespace leanstore::cr -------------------------------------------------------------------------------- /include/leanstore/concurrency/transaction.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/kv_interface.hpp" 4 | #include "leanstore/lean_store.hpp" 5 | #include "leanstore/units.hpp" 6 | #include "leanstore/utils/user_thread.hpp" 7 | 8 | namespace leanstore { 9 | namespace cr { 10 | 11 | enum class TxState { kIdle, kStarted, kCommitted, kAborted }; 12 | 13 | struct TxStatUtil { 14 | inline static std::string ToString(TxState state) { 15 | switch (state) { 16 | case TxState::kIdle: { 17 | return "Idle"; 18 | } 19 | case TxState::kStarted: { 20 | return "Started"; 21 | } 22 | case TxState::kCommitted: { 23 | return "Committed"; 24 | } 25 | case TxState::kAborted: { 26 | return "Aborted"; 27 | } 28 | default: { 29 | return "Unknown TxState"; 30 | } 31 | } 32 | } 33 | }; 34 | 35 | class Transaction { 36 | public: 37 | /// The state of the current transaction. 38 | TxState state_ = TxState::kIdle; 39 | 40 | /// start_ts_ is the start timestamp of the transaction. Also used as 41 | /// teansaction ID. 42 | TXID start_ts_ = 0; 43 | 44 | /// commit_ts_ is the commit timestamp of the transaction. 45 | TXID commit_ts_ = 0; 46 | 47 | /// Maximum observed system transaction id during transaction processing. Used to track 48 | /// transaction dependencies. 49 | TXID max_observed_sys_tx_id_ = 0; 50 | 51 | /// Whether the transaction has any remote dependencies. Currently, we only support SI isolation 52 | /// level, a user transaction can only depend on a system transaction executed in a remote worker 53 | /// thread. 54 | bool has_remote_dependency_ = false; 55 | 56 | /// tx_mode_ is the mode of the current transaction. 57 | TxMode tx_mode_ = TxMode::kShortRunning; 58 | 59 | /// tx_isolation_level_ is the isolation level for the current transaction. 60 | IsolationLevel tx_isolation_level_ = IsolationLevel::kSnapshotIsolation; 61 | 62 | /// Whether the transaction has any data writes. Transaction writes can be 63 | /// detected once it generates a WAL entry. 64 | bool has_wrote_ = false; 65 | 66 | /// Whether the transaction is durable. A durable transaction can be committed 67 | /// or aborted only after all the WAL entries are flushed to disk. 68 | bool is_durable_ = true; 69 | 70 | bool wal_exceed_buffer_ = false; 71 | 72 | public: 73 | bool IsLongRunning() { 74 | return tx_mode_ == TxMode::kLongRunning; 75 | } 76 | 77 | bool AtLeastSI() { 78 | return tx_isolation_level_ >= IsolationLevel::kSnapshotIsolation; 79 | } 80 | 81 | // Start a new transaction, initialize all fields 82 | void Start(TxMode mode, IsolationLevel level) { 83 | state_ = TxState::kStarted; 84 | start_ts_ = 0; 85 | commit_ts_ = 0; 86 | max_observed_sys_tx_id_ = 0; 87 | has_remote_dependency_ = false; 88 | tx_mode_ = mode; 89 | tx_isolation_level_ = level; 90 | has_wrote_ = false; 91 | is_durable_ = utils::tls_store->store_option_->enable_wal_; 92 | wal_exceed_buffer_ = false; 93 | } 94 | 95 | /// Check whether a user transaction with remote dependencies can be committed. 96 | bool CanCommit(TXID min_flushed_sys_tx, TXID min_flushed_usr_tx) { 97 | return max_observed_sys_tx_id_ <= min_flushed_sys_tx && start_ts_ <= min_flushed_usr_tx; 98 | } 99 | }; 100 | 101 | } // namespace cr 102 | } // namespace leanstore 103 | -------------------------------------------------------------------------------- /include/leanstore/concurrency/wal_payload_handler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/concurrency/group_committer.hpp" 4 | #include "leanstore/concurrency/worker_context.hpp" 5 | 6 | namespace leanstore::cr { 7 | 8 | template 9 | class WalPayloadHandler { 10 | public: 11 | /// payload of the active WAL 12 | T* wal_payload_; 13 | 14 | /// size of the whole WalEntry, including payloads 15 | uint64_t total_size_; 16 | 17 | public: 18 | WalPayloadHandler() = default; 19 | 20 | /// @brief Initialize a WalPayloadHandler 21 | /// @param walPayload the WalPayload object, should already being initialized 22 | /// @param size the total size of the WalEntry 23 | WalPayloadHandler(T* wal_payload, uint64_t size) : wal_payload_(wal_payload), total_size_(size) { 24 | } 25 | 26 | public: 27 | inline T* operator->() { 28 | return wal_payload_; 29 | } 30 | 31 | inline T& operator*() { 32 | return *wal_payload_; 33 | } 34 | 35 | void SubmitWal(); 36 | }; 37 | 38 | template 39 | inline void WalPayloadHandler::SubmitWal() { 40 | cr::WorkerContext::My().logging_.SubmitWALEntryComplex(total_size_); 41 | } 42 | 43 | } // namespace leanstore::cr -------------------------------------------------------------------------------- /include/leanstore/concurrency/worker_context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore-c/perf_counters.h" 4 | #include "leanstore/concurrency/concurrency_control.hpp" 5 | #include "leanstore/concurrency/logging.hpp" 6 | #include "leanstore/concurrency/transaction.hpp" 7 | #include "leanstore/units.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace leanstore { 14 | 15 | class LeanStore; 16 | 17 | } // namespace leanstore 18 | 19 | namespace leanstore::cr { 20 | 21 | class Logging; 22 | class ConcurrencyControl; 23 | 24 | class WorkerContext { 25 | public: 26 | /// The store it belongs to. 27 | leanstore::LeanStore* store_ = nullptr; 28 | 29 | /// The write-ahead logging component. 30 | Logging logging_; 31 | 32 | /// The concurrent control component. 33 | ConcurrencyControl cc_; 34 | 35 | /// The ID of the current command in the current transaction. 36 | COMMANDID command_id_ = 0; 37 | 38 | /// The current running transaction. 39 | Transaction active_tx_; 40 | 41 | /// The ID of the current transaction. It's set by the current worker thread and read by the 42 | /// garbage collection process to determine the lower watermarks of the transactions. 43 | std::atomic active_tx_id_ = 0; 44 | 45 | /// ID of the current worker itself. 46 | const uint64_t worker_id_; 47 | 48 | /// All the workers. 49 | std::vector& all_workers_; 50 | 51 | /// Construct a WorkerContext. 52 | WorkerContext(uint64_t worker_id, std::vector& all_workers, 53 | leanstore::LeanStore* store); 54 | 55 | /// Destruct a WorkerContext. 56 | ~WorkerContext(); 57 | 58 | /// Whether a user transaction is started. 59 | bool IsTxStarted() { 60 | return active_tx_.state_ == TxState::kStarted; 61 | } 62 | 63 | /// Starts a user transaction. 64 | void StartTx(TxMode mode = TxMode::kShortRunning, 65 | IsolationLevel level = IsolationLevel::kSnapshotIsolation, 66 | bool is_read_only = false); 67 | 68 | /// Commits a user transaction. 69 | void CommitTx(); 70 | 71 | /// Aborts a user transaction. 72 | void AbortTx(); 73 | 74 | /// Get the PerfCounters of the current worker. 75 | PerfCounters* GetPerfCounters(); 76 | 77 | public: 78 | /// Thread-local storage for WorkerContext. 79 | static thread_local std::unique_ptr sTlsWorkerCtx; 80 | 81 | /// Raw pointer to sTlsWorkerCtx to avoid the overhead of std::unique_ptr. 82 | static thread_local WorkerContext* sTlsWorkerCtxRaw; 83 | 84 | static constexpr uint64_t kRcBit = (1ull << 63); 85 | static constexpr uint64_t kLongRunningBit = (1ull << 62); 86 | static constexpr uint64_t kCleanBitsMask = ~(kRcBit | kLongRunningBit); 87 | 88 | static WorkerContext& My() { 89 | return *WorkerContext::sTlsWorkerCtxRaw; 90 | } 91 | 92 | static bool InWorker() { 93 | return WorkerContext::sTlsWorkerCtxRaw != nullptr; 94 | } 95 | }; 96 | 97 | // Shortcuts 98 | inline Transaction& ActiveTx() { 99 | return cr::WorkerContext::My().active_tx_; 100 | } 101 | 102 | } // namespace leanstore::cr 103 | -------------------------------------------------------------------------------- /include/leanstore/concurrency/worker_thread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/lean_store.hpp" 4 | #include "leanstore/units.hpp" 5 | #include "leanstore/utils/user_thread.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace leanstore::cr { 13 | 14 | /// WorkerThread contains the context of a worker thread. There can be multiple job senders, and 15 | /// each job sender can send a job to the worker thread. The worker thread state is represented by a 16 | /// pair of (jobSet, jobDone), a typical state transition is: 17 | /// 18 | /// (!jobSet, !jobDone): a new job sender is wake up, set a new job, notify the worker thread 19 | /// -> ( jobSet, !jobDone): the worker thread is wake up, execute the job, set job done, notify the 20 | /// original job sender 21 | /// -> ( jobSet, jobDone): the original job sender is wake up, clear the job, notify other 22 | /// job senders. 23 | class WorkerThread : public utils::UserThread { 24 | public: 25 | enum JobStatus : uint8_t { 26 | kJobIsEmpty = 0, 27 | kJobIsSet, 28 | kJobIsFinished, 29 | }; 30 | 31 | /// The id of the worker thread. 32 | const WORKERID worker_id_; 33 | 34 | /// The mutex to guard the job. 35 | std::mutex mutex_; 36 | 37 | /// The condition variable to notify the worker thread and job sender. 38 | std::condition_variable cv_; 39 | 40 | /// The job to be executed by the worker thread. 41 | std::function job_; 42 | 43 | /// Whether the current job is done. 44 | JobStatus job_status_; 45 | 46 | public: 47 | /// Constructor. 48 | WorkerThread(LeanStore* store, WORKERID worker_id, int cpu) 49 | : utils::UserThread(store, "Worker" + std::to_string(worker_id), cpu), 50 | worker_id_(worker_id), 51 | job_(nullptr), 52 | job_status_(kJobIsEmpty) { 53 | } 54 | 55 | /// Destructor. 56 | ~WorkerThread() override { 57 | Stop(); 58 | } 59 | 60 | /// Stop the worker thread. 61 | /// API for the job sender. 62 | void Stop() override; 63 | 64 | /// Set a job to the worker thread and notify it to run. 65 | /// API for the job sender. 66 | void SetJob(std::function job); 67 | 68 | /// Wait until the job is done. 69 | /// API for the job sender. 70 | void Wait(); 71 | 72 | protected: 73 | /// The main loop of the worker thread. 74 | void run_impl() override; 75 | }; 76 | 77 | inline void WorkerThread::run_impl() { 78 | while (keep_running_) { 79 | // wait until there is a job 80 | std::unique_lock guard(mutex_); 81 | cv_.wait(guard, [&]() { return !keep_running_ || (job_status_ == kJobIsSet); }); 82 | 83 | // check thread status 84 | if (!keep_running_) { 85 | break; 86 | } 87 | 88 | // execute the job 89 | job_(); 90 | 91 | // Set job done, change the worker state to (jobSet, jobDone), notify the job sender 92 | job_status_ = kJobIsFinished; 93 | 94 | guard.unlock(); 95 | cv_.notify_all(); 96 | } 97 | }; 98 | 99 | inline void WorkerThread::Stop() { 100 | if (!(thread_ && thread_->joinable())) { 101 | return; 102 | } 103 | 104 | keep_running_ = false; 105 | cv_.notify_all(); 106 | if (thread_ && thread_->joinable()) { 107 | thread_->join(); 108 | } 109 | thread_ = nullptr; 110 | } 111 | 112 | inline void WorkerThread::SetJob(std::function job) { 113 | // wait the previous job to finish 114 | std::unique_lock guard(mutex_); 115 | cv_.wait(guard, [&]() { return job_status_ == kJobIsEmpty; }); 116 | 117 | // set a new job, change the worker state to (jobSet, jobNotDone), notify the worker thread 118 | job_ = std::move(job); 119 | job_status_ = kJobIsSet; 120 | 121 | guard.unlock(); 122 | cv_.notify_all(); 123 | } 124 | 125 | inline void WorkerThread::Wait() { 126 | std::unique_lock guard(mutex_); 127 | cv_.wait(guard, [&]() { return job_status_ == kJobIsFinished; }); 128 | 129 | // reset the job, change the worker state to (jobNotSet, jobDone), notify other job senders 130 | job_ = nullptr; 131 | job_status_ = kJobIsEmpty; 132 | 133 | guard.unlock(); 134 | cv_.notify_all(); 135 | } 136 | 137 | } // namespace leanstore::cr 138 | -------------------------------------------------------------------------------- /include/leanstore/exceptions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define Generic_Exception(name) \ 9 | struct name : public std::exception { \ 10 | const std::string msg; \ 11 | explicit name() : msg(#name) { \ 12 | printf("Throwing exception: %s\n", #name); \ 13 | } \ 14 | explicit name(const std::string& msg) : msg(msg) { \ 15 | printf("Throwing exception: %s(%s)\n", #name, msg.c_str()); \ 16 | } \ 17 | ~name() = default; \ 18 | virtual const char* what() const noexcept override { \ 19 | return msg.c_str(); \ 20 | } \ 21 | }; 22 | 23 | namespace leanstore { 24 | namespace ex { 25 | Generic_Exception(GenericException); 26 | Generic_Exception(EnsureFailed); 27 | Generic_Exception(UnReachable); 28 | Generic_Exception(TODO); 29 | } // namespace ex 30 | } // namespace leanstore 31 | 32 | #define UNREACHABLE() \ 33 | throw leanstore::ex::UnReachable(std::string(__FILE__) + ":" + \ 34 | std::string(std::to_string(__LINE__))); 35 | 36 | #define always_check(e) \ 37 | (__builtin_expect(!(e), 0) ? throw leanstore::ex::EnsureFailed( \ 38 | std::string(__func__) + " in " + std::string(__FILE__) + "@" + \ 39 | std::to_string(__LINE__) + " msg: " + std::string(#e)) \ 40 | : (void)0) 41 | 42 | #ifdef DEBUG 43 | #define ENSURE(e) assert(e); 44 | #else 45 | #define ENSURE(e) always_check(e) 46 | #endif 47 | 48 | #define TODOException() \ 49 | throw leanstore::ex::TODO(std::string(__FILE__) + ":" + std::string(std::to_string(__LINE__))); 50 | 51 | #ifdef MACRO_CHECK_DEBUG 52 | #define DEBUG_BLOCK() if (true) 53 | #define RELEASE_BLOCK() if (true) 54 | #define BENCHMARK_BLOCK() if (true) 55 | #else 56 | #define DEBUG_BLOCK() if (false) 57 | #ifdef MACRO_CHECK_RELEASE 58 | #define RELEASE_BLOCK() if (true) 59 | #define BENCHMARK_BLOCK() if (true) 60 | #else 61 | #define RELEASE_BLOCK() if (false) 62 | #ifdef MACRO_CHECK_BENCHMARK 63 | #define BENCHMARK_BLOCK() if (true) 64 | #else 65 | #define BENCHMARK_BLOCK() if (false) 66 | #endif 67 | #endif 68 | #endif 69 | 70 | template 71 | inline void DoNotOptimize(const T& value) { 72 | #if defined(__clang__) 73 | asm volatile("" : : "g"(value) : "memory"); 74 | #else 75 | asm volatile("" : : "i,r,m"(value) : "memory"); 76 | #endif 77 | } 78 | -------------------------------------------------------------------------------- /include/leanstore/kv_interface.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/slice.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace leanstore { 10 | 11 | enum class OpCode : uint8_t { 12 | kOK = 0, 13 | kNotFound = 1, 14 | kDuplicated = 2, 15 | kAbortTx = 3, 16 | kSpaceNotEnough = 4, 17 | kOther = 5 18 | }; 19 | 20 | inline std::string ToString(OpCode result) { 21 | switch (result) { 22 | case OpCode::kOK: { 23 | return "OK"; 24 | } 25 | case OpCode::kNotFound: { 26 | return "NOT_FOUND"; 27 | } 28 | case OpCode::kDuplicated: { 29 | return "DUPLICATED"; 30 | } 31 | case OpCode::kAbortTx: { 32 | return "ABORT_TX"; 33 | } 34 | case OpCode::kSpaceNotEnough: { 35 | return "NOT_ENOUGH_SPACE"; 36 | } 37 | case OpCode::kOther: { 38 | return "OTHER"; 39 | } 40 | } 41 | return "Unknown OpCode"; 42 | } 43 | 44 | enum class TxMode : uint8_t { 45 | kLongRunning = 0, 46 | kShortRunning = 1, 47 | }; 48 | 49 | inline std::string ToString(TxMode tx_mode) { 50 | switch (tx_mode) { 51 | case TxMode::kLongRunning: { 52 | return "LongRunning"; 53 | } 54 | case TxMode::kShortRunning: { 55 | return "ShortRunning"; 56 | } 57 | } 58 | return "Unknown TxMode"; 59 | } 60 | 61 | enum class IsolationLevel : uint8_t { 62 | // kReadUnCommitted = 0, 63 | // kReadCommitted = 1, 64 | kSnapshotIsolation = 2, 65 | kSerializable = 3, 66 | }; 67 | 68 | inline IsolationLevel ParseIsolationLevel(std::string str) { 69 | if (str == "ser") { 70 | return leanstore::IsolationLevel::kSerializable; 71 | } 72 | if (str == "si") { 73 | return leanstore::IsolationLevel::kSnapshotIsolation; 74 | } 75 | return leanstore::IsolationLevel::kSnapshotIsolation; 76 | } 77 | 78 | class UpdateSlotInfo { 79 | public: 80 | uint16_t offset_ = 0; 81 | 82 | uint16_t size_ = 0; 83 | 84 | public: 85 | bool operator==(const UpdateSlotInfo& other) const { 86 | return offset_ == other.offset_ && size_ == other.size_; 87 | } 88 | }; 89 | 90 | /// Memory layout: 91 | /// --------------------------- 92 | /// | N | UpdateSlotInfo 0..N | 93 | /// --------------------------- 94 | class UpdateDesc { 95 | public: 96 | uint8_t num_slots_ = 0; 97 | 98 | UpdateSlotInfo update_slots_[]; 99 | 100 | public: 101 | uint64_t Size() const { 102 | return UpdateDesc::Size(num_slots_); 103 | } 104 | 105 | uint64_t SizeWithDelta() const { 106 | return Size() + delta_size(); 107 | } 108 | 109 | private: 110 | uint64_t delta_size() const { 111 | uint64_t length = 0; 112 | for (uint8_t i = 0; i < num_slots_; i++) { 113 | length += update_slots_[i].size_; 114 | } 115 | return length; 116 | } 117 | 118 | public: 119 | inline static const UpdateDesc* From(const uint8_t* buffer) { 120 | return reinterpret_cast(buffer); 121 | } 122 | 123 | inline static UpdateDesc* From(uint8_t* buffer) { 124 | return reinterpret_cast(buffer); 125 | } 126 | 127 | inline static uint64_t Size(uint8_t num_slots) { 128 | uint64_t self_size = sizeof(UpdateDesc); 129 | self_size += (num_slots * sizeof(UpdateSlotInfo)); 130 | return self_size; 131 | } 132 | 133 | inline static UpdateDesc* CreateFrom(uint8_t* buffer) { 134 | auto* update_desc = new (buffer) UpdateDesc(); 135 | return update_desc; 136 | } 137 | }; 138 | 139 | class MutableSlice; 140 | using StringU = std::basic_string; 141 | using ValCallback = std::function; 142 | using MutValCallback = std::function; 143 | using ScanCallback = std::function; 144 | using PrefixLookupCallback = std::function; 145 | 146 | class KVInterface { 147 | public: 148 | virtual OpCode Insert(Slice key, Slice val) = 0; 149 | 150 | /// Update old value with a same sized new value. 151 | /// NOTE: The value is updated via user provided callback. 152 | virtual OpCode UpdatePartial(Slice key, MutValCallback update_call_back, 153 | UpdateDesc& update_desc) = 0; 154 | 155 | virtual OpCode Remove(Slice key) = 0; 156 | 157 | virtual OpCode RangeRemove(Slice start_key, Slice end_key, bool page_wise = true) = 0; 158 | 159 | virtual OpCode ScanAsc(Slice start_key, ScanCallback callback) = 0; 160 | 161 | virtual OpCode ScanDesc(Slice start_key, ScanCallback callback) = 0; 162 | 163 | virtual OpCode Lookup(Slice key, ValCallback val_callback) = 0; 164 | 165 | virtual OpCode PrefixLookup(Slice, PrefixLookupCallback) = 0; 166 | 167 | virtual OpCode PrefixLookupForPrev(Slice, PrefixLookupCallback) = 0; 168 | 169 | virtual uint64_t CountEntries() = 0; 170 | }; 171 | 172 | } // namespace leanstore 173 | -------------------------------------------------------------------------------- /include/leanstore/slice.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace leanstore { 10 | 11 | class Slice : public std::basic_string_view { 12 | public: 13 | Slice() : std::basic_string_view() { 14 | } 15 | 16 | Slice(const std::string& str) 17 | : std::basic_string_view(reinterpret_cast(str.data()), str.size()) { 18 | } 19 | 20 | Slice(const std::string_view& str) 21 | : std::basic_string_view(reinterpret_cast(str.data()), str.size()) { 22 | } 23 | 24 | Slice(const std::basic_string& str) 25 | : std::basic_string_view(str.data(), str.size()) { 26 | } 27 | 28 | Slice(const uint8_t* data, size_t size) : std::basic_string_view(data, size) { 29 | } 30 | 31 | Slice(const char* data) 32 | : std::basic_string_view(reinterpret_cast(data), std::strlen(data)) { 33 | } 34 | 35 | Slice(const char* data, size_t size) 36 | : std::basic_string_view(reinterpret_cast(data), size) { 37 | } 38 | 39 | std::string ToString() const { 40 | return std::string(reinterpret_cast(data()), size()); 41 | } 42 | 43 | void CopyTo(std::string& dest) const { 44 | dest.resize(size()); 45 | std::memcpy(dest.data(), data(), size()); 46 | } 47 | }; 48 | 49 | class MutableSlice { 50 | private: 51 | uint8_t* data_; 52 | uint64_t size_; 53 | 54 | public: 55 | MutableSlice(uint8_t* ptr, uint64_t len) : data_(ptr), size_(len) { 56 | } 57 | 58 | uint8_t* Data() { 59 | return data_; 60 | } 61 | 62 | uint64_t Size() { 63 | return size_; 64 | } 65 | 66 | Slice Immutable() { 67 | return Slice(data_, size_); 68 | } 69 | }; 70 | 71 | inline std::string ToString(Slice slice) { 72 | return std::string(reinterpret_cast(slice.data()), slice.size()); 73 | } 74 | 75 | inline Slice ToSlice(const std::string& str) { 76 | return Slice(reinterpret_cast(str.data()), str.size()); 77 | } 78 | 79 | } // namespace leanstore 80 | -------------------------------------------------------------------------------- /include/leanstore/sync/hybrid_latch.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/utils/log.hpp" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace leanstore::storage { 11 | 12 | class ScopedHybridGuard; 13 | class HybridGuard; 14 | 15 | enum class LatchMode : uint8_t { 16 | kOptimisticOrJump = 0, 17 | kOptimisticSpin = 1, 18 | kPessimisticShared = 2, 19 | kPessimisticExclusive = 3, 20 | }; 21 | 22 | constexpr static uint64_t kLatchExclusiveBit = 1ull; 23 | 24 | inline bool HasExclusiveMark(uint64_t version) { 25 | return (version & kLatchExclusiveBit) == kLatchExclusiveBit; 26 | } 27 | 28 | /// An alternative to std::mutex and std::shared_mutex. A hybrid latch can be 29 | /// latched optimistically, pessimistically in shared or exclusive mode: 30 | /// - optimistic shared: in shared mode, for low-contention scenarios. At this 31 | /// mode, the version number is used to detech latch contention. 32 | /// - pessimistic shared: in shared mode, for high-contention scenarios. 33 | /// - pessimistic exclusive: in exclusive mode, for high-contention scenarios. 34 | class alignas(64) HybridLatch { 35 | private: 36 | /// The optimistic version. 37 | std::atomic version_ = 0; 38 | 39 | /// The pessimistic shared mutex. 40 | std::shared_mutex mutex_; 41 | 42 | friend class HybridGuard; 43 | friend class ScopedHybridGuard; 44 | 45 | public: 46 | HybridLatch(uint64_t version = 0) : version_(version) { 47 | } 48 | 49 | void LockExclusively() { 50 | mutex_.lock(); 51 | version_.fetch_add(kLatchExclusiveBit, std::memory_order_release); 52 | LS_DCHECK(IsLockedExclusively()); 53 | } 54 | 55 | void UnlockExclusively() { 56 | LS_DCHECK(IsLockedExclusively()); 57 | version_.fetch_add(kLatchExclusiveBit, std::memory_order_release); 58 | mutex_.unlock(); 59 | } 60 | 61 | uint64_t GetOptimisticVersion() { 62 | return version_.load(); 63 | } 64 | 65 | bool IsLockedExclusively() { 66 | return HasExclusiveMark(version_.load()); 67 | } 68 | }; 69 | 70 | static_assert(sizeof(HybridLatch) == 64, ""); 71 | 72 | } // namespace leanstore::storage -------------------------------------------------------------------------------- /include/leanstore/sync/optimistic_guarded.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace leanstore { 7 | namespace storage { 8 | 9 | /// Optimized for single-writer-single-reader scenarios. The reader can read the 10 | /// value without locking which is useful for performance-critical code. The 11 | /// value must be trivially copyable. 12 | template 13 | requires std::is_trivially_copy_assignable_v 14 | class OptimisticGuarded { 15 | private: 16 | /// Used for optimistic locking. The lowest 1 bit is used to indicate whether 17 | /// the value is being modified. The version is increased by 2 when the value 18 | /// is modified, which can be used to check whether the value is modified 19 | /// since the last read. 20 | std::atomic version_ = 0; 21 | 22 | /// The guarded value. 23 | T value_; 24 | 25 | public: 26 | /// Constructor. 27 | OptimisticGuarded() = default; 28 | 29 | /// Constructor. 30 | OptimisticGuarded(const T& value) : version_(0), value_(value) { 31 | } 32 | 33 | /// Copies the value and returns the version of the value. The version is 34 | /// guaranteed to be even. 35 | /// @param copiedVal The copied value. 36 | /// @return The version of the value. 37 | [[nodiscard]] uint64_t Get(T& copied_val); 38 | 39 | /// Stores the given value. Only one thread can call this function at a time. 40 | /// @param newVal The value to store. 41 | void Set(const T& new_val); 42 | 43 | /// Updates the given attribute of the value. Only one thread can call this 44 | /// function at a time. 45 | template 46 | void UpdateAttribute(Ta T::*a, const Ta& new_val); 47 | }; 48 | 49 | template 50 | requires std::is_trivially_copy_assignable_v 51 | inline uint64_t OptimisticGuarded::Get(T& copied_val) { 52 | while (true) { 53 | auto version = version_.load(); 54 | while (version & 1) { 55 | version = version_.load(); 56 | } 57 | copied_val = value_; 58 | if (version == version_.load()) { 59 | return version; 60 | } 61 | } 62 | } 63 | 64 | template 65 | requires std::is_trivially_copy_assignable_v 66 | inline void OptimisticGuarded::Set(const T& new_val) { 67 | version_.store(version_ + 1, std::memory_order_release); 68 | value_ = new_val; 69 | version_.store(version_ + 1, std::memory_order_release); 70 | } 71 | 72 | template 73 | requires std::is_trivially_copy_assignable_v 74 | template 75 | inline void OptimisticGuarded::UpdateAttribute(Ta T::*a, const Ta& new_val) { 76 | version_.store(version_ + 1, std::memory_order_release); 77 | value_.*a = new_val; 78 | version_.store(version_ + 1, std::memory_order_release); 79 | } 80 | 81 | } // namespace storage 82 | } // namespace leanstore -------------------------------------------------------------------------------- /include/leanstore/units.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using TREEID = uint64_t; // data structure ID 8 | using PID = uint64_t; // page ID 9 | using LID = uint64_t; // log ID 10 | using TXID = uint64_t; 11 | 12 | using COMMANDID = uint32_t; 13 | using WORKERID = uint16_t; 14 | 15 | constexpr COMMANDID kRemoveCommandMark = 1u << 31; 16 | 17 | using StringMap = std::unordered_map; 18 | 19 | namespace leanstore {} // namespace leanstore 20 | -------------------------------------------------------------------------------- /include/leanstore/utils/async_io.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/utils/error.hpp" 4 | #include "leanstore/utils/log.hpp" 5 | #include "leanstore/utils/result.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | namespace leanstore::utils { 18 | 19 | constexpr size_t kAlignment = 512; 20 | 21 | class AsyncIo { 22 | public: 23 | AsyncIo(uint64_t max_batch_size) 24 | : max_reqs_(max_batch_size), 25 | num_reqs_(0), 26 | iocbs_(max_batch_size), 27 | iocb_ptrs_(max_batch_size), 28 | io_events_(max_batch_size) { 29 | for (uint64_t i = 0; i < max_batch_size; i++) { 30 | iocb_ptrs_[i] = &iocbs_[i]; 31 | } 32 | 33 | std::memset(&aio_ctx_, 0, sizeof(aio_ctx_)); 34 | auto ret = io_setup(max_reqs_, &aio_ctx_); 35 | if (ret < 0) { 36 | Log::Fatal("io_setup failed, error={}", ret); 37 | } 38 | } 39 | 40 | ~AsyncIo() { 41 | auto ret = io_destroy(aio_ctx_); 42 | if (ret < 0) { 43 | Log::Fatal("io_destroy failed, error={}", ret); 44 | } 45 | } 46 | 47 | size_t GetNumRequests() { 48 | return num_reqs_; 49 | } 50 | 51 | bool IsFull() { 52 | return num_reqs_ >= max_reqs_; 53 | } 54 | 55 | bool IsEmpty() { 56 | return num_reqs_ <= 0; 57 | } 58 | 59 | void PrepareRead(int32_t fd, void* buf, size_t count, uint64_t offset) { 60 | LS_DCHECK((reinterpret_cast(buf) & (kAlignment - 1)) == 0); 61 | LS_DCHECK(!IsFull()); 62 | auto slot = num_reqs_++; 63 | io_prep_pread(&iocbs_[slot], fd, buf, count, offset); 64 | iocbs_[slot].data = buf; 65 | } 66 | 67 | void PrepareWrite(int32_t fd, void* buf, size_t count, uint64_t offset) { 68 | LS_DCHECK((reinterpret_cast(buf) & (kAlignment - 1)) == 0); 69 | LS_DCHECK(!IsFull()); 70 | auto slot = num_reqs_++; 71 | io_prep_pwrite(&iocbs_[slot], fd, buf, count, offset); 72 | iocbs_[slot].data = buf; 73 | } 74 | 75 | // Even for direct IO, fsync is still needed to flush file metadata. 76 | void PrepareFsync(int32_t fd) { 77 | LS_DCHECK(!IsFull()); 78 | auto slot = num_reqs_++; 79 | io_prep_fsync(&iocbs_[slot], fd); 80 | } 81 | 82 | Result SubmitAll() { 83 | if (IsEmpty()) { 84 | return 0; 85 | } 86 | 87 | int ret = io_submit(aio_ctx_, num_reqs_, &iocb_ptrs_[0]); 88 | if (ret < 0) { 89 | return std::unexpected( 90 | utils::Error::ErrorAio(ret, std::format("io_submit({}, {}, {})", (void*)&aio_ctx_, 91 | num_reqs_, (void*)&iocb_ptrs_[0]))); 92 | } 93 | 94 | // return requests submitted 95 | return ret; 96 | } 97 | 98 | Result WaitAll(timespec* timeout = nullptr) { 99 | if (IsEmpty()) { 100 | return 0; 101 | } 102 | 103 | int ret = io_getevents(aio_ctx_, num_reqs_, num_reqs_, &io_events_[0], timeout); 104 | if (ret < 0) { 105 | return std::unexpected(utils::Error::ErrorAio(ret, "io_getevents")); 106 | } 107 | 108 | // reset pending requests, allowing new writes 109 | num_reqs_ = 0; 110 | 111 | // return requests completed 112 | return ret; 113 | } 114 | 115 | const io_event* GetIoEvent(size_t i) const { 116 | return &io_events_[i]; 117 | } 118 | 119 | Result Create4DirectIo(const char* file) { 120 | int flags = O_TRUNC | O_CREAT | O_RDWR | O_DIRECT; 121 | auto fd = open(file, flags, 0666); 122 | if (fd == -1) { 123 | return std::unexpected(utils::Error::FileOpen(file, errno, strerror(errno))); 124 | } 125 | return fd; 126 | } 127 | 128 | private: 129 | size_t max_reqs_; 130 | size_t num_reqs_; 131 | io_context_t aio_ctx_; 132 | std::vector iocbs_; 133 | std::vector iocb_ptrs_; 134 | std::vector io_events_; 135 | }; 136 | 137 | } // namespace leanstore::utils -------------------------------------------------------------------------------- /include/leanstore/utils/counter_util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore-c/perf_counters.h" 4 | 5 | #ifdef ENABLE_PERF_COUNTERS 6 | #include 7 | #endif 8 | 9 | namespace leanstore { 10 | 11 | namespace cr { 12 | extern thread_local PerfCounters tls_perf_counters; 13 | } // namespace cr 14 | 15 | /// ScopedTimer for perf counters 16 | class ScopedTimer { 17 | private: 18 | #ifdef ENABLE_PERF_COUNTERS 19 | /// Counter to cumulate the time elasped 20 | CounterType* counter_to_cum_; 21 | 22 | /// Start timepoint 23 | std::chrono::steady_clock::time_point started_at_; 24 | #endif 25 | 26 | public: 27 | ScopedTimer(CounterType* counter [[maybe_unused]]) { 28 | #ifdef ENABLE_PERF_COUNTERS 29 | counter_to_cum_ = counter; 30 | started_at_ = std::chrono::steady_clock::now(); 31 | #endif 32 | } 33 | 34 | ~ScopedTimer() { 35 | #ifdef ENABLE_PERF_COUNTERS 36 | auto stopped_at = std::chrono::steady_clock::now(); 37 | auto elasped_ns = 38 | std::chrono::duration_cast(stopped_at - started_at_).count(); 39 | *counter_to_cum_ += elasped_ns; 40 | #endif 41 | } 42 | }; 43 | 44 | #ifdef ENABLE_PERF_COUNTERS 45 | 46 | //------------------------------------------------------------------------------ 47 | // Macros when counters are enabled 48 | //------------------------------------------------------------------------------ 49 | 50 | #define SCOPED_TIME_INTERNAL_INTERNAL(LINE) scoped_timer_at_line##LINE 51 | #define SCOPED_TIME_INTERNAL(LINE) SCOPED_TIME_INTERNAL_INTERNAL(LINE) 52 | 53 | /// Macro to create a ScopedTimer 54 | #define COUNTER_TIMER_SCOPED(counter) \ 55 | leanstore::ScopedTimer SCOPED_TIME_INTERNAL(__LINE__){counter}; 56 | 57 | /// Macro to inc a counter 58 | #define COUNTER_INC(counter) atomic_fetch_add(counter, 1); 59 | 60 | /// Macro to declare a block of code that will be executed only if counters are enabled 61 | #define COUNTERS_BLOCK() if constexpr (true) 62 | 63 | #else 64 | 65 | //------------------------------------------------------------------------------ 66 | // Macros when counters are disabled 67 | //------------------------------------------------------------------------------ 68 | 69 | #define COUNTER_TIMER_SCOPED(counter) 70 | #define COUNTER_INC(counter) 71 | #define COUNTERS_BLOCK() if constexpr (false) 72 | 73 | #endif 74 | 75 | } // namespace leanstore -------------------------------------------------------------------------------- /include/leanstore/utils/debug_flags.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace leanstore { 9 | namespace utils { 10 | 11 | #ifdef DEBUG 12 | #define LS_DEBUG_EXECUTE(store, name, action) \ 13 | if (store != nullptr && store->debug_flags_registry_.IsExists(name)) { \ 14 | action; \ 15 | } 16 | #define LS_DEBUG_ENABLE(store, name) \ 17 | if (store != nullptr) { \ 18 | store->debug_flags_registry_.Insert(name); \ 19 | } 20 | 21 | #define LS_DEBUG_DISABLE(store, name) \ 22 | if (store != nullptr) { \ 23 | store->debug_flags_registry_.Erase(name); \ 24 | } 25 | #else 26 | #define LS_DEBUG_EXECUTE(store, name, action) 27 | #define LS_DEBUG_ENABLE(store, name) 28 | #define LS_DEBUG_DISABLE(store, name) 29 | #endif 30 | 31 | class DebugFlagsRegistry { 32 | public: 33 | std::shared_mutex mutex_; 34 | std::unordered_set flags_; 35 | 36 | DebugFlagsRegistry() = default; 37 | ~DebugFlagsRegistry() = default; 38 | 39 | public: 40 | void Insert(const std::string& name) { 41 | std::unique_lock unique_guard(mutex_); 42 | flags_.insert(name); 43 | } 44 | 45 | void Erase(const std::string& name) { 46 | std::unique_lock unique_guard(mutex_); 47 | flags_.erase(name); 48 | } 49 | 50 | bool IsExists(const std::string& name) { 51 | std::shared_lock shared_guard(mutex_); 52 | auto it = flags_.find(name); 53 | return it != flags_.end(); 54 | } 55 | }; 56 | 57 | } // namespace utils 58 | } // namespace leanstore 59 | -------------------------------------------------------------------------------- /include/leanstore/utils/defer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace leanstore { 4 | namespace utils { 5 | 6 | #ifndef SCOPED_DEFER 7 | 8 | template 9 | struct ScopedDeferrer { 10 | F func_; 11 | 12 | ScopedDeferrer(F f) : func_(f) { 13 | } 14 | 15 | ~ScopedDeferrer() { 16 | func_(); 17 | } 18 | 19 | ScopedDeferrer(const ScopedDeferrer&) = delete; 20 | ScopedDeferrer(ScopedDeferrer&&) = default; 21 | ScopedDeferrer& operator=(const ScopedDeferrer&) = delete; 22 | ScopedDeferrer& operator=(ScopedDeferrer&&) = delete; 23 | }; 24 | 25 | template 26 | ScopedDeferrer MakeScopedDeferrer(F f) { 27 | return ScopedDeferrer(f); 28 | } 29 | 30 | #define SCOPED_DEFER_INTERNAL_INTERNAL(LINE) defer_at_line##LINE 31 | #define SCOPED_DEFER_INTERNAL(LINE) SCOPED_DEFER_INTERNAL_INTERNAL(LINE) 32 | #define SCOPED_DEFER(f) \ 33 | auto SCOPED_DEFER_INTERNAL(__LINE__) = leanstore::utils::MakeScopedDeferrer([&]() { f; }); 34 | 35 | #endif // SCOPED_DEFER 36 | 37 | } // namespace utils 38 | } // namespace leanstore -------------------------------------------------------------------------------- /include/leanstore/utils/error.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace leanstore { 9 | namespace utils { 10 | 11 | enum class ErrorCode : uint64_t { 12 | kGeneral = 1, 13 | 14 | // File related error code 15 | kFile = 100, 16 | kFileOpen, 17 | kFileClose, 18 | kFileSeek, 19 | kFileRead, 20 | kFileWrite, 21 | 22 | // AIO related error code 23 | kAioAgain = 150, 24 | kAioBadf, 25 | kAioFault, 26 | kAioInvalid, 27 | kAioNoSys, 28 | kAioPerm, 29 | kAioUnknown, 30 | 31 | // BasicKV related error code 32 | kBasicKV = 200, 33 | kBasicKVCreate, 34 | 35 | // TransactionKV related error code 36 | kTransactionKV = 300, 37 | kTransactionKVCreate, 38 | 39 | }; 40 | 41 | class Error { 42 | private: 43 | ErrorCode code_ = ErrorCode::kGeneral; 44 | std::string message_ = ""; 45 | 46 | public: 47 | Error() = default; 48 | 49 | template 50 | Error(ErrorCode code, const std::string& fmt, Args&&... args) 51 | : code_(code), 52 | message_(std::vformat(fmt, std::make_format_args(args...))) { 53 | } 54 | 55 | // copy construct 56 | Error(const Error& other) = default; 57 | 58 | // copy assign 59 | Error& operator=(const Error& other) = default; 60 | 61 | // move construct 62 | Error(Error&& other) noexcept : code_(other.code_), message_(std::move(other.message_)) { 63 | } 64 | 65 | // move assign 66 | Error& operator=(Error&& other) noexcept { 67 | code_ = other.code_; 68 | message_ = std::move(other.message_); 69 | return *this; 70 | } 71 | 72 | ~Error() = default; 73 | 74 | inline bool operator==(const Error& other) const { 75 | return code_ == other.code_ && message_ == other.message_; 76 | } 77 | 78 | inline std::string ToString() const { 79 | return std::format("ER-{}: {}", static_cast(code_), message_); 80 | } 81 | 82 | inline uint64_t Code() const { 83 | return static_cast(code_); 84 | } 85 | 86 | public: 87 | template 88 | inline static Error General(Args&&... args) { 89 | const std::string msg = "{}"; 90 | return Error(ErrorCode::kGeneral, msg, std::forward(args)...); 91 | } 92 | 93 | // TransactionKV 94 | template 95 | inline static Error BasicKVCreate(Args&&... args) { 96 | const std::string msg = "Fail to create BasicKV, treeName={}"; 97 | return Error(ErrorCode::kBasicKVCreate, msg, std::forward(args)...); 98 | } 99 | 100 | // File 101 | template 102 | inline static Error FileOpen(Args&&... args) { 103 | const std::string msg = "Fail to open file, file={}, errno={}, strerror={}"; 104 | return Error(ErrorCode::kFileOpen, msg, std::forward(args)...); 105 | } 106 | 107 | template 108 | inline static Error FileClose(Args&&... args) { 109 | const std::string msg = "Fail to close file, file={}, errno={}, strerror={}"; 110 | return Error(ErrorCode::kFileClose, msg, std::forward(args)...); 111 | } 112 | 113 | template 114 | inline static Error FileSeek(Args&&... args) { 115 | const std::string msg = "Fail to seek file, file={}, errno={}, strerror={}"; 116 | return Error(ErrorCode::kFileSeek, msg, std::forward(args)...); 117 | } 118 | 119 | template 120 | inline static Error FileRead(Args&&... args) { 121 | const std::string msg = "Fail to read file, file={}, errno={}, strerror={}"; 122 | return Error(ErrorCode::kFileRead, msg, std::forward(args)...); 123 | } 124 | 125 | template 126 | inline static Error FileWrite(Args&&... args) { 127 | const std::string msg = "Fail to write file, file={}, errno={}, strerror={}"; 128 | return Error(ErrorCode::kFileWrite, msg, std::forward(args)...); 129 | } 130 | 131 | static Error ErrorAio(int ret_code, const std::string& api_name) { 132 | switch (-ret_code) { 133 | case EAGAIN: 134 | return Error(ErrorCode::kAioAgain, 135 | std::format("AIO({}) failed with EAGAIN, insufficient resources", api_name)); 136 | case EBADF: 137 | return Error(ErrorCode::kAioBadf, "AIO({}) failed with EBADF, bad file descriptor", api_name); 138 | case EFAULT: 139 | return Error(ErrorCode::kAioFault, 140 | "AIO({}) failed with EFAULT, one of the data structures " 141 | "points to invalid data", 142 | api_name); 143 | case EINVAL: 144 | return Error(ErrorCode::kAioInvalid, "AIO({}) failed with EINVAL, invalid argument", 145 | api_name); 146 | case ENOSYS: 147 | return Error(ErrorCode::kAioNoSys, "AIO({}) failed with ENOSYS, not implemented", api_name); 148 | case EPERM: 149 | return Error(ErrorCode::kAioPerm, "AIO({}) failed with EPERM, operation not permitted", 150 | api_name); 151 | default: 152 | return Error(ErrorCode::kAioUnknown, "AIO({}) failed with unknown error code {}", api_name, 153 | ret_code); 154 | } 155 | } 156 | }; 157 | 158 | } // namespace utils 159 | } // namespace leanstore 160 | -------------------------------------------------------------------------------- /include/leanstore/utils/fnv_hash.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace leanstore { 6 | namespace utils { 7 | 8 | class FNV { 9 | private: 10 | static constexpr uint64_t kFnvOffsetBasis64 = 0xCBF29CE484222325L; 11 | static constexpr uint64_t kFnvPrime64 = 1099511628211L; 12 | 13 | public: 14 | static uint64_t Hash(uint64_t val); 15 | }; 16 | 17 | } // namespace utils 18 | } // namespace leanstore 19 | -------------------------------------------------------------------------------- /include/leanstore/utils/json_util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/slice.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace leanstore::utils { 10 | 11 | using JsonValue = rapidjson::GenericValue>; 12 | 13 | inline std::string JsonToStr(rapidjson::Value* obj) { 14 | rapidjson::StringBuffer buffer; 15 | rapidjson::Writer writer(buffer); 16 | obj->Accept(writer); 17 | return buffer.GetString(); 18 | } 19 | 20 | template 21 | inline void AddMemberToJson(JsonValue* obj, JsonValue::AllocatorType& allocator, const char* name, 22 | Args&&... args) { 23 | JsonValue member_name(name, allocator); 24 | JsonValue member_val(std::forward(args)...); 25 | obj->AddMember(member_name, member_val, allocator); 26 | } 27 | 28 | template 29 | inline void AddMemberToJson(JsonValue* obj, JsonValue::AllocatorType& allocator, const char* name, 30 | Slice val) { 31 | JsonValue member_name(name, allocator); 32 | JsonValue member_val(reinterpret_cast(val.data()), val.size(), allocator); 33 | obj->AddMember(member_name, member_val, allocator); 34 | } 35 | 36 | } // namespace leanstore::utils -------------------------------------------------------------------------------- /include/leanstore/utils/jump_mu.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #define JUMPMU_STACK_SIZE 100 9 | #define JUMPMU_STACK_OBJECTS_LIMIT 100 10 | 11 | namespace jumpmu { 12 | 13 | extern __thread int tls_num_jump_points; 14 | extern __thread jmp_buf tls_jump_points[JUMPMU_STACK_SIZE]; 15 | extern __thread int tls_jump_point_num_stack_objs[JUMPMU_STACK_SIZE]; 16 | 17 | extern __thread int tls_num_stack_objs; 18 | extern __thread void* tls_objs[JUMPMU_STACK_OBJECTS_LIMIT]; 19 | extern __thread void (*tls_obj_dtors[JUMPMU_STACK_OBJECTS_LIMIT])(void*); 20 | 21 | void Jump(); 22 | 23 | inline void PopBackDestructor() { 24 | assert(tls_num_stack_objs > 0); 25 | 26 | tls_objs[tls_num_stack_objs - 1] = nullptr; 27 | tls_obj_dtors[tls_num_stack_objs - 1] = nullptr; 28 | tls_num_stack_objs--; 29 | } 30 | 31 | inline void PushBackDesctructor(void* obj, void (*dtor)(void*)) { 32 | assert(tls_num_stack_objs < JUMPMU_STACK_SIZE); 33 | assert(obj != nullptr); 34 | assert(dtor != nullptr); 35 | 36 | tls_obj_dtors[tls_num_stack_objs] = dtor; 37 | tls_objs[tls_num_stack_objs] = obj; 38 | tls_num_stack_objs++; 39 | } 40 | 41 | } // namespace jumpmu 42 | 43 | template 44 | class JumpScoped { 45 | public: 46 | T obj_; 47 | 48 | template 49 | JumpScoped(Args&&... args) : obj_(std::forward(args)...) { 50 | jumpmu::PushBackDesctructor(this, &DestructBeforeJump); 51 | } 52 | 53 | ~JumpScoped() { 54 | jumpmu::PopBackDestructor(); 55 | } 56 | 57 | T* operator->() { 58 | return reinterpret_cast(&obj_); 59 | } 60 | 61 | /// Destructs the object, releases all the resources before longjump. 62 | static void DestructBeforeJump(void* jmuw_obj) { 63 | reinterpret_cast*>(jmuw_obj)->~JumpScoped(); 64 | } 65 | }; 66 | 67 | /// Set a jump point, save the execution context before jump 68 | #define JUMPMU_TRY() \ 69 | jumpmu::tls_jump_point_num_stack_objs[jumpmu::tls_num_jump_points] = jumpmu::tls_num_stack_objs; \ 70 | if (setjmp(jumpmu::tls_jump_points[jumpmu::tls_num_jump_points++]) == 0) { 71 | 72 | /// Remove the last jump point, add the execution path once jump happens 73 | #define JUMPMU_CATCH() \ 74 | jumpmu::tls_num_jump_points--; \ 75 | } \ 76 | else 77 | 78 | /// Remove the last jump point, finish the function execution 79 | #define JUMPMU_RETURN \ 80 | jumpmu::tls_num_jump_points--; \ 81 | return 82 | 83 | /// Remove the last jump point, break the current loop 84 | #define JUMPMU_BREAK \ 85 | jumpmu::tls_num_jump_points--; \ 86 | break 87 | 88 | /// Remove the last jump point, continue the current loop 89 | #define JUMPMU_CONTINUE \ 90 | jumpmu::tls_num_jump_points--; \ 91 | continue 92 | 93 | /// Define a class function to destruct the object. Usually used together with 94 | /// JUMPMU_PUSH_BACK_DESTRUCTOR_BEFORE_JUMP() and JUMPMU_POP_BACK_DESTRUCTOR_BEFORE_JUMP() 95 | #define JUMPMU_DEFINE_DESTRUCTOR_BEFORE_JUMP(T) \ 96 | static void DestructBeforeJump(void* obj) { \ 97 | reinterpret_cast(obj)->~T(); \ 98 | } 99 | 100 | /// Pushe the desctructor to be called before jump to the thread local stack. Should be put in 101 | /// every constructor. 102 | #define JUMPMU_PUSH_BACK_DESTRUCTOR_BEFORE_JUMP() \ 103 | jumpmu::PushBackDesctructor(this, &DestructBeforeJump); 104 | 105 | /// Pop the desctructor to be called before jump from the thread local stack. Should be put in the 106 | /// desctructor. 107 | #define JUMPMU_POP_BACK_DESTRUCTOR_BEFORE_JUMP() jumpmu::PopBackDestructor(); -------------------------------------------------------------------------------- /include/leanstore/utils/log.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore-c/store_option.h" 4 | 5 | #include 6 | #include 7 | 8 | #ifdef DEBUG 9 | #define LS_DLOG(...) leanstore::Log::Debug(__VA_ARGS__); 10 | #define LS_DCHECK(...) leanstore::Log::DebugCheck(__VA_ARGS__); 11 | #else 12 | #define LS_DLOG(...) (void)0; 13 | #define LS_DCHECK(...) (void)0; 14 | #endif 15 | 16 | namespace leanstore { 17 | 18 | class Log { 19 | public: 20 | inline static bool sInited = false; 21 | inline static std::mutex sInitMutex; 22 | 23 | static void Init(const StoreOption* option); 24 | 25 | static void DebugCheck(bool condition, const std::string& msg = ""); 26 | 27 | static void Debug(const std::string& msg); 28 | 29 | static void Info(const std::string& msg); 30 | 31 | static void Warn(const std::string& msg); 32 | 33 | static void Error(const std::string& msg); 34 | 35 | static void Fatal(const std::string& msg); 36 | 37 | template 38 | static void DebugCheck(bool condition, std::format_string fmt, Args&&... args) { 39 | DebugCheck(condition, std::format(fmt, std::forward(args)...)); 40 | } 41 | 42 | template 43 | static void Debug(std::format_string fmt, Args&&... args) { 44 | Debug(std::format(fmt, std::forward(args)...)); 45 | } 46 | 47 | template 48 | static void Info(std::format_string fmt, Args&&... args) { 49 | Info(std::format(fmt, std::forward(args)...)); 50 | } 51 | 52 | template 53 | static void Warn(std::format_string fmt, Args&&... args) { 54 | Warn(std::format(fmt, std::forward(args)...)); 55 | } 56 | 57 | template 58 | static void Error(std::format_string fmt, Args&&... args) { 59 | Error(std::format(fmt, std::forward(args)...)); 60 | } 61 | 62 | template 63 | static void Fatal(std::format_string fmt, Args&&... args) { 64 | Fatal(std::format(fmt, std::forward(args)...)); 65 | } 66 | }; 67 | 68 | } // namespace leanstore 69 | -------------------------------------------------------------------------------- /include/leanstore/utils/parallelize.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace leanstore { 7 | namespace utils { 8 | 9 | class Parallelize { 10 | public: 11 | static void Range( 12 | uint64_t num_threads, uint64_t num_jobs, 13 | std::function job_handler); 14 | 15 | static void ParallelRange(uint64_t num_jobs, 16 | std::function job_handler); 17 | }; 18 | 19 | } // namespace utils 20 | } // namespace leanstore 21 | -------------------------------------------------------------------------------- /include/leanstore/utils/random_generator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/utils/log.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace leanstore { 11 | namespace utils { 12 | 13 | class MersenneTwister { 14 | private: 15 | static const int sNn = 312; 16 | static const int sMm = 156; 17 | static const uint64_t sMatrixA = 0xB5026F5AA96619E9ULL; 18 | static const uint64_t sUm = 0xFFFFFFFF80000000ULL; 19 | static const uint64_t sLm = 0x7FFFFFFFULL; 20 | 21 | uint64_t mt_[sNn]; 22 | int mti_; 23 | 24 | void init(uint64_t seed); 25 | 26 | public: 27 | MersenneTwister(uint64_t seed = 19650218ULL); 28 | uint64_t Rand(); 29 | }; 30 | 31 | } // namespace utils 32 | } // namespace leanstore 33 | 34 | static thread_local leanstore::utils::MersenneTwister tls_mt_generator; 35 | static thread_local std::mt19937 tls_std_generator; 36 | 37 | namespace leanstore { 38 | namespace utils { 39 | 40 | class RandomGenerator { 41 | public: 42 | /// Get a random number between min inclusive and max exclusive, i.e. in the 43 | /// range [min, max) 44 | static uint64_t RandU64(uint64_t min, uint64_t max) { 45 | uint64_t rand = min + (tls_mt_generator.Rand() % (max - min)); 46 | return rand; 47 | } 48 | 49 | static uint64_t RandU64() { 50 | return tls_mt_generator.Rand(); 51 | } 52 | 53 | static uint64_t RandU64Std(uint64_t min, uint64_t max) { 54 | std::uniform_int_distribution distribution(min, max - 1); 55 | return distribution(tls_std_generator); 56 | } 57 | 58 | template 59 | inline static T Rand(T min, T max) { 60 | uint64_t rand = RandU64(min, max); 61 | return static_cast(rand); 62 | } 63 | 64 | static void RandString(uint8_t* dst, uint64_t size) { 65 | for (uint64_t i = 0; i < size; i++) { 66 | dst[i] = Rand(48, 123); 67 | } 68 | } 69 | 70 | static std::string RandAlphString(size_t len) { 71 | static constexpr auto kChars = "0123456789" 72 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 73 | "abcdefghijklmnopqrstuvwxyz"; 74 | auto result = std::string(len, '\0'); 75 | std::generate_n(begin(result), len, [&]() { 76 | auto i = RandU64Std(0, std::strlen(kChars)); 77 | return kChars[i]; 78 | }); 79 | return result; 80 | } 81 | 82 | static void RandAlphString(size_t len, std::string& result) { 83 | static constexpr auto kChars = "0123456789" 84 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 85 | "abcdefghijklmnopqrstuvwxyz"; 86 | result.resize(len, '\0'); 87 | std::generate_n(begin(result), len, [&]() { 88 | auto i = RandU64Std(0, std::strlen(kChars)); 89 | return kChars[i]; 90 | }); 91 | } 92 | }; 93 | 94 | } // namespace utils 95 | } // namespace leanstore 96 | -------------------------------------------------------------------------------- /include/leanstore/utils/result.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/utils/error.hpp" 4 | 5 | #include 6 | 7 | namespace leanstore { 8 | 9 | template 10 | using Result = std::expected; 11 | 12 | } // namespace leanstore -------------------------------------------------------------------------------- /include/leanstore/utils/scrambled_zipf_generator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "zipf_generator.hpp" 4 | 5 | namespace leanstore::utils { 6 | 7 | class ScrambledZipfGenerator { 8 | public: 9 | uint64_t min; 10 | uint64_t max; 11 | uint64_t n; 12 | ZipfGenerator zipf_generator; 13 | 14 | ScrambledZipfGenerator(uint64_t min_inclusive, uint64_t max_exclusive, double theta) 15 | : min(min_inclusive), 16 | max(max_exclusive), 17 | n(max - min), 18 | zipf_generator((max - min) * 2, theta) { 19 | } 20 | 21 | uint64_t rand(); 22 | }; 23 | 24 | } // namespace leanstore::utils 25 | -------------------------------------------------------------------------------- /include/leanstore/utils/user_thread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defer.hpp" 4 | #include "leanstore/utils/log.hpp" 5 | #include "misc.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | namespace leanstore { 15 | 16 | class LeanStore; 17 | 18 | } // namespace leanstore 19 | 20 | namespace leanstore::utils { 21 | 22 | inline thread_local LeanStore* tls_store = nullptr; 23 | inline thread_local std::string tls_thread_name = ""; 24 | 25 | /// User thread with custom thread name. 26 | class UserThread { 27 | protected: 28 | LeanStore* store_ = nullptr; 29 | 30 | std::string thread_name_ = ""; 31 | 32 | int running_cpu_ = -1; 33 | 34 | std::unique_ptr thread_ = nullptr; 35 | 36 | std::atomic keep_running_ = false; 37 | 38 | public: 39 | UserThread(LeanStore* store, const std::string& name, int running_cpu = -1) 40 | : store_(store), 41 | thread_name_(name), 42 | running_cpu_(running_cpu) { 43 | if (thread_name_.size() > 15) { 44 | Log::Error("Thread name should be restricted to 15 characters, name={}, size={}", name, 45 | name.size()); 46 | } 47 | } 48 | 49 | virtual ~UserThread() { 50 | Stop(); 51 | } 52 | 53 | public: 54 | /// Start executing the thread. 55 | void Start() { 56 | if (thread_ == nullptr) { 57 | keep_running_ = true; 58 | thread_ = std::make_unique(&UserThread::run, this); 59 | } 60 | } 61 | 62 | /// Stop executing the thread. 63 | virtual void Stop() { 64 | keep_running_ = false; 65 | if (thread_ && thread_->joinable()) { 66 | thread_->join(); 67 | } 68 | thread_ = nullptr; 69 | } 70 | 71 | bool IsStarted() { 72 | return thread_ != nullptr && thread_->joinable(); 73 | } 74 | 75 | protected: 76 | void run() { 77 | tls_store = store_; 78 | 79 | // set thread-local thread name at the very beging so that logs printed by 80 | // the thread can get it. 81 | tls_thread_name = thread_name_; 82 | 83 | // log info about thread start and stop events 84 | Log::Info("{} thread started", thread_name_); 85 | SCOPED_DEFER(Log::Info("{} thread stopped", thread_name_)); 86 | 87 | // setup thread name 88 | pthread_setname_np(pthread_self(), thread_name_.c_str()); 89 | 90 | // pin the thread to a specific CPU 91 | if (running_cpu_ != -1) { 92 | utils::PinThisThread(running_cpu_); 93 | Log::Info("{} pined to CPU {}", thread_name_, running_cpu_); 94 | } 95 | 96 | // run custom thread loop 97 | run_impl(); 98 | } 99 | 100 | /// Custom thread loop 101 | virtual void run_impl() = 0; 102 | }; 103 | 104 | } // namespace leanstore::utils 105 | -------------------------------------------------------------------------------- /include/leanstore/utils/zipf_generator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace leanstore { 6 | namespace utils { 7 | 8 | // A Zipf distributed random number generator 9 | // Based on Jim Gray Algorithm as described in "Quickly Generating 10 | // Billion-Record..." 11 | class ZipfGenerator { 12 | private: 13 | uint64_t n; 14 | double theta; 15 | 16 | double alpha, zetan, eta; 17 | 18 | double zeta(uint64_t n, double theta); 19 | 20 | public: 21 | // [0, n) 22 | ZipfGenerator(uint64_t ex_n, double theta); 23 | // uint64_t rand(uint64_t new_n); 24 | uint64_t rand(); 25 | }; 26 | 27 | } // namespace utils 28 | } // namespace leanstore 29 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -uvx 4 | 5 | # global variables 6 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 7 | PROJECT_DIR=${SCRIPT_DIR}/.. 8 | 9 | # build and test 10 | buildAndTest() { 11 | build_type=$1 12 | build_dir=$2 13 | pushd ${PROJECT_DIR} 14 | cmake -B ${build_dir} -S . -DCMAKE_BUILD_TYPE=${build_type} 15 | cppcheck --project=${build_dir}/compile_commands.json -i tests --enable=all --error-exitcode=0 16 | cmake --build ${build_dir} -j `nproc` 17 | ctest --test-dir ${build_dir} 18 | gcovr -v -r . --html-details --output coverage/ --exclude 'build/*' --exclude 'tests/*' --exclude 'benchmarks/*' 19 | # to view the html report: python3 -m http.server --directory coverage 20 | popd 21 | } 22 | 23 | main() { 24 | build_type="Debug" 25 | build_dir="build/debug" 26 | buildAndTest ${build_type} ${build_dir} 27 | } 28 | 29 | main 30 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Files for the lib 3 | # ------------------------------------------------------------------------------ 4 | file(GLOB_RECURSE LEANSTORE_SRC **.cpp **/**.cpp **.cc **/**.cc **.hpp **/**.hpp **.h **/**.h) 5 | 6 | 7 | # ------------------------------------------------------------------------------ 8 | # Set build target and dependencies for the lib 9 | # ------------------------------------------------------------------------------ 10 | option(BUILD_SHARED_LIBS "Build using shared libraries" ON) 11 | if(BUILD_SHARED_LIBS) 12 | add_library(leanstore SHARED ${LEANSTORE_SRC}) 13 | else() 14 | add_library(leanstore STATIC ${LEANSTORE_SRC}) 15 | endif() 16 | 17 | # dependencies 18 | set(leanstore_deps 19 | aio 20 | rapidjson 21 | spdlog::spdlog_header_only 22 | Crc32c::crc32c 23 | httplib::httplib 24 | PkgConfig::libunwind 25 | ) 26 | target_link_libraries(leanstore PUBLIC ${leanstore_deps}) 27 | 28 | option(ENABLE_PROFILING "Enable profiling" OFF) 29 | if(ENABLE_PROFILING) 30 | target_compile_definitions(leanstore PUBLIC ENABLE_PROFILING) 31 | 32 | find_package(PkgConfig REQUIRED) 33 | pkg_check_modules(PKG_PROFILER IMPORTED_TARGET libprofiler) 34 | pkg_check_modules(PKG_TCMALLOC IMPORTED_TARGET libtcmalloc) 35 | if (PKG_PROFILER_FOUND) 36 | message(STATUS "libprofiler found: ${PKG_PROFILER}") 37 | target_link_libraries(leanstore PUBLIC PkgConfig::PKG_PROFILER) 38 | endif (PKG_PROFILER_FOUND) 39 | if (PKG_TCMALLOC_FOUND) 40 | message(STATUS "libtcmalloc found: ${PKG_TCMALLOC}") 41 | target_link_libraries(leanstore PUBLIC PkgConfig::PKG_TCMALLOC) 42 | endif (PKG_TCMALLOC_FOUND) 43 | endif(ENABLE_PROFILING) 44 | 45 | # include dirs 46 | target_include_directories(leanstore PUBLIC ${CMAKE_SOURCE_DIR}/include) 47 | target_include_directories(leanstore PRIVATE ${CMAKE_CURRENT_LIST_DIR}) 48 | 49 | # Get the include directories for the target. 50 | get_target_property(LIBA_INCLUDES leanstore INCLUDE_DIRECTORIES) 51 | foreach(dir ${LIBA_INCLUDES}) 52 | message(STATUS "include dir: ${dir}") 53 | endforeach() 54 | 55 | 56 | # --------------------------------------------------------------------------- 57 | # Set compile options for the lib 58 | # --------------------------------------------------------------------------- 59 | 60 | option(SANI "Compile leanstore with sanitizers" OFF) 61 | if(SANI) 62 | target_compile_options(leanstore PUBLIC -fsanitize=address) 63 | target_link_libraries(leanstore asan) 64 | endif(SANI) 65 | 66 | 67 | option(ENABLE_PERF_COUNTERS "Whether to enable perf counters" OFF) 68 | if(ENABLE_PERF_COUNTERS) 69 | target_compile_definitions(leanstore PUBLIC ENABLE_PERF_COUNTERS) 70 | endif() 71 | 72 | set(CHECKS_LEVEL "default" CACHE STRING "Which checks to leave in leanstore build") 73 | if(CHECKS_LEVEL STREQUAL "default") 74 | if(CMAKE_BUILD_TYPE MATCHES Debug) 75 | target_compile_definitions(leanstore PUBLIC MACRO_CHECK_DEBUG) 76 | elseif(CMAKE_BUILD_TYPE MATCHES RelWithDebInfo OR CMAKE_BUILD_TYPE MATCHES Release) 77 | target_compile_definitions(leanstore PUBLIC MACRO_CHECK_RELEASE) 78 | endif() 79 | elseif(CHECKS_LEVEL STREQUAL "debug") 80 | target_compile_definitions(leanstore PUBLIC MACRO_CHECK_DEBUG) 81 | elseif(CHECKS_LEVEL STREQUAL "release") 82 | target_compile_definitions(leanstore PUBLIC MACRO_CHECK_RELEASE) 83 | elseif(CHECKS_LEVEL STREQUAL "benchmark") 84 | target_compile_definitions(leanstore PUBLIC MACRO_CHECK_BENCHMARK) 85 | endif() 86 | 87 | # ------------------------------------------------------------------------------ 88 | # export header files for lib consumers 89 | # ------------------------------------------------------------------------------ 90 | set(LEANSTORE_INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR}) 91 | set_property(TARGET leanstore APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${LEANSTORE_INCLUDE_DIR}) 92 | 93 | # ------------------------------------------------------------------------------ 94 | # install rule 95 | # ------------------------------------------------------------------------------ 96 | include(GNUInstallDirs) 97 | install(TARGETS leanstore 98 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 99 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 100 | install(DIRECTORY 101 | ${CMAKE_SOURCE_DIR}/include/leanstore 102 | ${CMAKE_SOURCE_DIR}/include/leanstore-c 103 | DESTINATION include) 104 | 105 | # ------------------------------------------------------------------------------ 106 | # export pkg-config files for lib consumers 107 | # ------------------------------------------------------------------------------ 108 | configure_file(leanstore.pc.in leanstore.pc @ONLY) 109 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/leanstore.pc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) 110 | -------------------------------------------------------------------------------- /src/buffer-manager/async_write_buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/buffer-manager/async_write_buffer.hpp" 2 | 3 | #include "leanstore/buffer-manager/buffer_frame.hpp" 4 | #include "leanstore/utils/log.hpp" 5 | #include "leanstore/utils/result.hpp" 6 | 7 | namespace leanstore::storage { 8 | 9 | AsyncWriteBuffer::AsyncWriteBuffer(int fd, uint64_t page_size, uint64_t max_batch_size) 10 | : fd_(fd), 11 | page_size_(page_size), 12 | aio_(max_batch_size), 13 | write_buffer_(page_size * max_batch_size), 14 | write_commands_(max_batch_size) { 15 | } 16 | 17 | AsyncWriteBuffer::~AsyncWriteBuffer() { 18 | } 19 | 20 | bool AsyncWriteBuffer::IsFull() { 21 | return aio_.IsFull(); 22 | } 23 | 24 | void AsyncWriteBuffer::Add(const BufferFrame& bf) { 25 | LS_DCHECK(uint64_t(&bf) % 512 == 0, "BufferFrame is not aligned to 512 bytes"); 26 | 27 | // record the written buffer frame and page id for later use 28 | auto page_id = bf.header_.page_id_; 29 | auto slot = aio_.GetNumRequests(); 30 | write_commands_[slot].Reset(&bf, page_id); 31 | 32 | // copy the page content to write buffer 33 | auto* buffer = copy_to_buffer(&bf.page_, slot); 34 | 35 | aio_.PrepareWrite(fd_, buffer, page_size_, page_size_ * page_id); 36 | } 37 | 38 | Result AsyncWriteBuffer::SubmitAll() { 39 | return aio_.SubmitAll(); 40 | } 41 | 42 | Result AsyncWriteBuffer::WaitAll() { 43 | return aio_.WaitAll(); 44 | } 45 | 46 | void AsyncWriteBuffer::IterateFlushedBfs( 47 | std::function callback, 48 | uint64_t num_flushed_bfs) { 49 | for (uint64_t i = 0; i < num_flushed_bfs; i++) { 50 | const auto slot = (reinterpret_cast(aio_.GetIoEvent(i)->data) - 51 | reinterpret_cast(write_buffer_.Get())) / 52 | page_size_; 53 | auto* flushed_page = reinterpret_cast(get_write_buffer(slot)); 54 | auto flushed_psn = flushed_page->psn_; 55 | auto* flushed_bf = write_commands_[slot].bf_; 56 | callback(*const_cast(flushed_bf), flushed_psn); 57 | } 58 | } 59 | 60 | } // namespace leanstore::storage 61 | -------------------------------------------------------------------------------- /src/buffer-manager/partition.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/buffer-manager/partition.hpp" 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace leanstore { 8 | namespace storage { 9 | 10 | void* MallocHuge(size_t size) { 11 | void* p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 12 | madvise(p, size, MADV_HUGEPAGE); 13 | memset(p, 0, size); 14 | return p; 15 | } 16 | 17 | HashTable::Entry::Entry(PID key) : key_(key) { 18 | } 19 | 20 | HashTable::HashTable(uint64_t size_in_bits) { 21 | uint64_t size = (1ull << size_in_bits); 22 | mask_ = size - 1; 23 | entries_ = (Entry**)MallocHuge(size * sizeof(Entry*)); 24 | } 25 | 26 | uint64_t HashTable::HashKey(PID k) { 27 | // MurmurHash64A 28 | const uint64_t m = 0xc6a4a7935bd1e995ull; 29 | const int r = 47; 30 | uint64_t h = 0x8445d61a4e774912ull ^ (8 * m); 31 | k *= m; 32 | k ^= k >> r; 33 | k *= m; 34 | h ^= k; 35 | h *= m; 36 | h ^= h >> r; 37 | h *= m; 38 | h ^= h >> r; 39 | return h; 40 | } 41 | 42 | IOFrame& HashTable::Insert(PID key) { 43 | auto* e = new Entry(key); 44 | uint64_t pos = HashKey(key) & mask_; 45 | e->next_ = entries_[pos]; 46 | entries_[pos] = e; 47 | return e->value_; 48 | } 49 | 50 | HashTable::Handler HashTable::Lookup(PID key) { 51 | uint64_t pos = HashKey(key) & mask_; 52 | Entry** e_ptr = entries_ + pos; 53 | Entry* e = *e_ptr; // e is only here for readability 54 | while (e) { 55 | if (e->key_ == key) 56 | return {e_ptr}; 57 | e_ptr = &(e->next_); 58 | e = e->next_; 59 | } 60 | return {nullptr}; 61 | } 62 | 63 | void HashTable::Remove(HashTable::Handler& handler) { 64 | Entry* to_delete = *handler.holder_; 65 | *handler.holder_ = (*handler.holder_)->next_; 66 | delete to_delete; 67 | } 68 | 69 | void HashTable::Remove(uint64_t key) { 70 | auto handler = Lookup(key); 71 | assert(handler); 72 | Remove(handler); 73 | } 74 | 75 | bool HashTable::Has(uint64_t key) { 76 | uint64_t pos = HashKey(key) & mask_; 77 | auto* e = entries_[pos]; 78 | while (e) { 79 | if (e->key_ == key) 80 | return true; 81 | e = e->next_; 82 | } 83 | return false; 84 | } 85 | 86 | } // namespace storage 87 | } // namespace leanstore 88 | -------------------------------------------------------------------------------- /src/concurrency/cr_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/concurrency/cr_manager.hpp" 2 | 3 | #include "leanstore/btree/basic_kv.hpp" 4 | #include "leanstore/concurrency/group_committer.hpp" 5 | #include "leanstore/concurrency/history_storage.hpp" 6 | #include "leanstore/concurrency/worker_context.hpp" 7 | #include "leanstore/concurrency/worker_thread.hpp" 8 | #include "leanstore/lean_store.hpp" 9 | #include "leanstore/utils/log.hpp" 10 | 11 | #include 12 | #include 13 | 14 | namespace leanstore::cr { 15 | 16 | CRManager::CRManager(leanstore::LeanStore* store) : store_(store), group_committer_(nullptr) { 17 | auto* store_option = store->store_option_; 18 | // start all worker threads 19 | worker_ctxs_.resize(store_option->worker_threads_); 20 | worker_threads_.reserve(store_option->worker_threads_); 21 | for (uint64_t worker_id = 0; worker_id < store_option->worker_threads_; worker_id++) { 22 | auto worker_thread = std::make_unique(store, worker_id, worker_id); 23 | worker_thread->Start(); 24 | 25 | // create thread-local transaction executor on each worker thread 26 | worker_thread->SetJob([&]() { 27 | WorkerContext::sTlsWorkerCtx = 28 | std::make_unique(worker_id, worker_ctxs_, store_); 29 | WorkerContext::sTlsWorkerCtxRaw = WorkerContext::sTlsWorkerCtx.get(); 30 | worker_ctxs_[worker_id] = WorkerContext::sTlsWorkerCtx.get(); 31 | }); 32 | worker_thread->Wait(); 33 | worker_threads_.emplace_back(std::move(worker_thread)); 34 | } 35 | 36 | // start group commit thread 37 | if (store_->store_option_->enable_wal_) { 38 | const int cpu = store_option->worker_threads_; 39 | group_committer_ = std::make_unique(store_, store_->wal_fd_, worker_ctxs_, cpu); 40 | group_committer_->Start(); 41 | } 42 | 43 | // create history storage for each worker 44 | // History tree should be created after worker thread and group committer are 45 | // started. 46 | if (store_option->worker_threads_ > 0) { 47 | worker_threads_[0]->SetJob([&]() { setup_history_storage4_each_worker(); }); 48 | worker_threads_[0]->Wait(); 49 | } 50 | } 51 | 52 | void CRManager::Stop() { 53 | group_committer_->Stop(); 54 | 55 | for (auto& worker_thread : worker_threads_) { 56 | worker_thread->Stop(); 57 | } 58 | worker_threads_.clear(); 59 | } 60 | 61 | CRManager::~CRManager() { 62 | Stop(); 63 | } 64 | 65 | void CRManager::setup_history_storage4_each_worker() { 66 | for (uint64_t i = 0; i < store_->store_option_->worker_threads_; i++) { 67 | // setup update tree 68 | std::string update_btree_name = std::format("_history_tree_{}_updates", i); 69 | auto res = storage::btree::BasicKV::Create( 70 | store_, update_btree_name, BTreeConfig{.enable_wal_ = false, .use_bulk_insert_ = true}); 71 | if (!res) { 72 | Log::Fatal("Failed to set up update history tree, updateBtreeName={}, error={}", 73 | update_btree_name, res.error().ToString()); 74 | } 75 | auto* update_index = res.value(); 76 | 77 | // setup delete tree 78 | std::string remove_btree_name = std::format("_history_tree_{}_removes", i); 79 | res = storage::btree::BasicKV::Create( 80 | store_, remove_btree_name, BTreeConfig{.enable_wal_ = false, .use_bulk_insert_ = true}); 81 | if (!res) { 82 | Log::Fatal("Failed to set up remove history tree, removeBtreeName={}, error={}", 83 | remove_btree_name, res.error().ToString()); 84 | } 85 | auto* remove_index = res.value(); 86 | worker_ctxs_[i]->cc_.history_storage_.SetUpdateIndex(update_index); 87 | worker_ctxs_[i]->cc_.history_storage_.SetRemoveIndex(remove_index); 88 | } 89 | } 90 | 91 | constexpr char kKeyWalSize[] = "wal_size"; 92 | constexpr char kKeyGlobalUsrTso[] = "global_user_tso"; 93 | constexpr char kKeyGlobalSysTso[] = "global_system_tso"; 94 | 95 | StringMap CRManager::Serialize() { 96 | StringMap map; 97 | map[kKeyWalSize] = std::to_string(group_committer_->wal_size_); 98 | map[kKeyGlobalUsrTso] = std::to_string(store_->usr_tso_.load()); 99 | map[kKeyGlobalSysTso] = std::to_string(store_->sys_tso_.load()); 100 | return map; 101 | } 102 | 103 | void CRManager::Deserialize(StringMap map) { 104 | group_committer_->wal_size_ = std::stoull(map[kKeyWalSize]); 105 | store_->usr_tso_ = std::stoull(map[kKeyGlobalUsrTso]); 106 | store_->sys_tso_ = std::stoull(map[kKeyGlobalSysTso]); 107 | 108 | store_->crmanager_->global_wmk_info_.wmk_of_all_tx_ = store_->usr_tso_.load(); 109 | } 110 | 111 | } // namespace leanstore::cr 112 | -------------------------------------------------------------------------------- /src/concurrency/logging.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/concurrency/logging.hpp" 2 | 3 | #include "leanstore/concurrency/wal_entry.hpp" 4 | #include "leanstore/concurrency/worker_context.hpp" 5 | #include "leanstore/exceptions.hpp" 6 | #include "leanstore/utils/log.hpp" 7 | #include "utils/to_json.hpp" 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | namespace leanstore::cr { 15 | 16 | uint32_t Logging::wal_contiguous_free_space() { 17 | const auto flushed = wal_flushed_.load(); 18 | if (flushed <= wal_buffered_) { 19 | return wal_buffer_size_ - wal_buffered_; 20 | } 21 | return flushed - wal_buffered_; 22 | } 23 | 24 | void Logging::ReserveContiguousBuffer(uint32_t bytes_required) { 25 | // Spin until there is enough space. The wal ring buffer space is reclaimed 26 | // when the group commit thread commits the written wal entries. 27 | while (true) { 28 | const auto flushed = wal_flushed_.load(); 29 | if (flushed <= wal_buffered_) { 30 | // carraige return, consume the last bytes from wal_buffered_ to the end 31 | if (wal_buffer_size_ - wal_buffered_ < bytes_required) { 32 | WriteWalCarriageReturn(); 33 | continue; 34 | } 35 | // Have enough space from wal_buffered_ to the end 36 | return; 37 | } 38 | 39 | if (flushed - wal_buffered_ < bytes_required) { 40 | // wait for group commit thread to commit the written wal entries 41 | continue; 42 | } 43 | return; 44 | } 45 | } 46 | 47 | void Logging::WriteWalTxAbort() { 48 | // Reserve space 49 | auto size = sizeof(WalTxAbort); 50 | ReserveContiguousBuffer(size); 51 | 52 | // Initialize a WalTxAbort 53 | auto* data = wal_buffer_ + wal_buffered_; 54 | std::memset(data, 0, size); 55 | auto* entry [[maybe_unused]] = new (data) WalTxAbort(size); 56 | 57 | // Submit the WalTxAbort to group committer 58 | wal_buffered_ += size; 59 | publish_wal_flush_req(); 60 | 61 | LS_DLOG("WriteWalTxAbort, workerId={}, startTs={}, walJson={}", WorkerContext::My().worker_id_, 62 | WorkerContext::My().active_tx_.start_ts_, utils::ToJsonString(entry)); 63 | } 64 | 65 | void Logging::WriteWalTxFinish() { 66 | // Reserve space 67 | auto size = sizeof(WalTxFinish); 68 | ReserveContiguousBuffer(size); 69 | 70 | // Initialize a WalTxFinish 71 | auto* data = wal_buffer_ + wal_buffered_; 72 | std::memset(data, 0, size); 73 | auto* entry [[maybe_unused]] = new (data) WalTxFinish(WorkerContext::My().active_tx_.start_ts_); 74 | 75 | // Submit the WalTxAbort to group committer 76 | wal_buffered_ += size; 77 | publish_wal_flush_req(); 78 | 79 | LS_DLOG("WriteWalTxFinish, workerId={}, startTs={}, walJson={}", WorkerContext::My().worker_id_, 80 | WorkerContext::My().active_tx_.start_ts_, utils::ToJsonString(entry)); 81 | } 82 | 83 | void Logging::WriteWalCarriageReturn() { 84 | LS_DCHECK(wal_flushed_ <= wal_buffered_, 85 | "CarriageReturn should only used for the last bytes in the wal buffer"); 86 | auto entry_size = wal_buffer_size_ - wal_buffered_; 87 | auto* entry_ptr = wal_buffer_ + wal_buffered_; 88 | new (entry_ptr) WalCarriageReturn(entry_size); 89 | wal_buffered_ = 0; 90 | publish_wal_buffered_offset(); 91 | } 92 | 93 | void Logging::SubmitWALEntryComplex(uint64_t total_size) { 94 | active_walentry_complex_->crc32_ = active_walentry_complex_->ComputeCRC32(); 95 | wal_buffered_ += total_size; 96 | publish_wal_flush_req(); 97 | 98 | LS_DLOG("SubmitWal, workerId={}, startTs={}, walJson={}", WorkerContext::My().worker_id_, 99 | WorkerContext::My().active_tx_.start_ts_, utils::ToJsonString(active_walentry_complex_)); 100 | } 101 | 102 | void Logging::publish_wal_buffered_offset() { 103 | wal_flush_req_.UpdateAttribute(&WalFlushReq::wal_buffered_, wal_buffered_); 104 | } 105 | 106 | void Logging::publish_wal_flush_req() { 107 | WalFlushReq current(wal_buffered_, sys_tx_writtern_, WorkerContext::My().active_tx_.start_ts_); 108 | wal_flush_req_.Set(current); 109 | } 110 | 111 | // Called by worker, so concurrent writes on the buffer 112 | void Logging::IterateCurrentTxWALs(std::function callback) { 113 | uint64_t cursor = tx_wal_begin_; 114 | while (cursor != wal_buffered_) { 115 | const WalEntry& entry = *reinterpret_cast(wal_buffer_ + cursor); 116 | DEBUG_BLOCK() { 117 | if (entry.type_ == WalEntry::Type::kComplex) { 118 | reinterpret_cast(&entry)->CheckCRC(); 119 | } 120 | } 121 | 122 | if (entry.type_ == WalEntry::Type::kCarriageReturn) { 123 | cursor = 0; 124 | } else { 125 | callback(entry); 126 | cursor += WalEntry::Size(&entry); 127 | } 128 | } 129 | } 130 | 131 | } // namespace leanstore::cr 132 | -------------------------------------------------------------------------------- /src/leanstore-c/store_option.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore-c/store_option.h" 2 | 3 | #include 4 | 5 | /// The default store option. 6 | static const StoreOption kDefaultStoreOption = { 7 | // Store related options 8 | .create_from_scratch_ = true, 9 | .store_dir_ = "~/.leanstore", 10 | 11 | // log related options 12 | .log_level_ = LogLevel::kInfo, 13 | 14 | // Worker thread related options 15 | .worker_threads_ = 4, 16 | .wal_buffer_size_ = 10 * 1024 * 1024, 17 | 18 | // Buffer pool related options 19 | .page_size_ = 4 * 1024, 20 | .buffer_frame_size_ = 512 + 4 * 1024, 21 | .num_partitions_ = 64, 22 | .buffer_pool_size_ = 1ull * 1024 * 1024 * 1024, 23 | .free_pct_ = 1, 24 | .num_buffer_providers_ = 1, 25 | .buffer_write_batch_size_ = 1024, 26 | .enable_buffer_crc_check_ = false, 27 | .buffer_frame_recycle_batch_size_ = 64, 28 | .enable_reclaim_page_ids_ = true, 29 | 30 | // Logging and recovery related options 31 | .enable_wal_ = true, 32 | .enable_wal_fsync_ = false, 33 | 34 | // Generic BTree related options 35 | .enable_bulk_insert_ = false, 36 | .enable_xmerge_ = true, 37 | .xmerge_k_ = 5, 38 | .xmerge_target_pct_ = 80, 39 | .enable_contention_split_ = true, 40 | .contention_split_probility_ = 14, 41 | .contention_split_sample_probability_ = 7, 42 | .contention_split_threshold_pct_ = 1, 43 | .btree_hints_ = 1, 44 | .enable_head_optimization_ = true, 45 | .enable_optimistic_scan_ = true, 46 | 47 | // Transaction related options 48 | .enable_long_running_tx_ = true, 49 | .enable_fat_tuple_ = false, 50 | .enable_gc_ = true, 51 | .enable_eager_gc_ = false, 52 | 53 | // Metrics related options 54 | .enable_cpu_counters_ = true, 55 | .enable_time_measure_ = false, 56 | .enable_perf_events_ = false, 57 | }; 58 | 59 | StoreOption* CreateStoreOption(const char* store_dir) { 60 | // create a new store option with default values 61 | StoreOption* option = new StoreOption(); 62 | *option = kDefaultStoreOption; 63 | 64 | if (store_dir == nullptr) { 65 | store_dir = kDefaultStoreOption.store_dir_; 66 | } 67 | 68 | // override the default store directory 69 | char* store_dir_copy = new char[strlen(store_dir) + 1]; 70 | memcpy(store_dir_copy, store_dir, strlen(store_dir)); 71 | store_dir_copy[strlen(store_dir)] = '\0'; 72 | option->store_dir_ = store_dir_copy; 73 | 74 | return option; 75 | } 76 | 77 | StoreOption* CreateStoreOptionFrom(const StoreOption* store_dir) { 78 | StoreOption* option = new StoreOption(); 79 | *option = *store_dir; 80 | 81 | // deep copy the store directory 82 | char* store_dir_copy = new char[strlen(store_dir->store_dir_) + 1]; 83 | memcpy(store_dir_copy, store_dir->store_dir_, strlen(store_dir->store_dir_)); 84 | store_dir_copy[strlen(store_dir->store_dir_)] = '\0'; 85 | option->store_dir_ = store_dir_copy; 86 | 87 | return option; 88 | } 89 | 90 | void DestroyStoreOption(const StoreOption* option) { 91 | if (option != nullptr) { 92 | if (option->store_dir_ != nullptr) { 93 | delete[] option->store_dir_; 94 | } 95 | delete option; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/leanstore.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=@CMAKE_INSTALL_PREFIX@ 3 | libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ 4 | includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ 5 | 6 | Name: @PROJECT_NAME@ 7 | Description: @PROJECT_DESCRIPTION@ 8 | Version: @PROJECT_VERSION@ 9 | 10 | Requires: 11 | Libs: -L${libdir} -lleanstore 12 | Cflags: -I${includedir} -------------------------------------------------------------------------------- /src/telemetry/metrics_http_exposer.cpp: -------------------------------------------------------------------------------- 1 | #include "telemetry/metrics_http_exposer.hpp" 2 | 3 | namespace leanstore::telemetry { 4 | 5 | MetricsHttpExposer::MetricsHttpExposer(int32_t port) 6 | : UserThread(nullptr, "MetricsExposer"), 7 | port_(port) { 8 | server_.new_task_queue = [] { return new httplib::ThreadPool(1); }; 9 | 10 | server_.Get("/heap", 11 | [&](const httplib::Request& req, httplib::Response& res) { handle_heap(req, res); }); 12 | 13 | server_.Get("/profile", [&](const httplib::Request& req, httplib::Response& res) { 14 | handle_profile(req, res); 15 | }); 16 | } 17 | 18 | } // namespace leanstore::telemetry -------------------------------------------------------------------------------- /src/telemetry/metrics_http_exposer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "leanstore/utils/random_generator.hpp" 4 | #include "leanstore/utils/user_thread.hpp" 5 | 6 | #ifdef ENABLE_PROFILING 7 | #include 8 | #include 9 | #endif 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | namespace leanstore::telemetry { 17 | 18 | const std::string kContentType("text/plain; version=0.0.4; charset=utf-8"); 19 | 20 | class MetricsHttpExposer : public utils::UserThread { 21 | private: 22 | /// The http server 23 | httplib::Server server_; 24 | 25 | /// The port to expose metrics 26 | int32_t port_; 27 | 28 | public: 29 | MetricsHttpExposer(int32_t port); 30 | 31 | ~MetricsHttpExposer() override { 32 | server_.stop(); 33 | } 34 | 35 | protected: 36 | void run_impl() override { 37 | while (keep_running_) { 38 | server_.listen("0.0.0.0", port_); 39 | } 40 | } 41 | 42 | private: 43 | void handle_heap(const httplib::Request& req [[maybe_unused]], httplib::Response& res) { 44 | #ifdef ENABLE_PROFILING 45 | // get the profiling time in seconds from the query 46 | auto seconds_str = req.get_param_value("seconds"); 47 | auto seconds = seconds_str.empty() ? 10 : std::stoi(seconds_str); 48 | 49 | // generate a random file name 50 | auto perf_file = create_random_file(); 51 | 52 | // profile for the given seconds 53 | HeapProfilerStart(perf_file.c_str()); 54 | SCOPED_DEFER({ 55 | HeapProfilerStop(); 56 | std::remove(perf_file.c_str()); 57 | }); 58 | sleep(seconds); 59 | 60 | // dump the profile and return it 61 | res.set_content(GetHeapProfile(), kContentType); 62 | return; 63 | #else 64 | res.set_content("not implemented", kContentType); 65 | return; 66 | #endif 67 | } 68 | 69 | void handle_profile(const httplib::Request& req [[maybe_unused]], httplib::Response& res) { 70 | 71 | #ifdef ENABLE_PROFILING 72 | // get the profiling time in seconds from the query 73 | auto seconds_str = req.get_param_value("seconds"); 74 | auto seconds = seconds_str.empty() ? 10 : std::stoi(seconds_str); 75 | 76 | // generate a random file name 77 | auto perf_file = create_random_file(); 78 | SCOPED_DEFER(std::remove(perf_file.c_str())); 79 | 80 | // profile for the given seconds 81 | ProfilerStart(perf_file.c_str()); 82 | sleep(seconds); 83 | ProfilerStop(); 84 | ProfilerFlush(); 85 | 86 | // read the file and return it 87 | read_profile(perf_file, res); 88 | return; 89 | #else 90 | res.set_content("not implemented", kContentType); 91 | #endif 92 | } 93 | 94 | std::string create_random_file() { 95 | auto perf_file = 96 | std::format("/tmp/leanstore-{}.prof", utils::RandomGenerator::RandAlphString(8)); 97 | std::ofstream file(perf_file); 98 | file.close(); 99 | return perf_file; 100 | } 101 | 102 | void read_profile(const std::string& file, httplib::Response& res) { 103 | std::ifstream stream(file); 104 | std::stringstream buffer; 105 | buffer << stream.rdbuf(); 106 | res.set_content(buffer.str(), kContentType); 107 | } 108 | }; 109 | 110 | } // namespace leanstore::telemetry -------------------------------------------------------------------------------- /src/utils/fnv_hash.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/utils/fnv_hash.hpp" 2 | 3 | namespace leanstore { 4 | namespace utils { 5 | 6 | uint64_t FNV::Hash(uint64_t val) { 7 | // from http://en.wikipedia.org/wiki/Fowler_Noll_Vo_hash 8 | uint64_t hash_val = kFnvOffsetBasis64; 9 | for (int i = 0; i < 8; i++) { 10 | uint64_t octet = val & 0x00ff; 11 | val = val >> 8; 12 | 13 | hash_val = hash_val ^ octet; 14 | hash_val = hash_val * kFnvPrime64; 15 | } 16 | return hash_val; 17 | } 18 | 19 | } // namespace utils 20 | } // namespace leanstore 21 | -------------------------------------------------------------------------------- /src/utils/jump_mu.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/utils/jump_mu.hpp" 2 | 3 | #include "leanstore/utils/log.hpp" 4 | 5 | namespace jumpmu { 6 | 7 | __thread int tls_num_jump_points = 0; 8 | __thread jmp_buf tls_jump_points[JUMPMU_STACK_SIZE]; 9 | __thread int tls_jump_point_num_stack_objs[JUMPMU_STACK_SIZE]; 10 | 11 | __thread int tls_num_stack_objs = 0; 12 | __thread void* tls_objs[JUMPMU_STACK_OBJECTS_LIMIT]; 13 | __thread void (*tls_obj_dtors[JUMPMU_STACK_OBJECTS_LIMIT])(void*); 14 | 15 | void Jump() { 16 | LS_DCHECK(tls_num_jump_points > 0, "tlsNumJumpPoints={}", tls_num_jump_points); 17 | LS_DCHECK(tls_num_stack_objs >= 0, "tlsNumStackObjs={}", tls_num_stack_objs); 18 | auto num_jump_stack_objs = tls_jump_point_num_stack_objs[tls_num_jump_points - 1]; 19 | 20 | // Release resource hold by stack objects in reverse (FILO) order. 21 | if (num_jump_stack_objs < tls_num_stack_objs) { 22 | int first = num_jump_stack_objs; 23 | int last = tls_num_stack_objs - 1; 24 | for (int i = last; i >= first; i--) { 25 | tls_obj_dtors[i](tls_objs[i]); 26 | } 27 | } 28 | 29 | // Jump to the preset jump point 30 | auto& jump_point = jumpmu::tls_jump_points[jumpmu::tls_num_jump_points - 1]; 31 | LS_DLOG("Jump to jump point {} ({} stack objects, {} jump stack objects)", 32 | jumpmu::tls_num_jump_points - 1, jumpmu::tls_num_stack_objs, 33 | jumpmu::tls_jump_point_num_stack_objs[jumpmu::tls_num_jump_points - 1]); 34 | tls_num_jump_points--; 35 | longjmp(jump_point, 1); 36 | } 37 | 38 | } // namespace jumpmu 39 | -------------------------------------------------------------------------------- /src/utils/log.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/utils/log.hpp" 2 | 3 | #include "leanstore/utils/defer.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace leanstore { 14 | 15 | void Log::Init(const StoreOption* option) { 16 | if (sInited) { 17 | return; 18 | } 19 | 20 | std::unique_lock write_lock(sInitMutex); 21 | 22 | auto log_path = std::string(option->store_dir_) + "/db.log"; 23 | auto logger = spdlog::basic_logger_mt("basic_logger", log_path.c_str()); 24 | 25 | SCOPED_DEFER({ 26 | spdlog::set_default_logger(logger); 27 | spdlog::flush_every(std::chrono::seconds(3)); 28 | sInited = true; 29 | }); 30 | 31 | // set log pattern 32 | logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] %v"); 33 | 34 | // set flush strategy 35 | logger->flush_on(spdlog::level::info); 36 | 37 | // set log level 38 | switch (option->log_level_) { 39 | case LogLevel::kDebug: { 40 | logger->set_level(spdlog::level::debug); 41 | break; 42 | } 43 | case LogLevel::kInfo: { 44 | logger->set_level(spdlog::level::info); 45 | break; 46 | } 47 | case LogLevel::kWarn: { 48 | logger->set_level(spdlog::level::warn); 49 | break; 50 | } 51 | case LogLevel::kError: { 52 | logger->set_level(spdlog::level::err); 53 | break; 54 | } 55 | default: { 56 | std::cerr << std::format("unsupported log level: {}", static_cast(option->log_level_)); 57 | std::abort(); 58 | } 59 | } 60 | } 61 | 62 | void Log::DebugCheck(bool condition, const std::string& msg) { 63 | if (!condition) { 64 | Fatal(msg); 65 | } 66 | } 67 | 68 | void Log::Debug(const std::string& msg) { 69 | spdlog::debug(msg); 70 | } 71 | 72 | void Log::Info(const std::string& msg) { 73 | spdlog::info(msg); 74 | } 75 | 76 | void Log::Warn(const std::string& msg) { 77 | spdlog::warn(msg); 78 | } 79 | 80 | void Log::Error(const std::string& msg) { 81 | spdlog::error(msg); 82 | } 83 | 84 | void Log::Fatal(const std::string& msg) { 85 | spdlog::critical(msg); 86 | std::abort(); 87 | } 88 | 89 | } // namespace leanstore -------------------------------------------------------------------------------- /src/utils/misc.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/utils/misc.hpp" 2 | 3 | #include 4 | 5 | namespace leanstore::utils { 6 | 7 | uint32_t CRC(const uint8_t* src, uint64_t size) { 8 | return crc32c::Crc32c(src, size); 9 | } 10 | 11 | } // namespace leanstore::utils 12 | -------------------------------------------------------------------------------- /src/utils/parallelize.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/utils/parallelize.hpp" 2 | 3 | #include "leanstore/utils/log.hpp" 4 | #include "leanstore/utils/user_thread.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace leanstore::utils { 11 | 12 | void Parallelize::Range( 13 | uint64_t num_threads, uint64_t num_jobs, 14 | std::function job_handler) { 15 | auto* store = tls_store; 16 | std::vector threads; 17 | const uint64_t jobs_per_thread = num_jobs / num_threads; 18 | LS_DCHECK(jobs_per_thread > 0, "Jobs per thread must be > 0"); 19 | 20 | for (uint64_t thread_id = 0; thread_id < num_threads; thread_id++) { 21 | uint64_t begin = (thread_id * jobs_per_thread); 22 | uint64_t end = begin + (jobs_per_thread); 23 | if (thread_id == num_threads - 1) { 24 | end = num_jobs; 25 | } 26 | 27 | threads.emplace_back( 28 | [&](uint64_t begin, uint64_t end) { 29 | tls_store = store; 30 | job_handler(thread_id, begin, end); 31 | }, 32 | begin, end); 33 | } 34 | 35 | // wait all threads to finish 36 | for (auto& thread : threads) { 37 | thread.join(); 38 | } 39 | } 40 | 41 | void Parallelize::ParallelRange( 42 | uint64_t num_jobs, std::function job_handler) { 43 | auto* store = tls_store; 44 | std::vector threads; 45 | uint64_t num_thread = std::thread::hardware_concurrency(); 46 | uint64_t jobs_per_thread = num_jobs / num_thread; 47 | uint64_t num_remaining = num_jobs % num_thread; 48 | uint64_t num_proceed_tasks = 0; 49 | if (jobs_per_thread < num_thread) { 50 | num_thread = num_remaining; 51 | } 52 | 53 | // To balance the workload among all threads: 54 | // - the first numRemaining threads process jobsPerThread+1 tasks 55 | // - other threads process jobsPerThread tasks 56 | for (uint64_t i = 0; i < num_thread; i++) { 57 | uint64_t begin = num_proceed_tasks; 58 | uint64_t end = begin + jobs_per_thread; 59 | if (num_remaining > 0) { 60 | end++; 61 | num_remaining--; 62 | } 63 | num_proceed_tasks = end; 64 | threads.emplace_back( 65 | [&](uint64_t begin, uint64_t end) { 66 | tls_store = store; 67 | job_handler(begin, end); 68 | }, 69 | begin, end); 70 | } 71 | for (auto& thread : threads) { 72 | thread.join(); 73 | } 74 | } 75 | 76 | } // namespace leanstore::utils 77 | -------------------------------------------------------------------------------- /src/utils/random_generator.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/utils/random_generator.hpp" 2 | 3 | #include 4 | 5 | namespace leanstore { 6 | namespace utils { 7 | 8 | static std::atomic sMtCounter = 0; 9 | 10 | MersenneTwister::MersenneTwister(uint64_t seed) : mti_(sNn + 1) { 11 | init(seed + (sMtCounter++)); 12 | } 13 | 14 | void MersenneTwister::init(uint64_t seed) { 15 | mt_[0] = seed; 16 | for (mti_ = 1; mti_ < sNn; mti_++) 17 | mt_[mti_] = (6364136223846793005ULL * (mt_[mti_ - 1] ^ (mt_[mti_ - 1] >> 62)) + mti_); 18 | } 19 | 20 | uint64_t MersenneTwister::Rand() { 21 | uint64_t x; 22 | static const uint64_t kMag01[2] = {0ULL, sMatrixA}; 23 | 24 | if (mti_ >= sNn) { /* generate sNn words at one time */ 25 | int i; 26 | for (i = 0; i < sNn - sMm; i++) { 27 | x = (mt_[i] & sUm) | (mt_[i + 1] & sLm); 28 | mt_[i] = mt_[i + sMm] ^ (x >> 1) ^ kMag01[(int)(x & 1ULL)]; 29 | } 30 | for (; i < sNn - 1; i++) { 31 | x = (mt_[i] & sUm) | (mt_[i + 1] & sLm); 32 | mt_[i] = mt_[i + (sMm - sNn)] ^ (x >> 1) ^ kMag01[(int)(x & 1ULL)]; 33 | } 34 | x = (mt_[sNn - 1] & sUm) | (mt_[0] & sLm); 35 | mt_[sNn - 1] = mt_[sMm - 1] ^ (x >> 1) ^ kMag01[(int)(x & 1ULL)]; 36 | 37 | mti_ = 0; 38 | } 39 | 40 | x = mt_[mti_++]; 41 | 42 | x ^= (x >> 29) & 0x5555555555555555ULL; 43 | x ^= (x << 17) & 0x71D67FFFEDA60000ULL; 44 | x ^= (x << 37) & 0xFFF7EEE000000000ULL; 45 | x ^= (x >> 43); 46 | 47 | return x; 48 | } 49 | 50 | } // namespace utils 51 | } // namespace leanstore 52 | -------------------------------------------------------------------------------- /src/utils/scrambled_zipf_generator.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/utils/scrambled_zipf_generator.hpp" 2 | 3 | #include "leanstore/utils/fnv_hash.hpp" 4 | 5 | namespace leanstore::utils { 6 | 7 | uint64_t ScrambledZipfGenerator::rand() { 8 | uint64_t zipf_value = zipf_generator.rand(); 9 | return min + (FNV::Hash(zipf_value) % n); 10 | } 11 | 12 | } // namespace leanstore::utils 13 | -------------------------------------------------------------------------------- /src/utils/zipf_generator.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/utils/zipf_generator.hpp" 2 | 3 | #include "leanstore/utils/random_generator.hpp" 4 | 5 | using namespace std; 6 | namespace leanstore { 7 | namespace utils { 8 | 9 | ZipfGenerator::ZipfGenerator(uint64_t ex_n, double theta) : n(ex_n - 1), theta(theta) { 10 | alpha = 1.0 / (1.0 - theta); 11 | zetan = zeta(n, theta); 12 | eta = (1.0 - std::pow(2.0 / n, 1.0 - theta)) / (1.0 - zeta(2, theta) / zetan); 13 | } 14 | 15 | double ZipfGenerator::zeta(uint64_t n, double theta) { 16 | double ans = 0; 17 | for (uint64_t i = 1; i <= n; i++) 18 | ans += std::pow(1.0 / n, theta); 19 | return ans; 20 | } 21 | 22 | uint64_t ZipfGenerator::rand() { 23 | double constant = 1000000000000000000.0; 24 | uint64_t i = RandomGenerator::RandU64(0, 1000000000000000001); 25 | double u = static_cast(i) / constant; 26 | // return (uint64_t)u; 27 | double uz = u * zetan; 28 | if (uz < 1) { 29 | return 1; 30 | } 31 | if (uz < (1 + std::pow(0.5, theta))) 32 | return 2; 33 | uint64_t ret = 1 + (long)(n * pow(eta * u - eta + 1, alpha)); 34 | return ret; 35 | } 36 | 37 | } // namespace utils 38 | } // namespace leanstore 39 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(GoogleTest) 2 | enable_testing() 3 | 4 | set(TEST_EXECUTABLES "") 5 | 6 | # Define a cmake function to add a test file linked with Google Test 7 | function(leanstore_add_test TARGET_NAME) 8 | add_executable( 9 | ${TARGET_NAME} 10 | ${TARGET_NAME}.cpp 11 | ) 12 | target_link_libraries( 13 | ${TARGET_NAME} 14 | GTest::gtest 15 | GTest::gtest_main 16 | leanstore 17 | ) 18 | gtest_discover_tests(${TARGET_NAME}) 19 | list(APPEND TEST_EXECUTABLES ${TARGET_NAME}) 20 | endfunction(leanstore_add_test) 21 | 22 | 23 | # add test in dir 24 | function(leanstore_add_test_in_dir TARGET_DIR) 25 | # list all files in the directory 26 | file(GLOB_RECURSE TEST_FILES "${TARGET_DIR}/*.cpp") 27 | foreach(TEST_FILE ${TEST_FILES}) 28 | get_filename_component(TEST_NAME ${TEST_FILE} NAME_WE) 29 | add_executable( 30 | ${TEST_NAME} 31 | ${TEST_FILE} 32 | ) 33 | target_link_libraries( 34 | ${TEST_NAME} 35 | GTest::gtest 36 | GTest::gtest_main 37 | leanstore 38 | ) 39 | gtest_discover_tests(${TEST_NAME}) 40 | list(APPEND TEST_EXECUTABLES ${TEST_NAME}) 41 | endforeach() 42 | endfunction(leanstore_add_test_in_dir) 43 | 44 | 45 | # Add tests 46 | leanstore_add_test(recovery_test) 47 | leanstore_add_test(optimistic_guarded_test) 48 | leanstore_add_test(transaction_kv_test) 49 | leanstore_add_test(mvcc_test) 50 | leanstore_add_test(anomalies_test) 51 | leanstore_add_test(abort_test) 52 | leanstore_add_test(long_running_tx_test) 53 | 54 | # tests in sub-directories 55 | leanstore_add_test_in_dir(btree) 56 | leanstore_add_test_in_dir(buffer-manager) 57 | leanstore_add_test_in_dir(concurrency) 58 | leanstore_add_test_in_dir(sync) 59 | leanstore_add_test_in_dir(telemetry) 60 | -------------------------------------------------------------------------------- /tests/btree/b_tree_generic_test.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore-c/store_option.h" 2 | #include "leanstore/btree/basic_kv.hpp" 3 | #include "leanstore/btree/transaction_kv.hpp" 4 | #include "leanstore/buffer-manager/buffer_manager.hpp" 5 | #include "leanstore/concurrency/cr_manager.hpp" 6 | #include "leanstore/lean_store.hpp" 7 | #include "leanstore/utils/defer.hpp" 8 | #include "leanstore/utils/random_generator.hpp" 9 | 10 | #include 11 | 12 | #include 13 | 14 | using namespace leanstore::utils; 15 | using namespace leanstore::storage::btree; 16 | 17 | namespace leanstore::test { 18 | 19 | class BTreeGenericTest : public ::testing::Test { 20 | 21 | protected: 22 | std::unique_ptr store_; 23 | std::string tree_name_; 24 | TransactionKV* btree_; 25 | 26 | BTreeGenericTest() { 27 | auto* cur_test = ::testing::UnitTest::GetInstance()->current_test_info(); 28 | auto cur_test_name = 29 | std::string(cur_test->test_case_name()) + "_" + std::string(cur_test->name()); 30 | auto store_dir_str = "/tmp/leanstore/" + cur_test_name; 31 | StoreOption* option = CreateStoreOption(store_dir_str.c_str()); 32 | option->create_from_scratch_ = true; 33 | option->worker_threads_ = 2; 34 | auto res = LeanStore::Open(option); 35 | store_ = std::move(res.value()); 36 | } 37 | 38 | ~BTreeGenericTest() = default; 39 | 40 | void SetUp() override { 41 | tree_name_ = RandomGenerator::RandAlphString(10); 42 | store_->ExecSync(0, [&]() { 43 | auto res = store_->CreateTransactionKV(tree_name_); 44 | ASSERT_TRUE(res); 45 | btree_ = res.value(); 46 | ASSERT_NE(btree_, nullptr); 47 | }); 48 | } 49 | 50 | void TearDown() override { 51 | store_->ExecSync(1, [&]() { 52 | cr::WorkerContext::My().StartTx(); 53 | SCOPED_DEFER(cr::WorkerContext::My().CommitTx()); 54 | store_->DropTransactionKV(tree_name_); 55 | }); 56 | } 57 | }; 58 | 59 | TEST_F(BTreeGenericTest, GetSummary) { 60 | // insert 200 key-value pairs 61 | for (int i = 0; i < 200; i++) { 62 | store_->ExecSync(0, [&]() { 63 | auto key = RandomGenerator::RandAlphString(24) + std::to_string(i); 64 | auto val = RandomGenerator::RandAlphString(176); 65 | 66 | cr::WorkerContext::My().StartTx(); 67 | SCOPED_DEFER(cr::WorkerContext::My().CommitTx()); 68 | btree_->Insert(key, val); 69 | }); 70 | } 71 | 72 | auto* btree = dynamic_cast(btree_); 73 | ASSERT_NE(btree, nullptr); 74 | EXPECT_TRUE(btree->Summary().contains("entries=200")); 75 | } 76 | 77 | } // namespace leanstore::test -------------------------------------------------------------------------------- /tests/btree/b_tree_wal_payload_test.cpp: -------------------------------------------------------------------------------- 1 | #include "btree/core/b_tree_wal_payload.hpp" 2 | #include "leanstore/btree/core/b_tree_node.hpp" 3 | #include "leanstore/kv_interface.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace leanstore::storage::btree::test { 13 | 14 | class BTreeWalPayloadTest : public ::testing::Test { 15 | protected: 16 | std::string to_string(const rapidjson::Document* doc) { 17 | rapidjson::StringBuffer buffer; 18 | rapidjson::Writer writer(buffer); 19 | doc->Accept(writer); 20 | return std::string(buffer.GetString()); 21 | } 22 | }; 23 | 24 | TEST_F(BTreeWalPayloadTest, Size) { 25 | EXPECT_EQ(sizeof(WalPayload), 1); 26 | EXPECT_EQ(sizeof(WalInsert), 6); 27 | EXPECT_EQ(sizeof(WalTxInsert), 24); 28 | EXPECT_EQ(sizeof(WalUpdate), 6); 29 | EXPECT_EQ(sizeof(WalTxUpdate), 48); 30 | EXPECT_EQ(sizeof(WalRemove), 6); 31 | EXPECT_EQ(sizeof(WalTxRemove), 24); 32 | EXPECT_EQ(sizeof(WalInitPage), 32); 33 | EXPECT_EQ(sizeof(WalSplitRoot), 48); 34 | EXPECT_EQ(sizeof(WalSplitNonRoot), 40); 35 | } 36 | 37 | TEST_F(BTreeWalPayloadTest, ToJson) { 38 | std::unique_ptr wal = std::make_unique("", ""); 39 | auto wal_str = WalPayload::ToJsonString(wal.get()); 40 | EXPECT_TRUE(wal_str.contains("type_")); 41 | EXPECT_TRUE(wal_str.contains("kWalInsert")); 42 | EXPECT_TRUE(wal_str.contains("key_size_")); 43 | EXPECT_TRUE(wal_str.contains("key_")); 44 | EXPECT_TRUE(wal_str.contains("val_size_")); 45 | EXPECT_TRUE(wal_str.contains("val_")); 46 | 47 | wal = std::make_unique("", "", 0, 0, 0); 48 | wal_str = WalPayload::ToJsonString(wal.get()); 49 | EXPECT_TRUE(wal_str.contains("type_")); 50 | EXPECT_TRUE(wal_str.contains("kWalTxInsert")); 51 | EXPECT_TRUE(wal_str.contains("key_size_")); 52 | EXPECT_TRUE(wal_str.contains("key_")); 53 | EXPECT_TRUE(wal_str.contains("val_size_")); 54 | EXPECT_TRUE(wal_str.contains("val_")); 55 | 56 | wal = std::make_unique(); 57 | wal_str = WalPayload::ToJsonString(wal.get()); 58 | EXPECT_TRUE(wal_str.contains("type_")); 59 | EXPECT_TRUE(wal_str.contains("kWalUpdate")); 60 | EXPECT_TRUE(wal_str.contains("Not implemented")); 61 | 62 | UpdateDesc upate_desc; 63 | wal = std::make_unique("", upate_desc, 0, 0, 0, 0); 64 | wal_str = WalPayload::ToJsonString(wal.get()); 65 | EXPECT_TRUE(wal_str.contains("type_")); 66 | EXPECT_TRUE(wal_str.contains("kWalTxUpdate")); 67 | EXPECT_TRUE(wal_str.contains("Not implemented")); 68 | 69 | wal = std::make_unique("", ""); 70 | wal_str = WalPayload::ToJsonString(wal.get()); 71 | EXPECT_TRUE(wal_str.contains("type_")); 72 | EXPECT_TRUE(wal_str.contains("kWalRemove")); 73 | EXPECT_TRUE(wal_str.contains("Not implemented")); 74 | 75 | wal = std::make_unique("", "", 0, 0, 0); 76 | wal_str = WalPayload::ToJsonString(wal.get()); 77 | EXPECT_TRUE(wal_str.contains("type_")); 78 | EXPECT_TRUE(wal_str.contains("kWalTxRemove")); 79 | EXPECT_TRUE(wal_str.contains("Not implemented")); 80 | 81 | wal = std::make_unique(0, 0, false); 82 | wal_str = WalPayload::ToJsonString(wal.get()); 83 | EXPECT_TRUE(wal_str.contains("type_")); 84 | EXPECT_TRUE(wal_str.contains("kWalInitPage")); 85 | EXPECT_TRUE(wal_str.contains("tree_id_")); 86 | EXPECT_TRUE(wal_str.contains("is_leaf_")); 87 | 88 | BTreeNode::SeparatorInfo sep_info; 89 | wal = std::make_unique(0, 0, 0, 0, sep_info); 90 | wal_str = WalPayload::ToJsonString(wal.get()); 91 | EXPECT_TRUE(wal_str.contains("type_")); 92 | EXPECT_TRUE(wal_str.contains("kWalSplitRoot")); 93 | EXPECT_TRUE(wal_str.contains("new_left_")); 94 | EXPECT_TRUE(wal_str.contains("new_root_")); 95 | 96 | wal = std::make_unique(0, 0, 0, sep_info); 97 | wal_str = WalPayload::ToJsonString(wal.get()); 98 | EXPECT_TRUE(wal_str.contains("type_")); 99 | EXPECT_TRUE(wal_str.contains("kWalSplitNonRoot")); 100 | EXPECT_TRUE(wal_str.contains("parent_page_id_")); 101 | } 102 | 103 | } // namespace leanstore::storage::btree::test 104 | -------------------------------------------------------------------------------- /tests/buffer-manager/async_write_buffer_test.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/buffer-manager/async_write_buffer.hpp" 2 | #include "leanstore/buffer-manager/buffer_frame.hpp" 3 | #include "leanstore/buffer-manager/swip.hpp" 4 | #include "leanstore/utils/defer.hpp" 5 | #include "leanstore/utils/log.hpp" 6 | #include "leanstore/utils/misc.hpp" 7 | #include "leanstore/utils/random_generator.hpp" 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | namespace leanstore::storage::test { 20 | 21 | class AsyncWriteBufferTest : public ::testing::Test { 22 | protected: 23 | std::string test_dir_ = "/tmp/leanstore/AsyncWriteBufferTest"; 24 | 25 | struct BufferFrameHolder { 26 | utils::AlignedBuffer<512> buffer_; 27 | BufferFrame* bf_; 28 | 29 | BufferFrameHolder(size_t page_size, PID page_id) 30 | : buffer_(512 + page_size), 31 | bf_(new(buffer_.Get()) BufferFrame()) { 32 | bf_->Init(page_id); 33 | } 34 | }; 35 | 36 | void SetUp() override { 37 | // remove the test directory if it exists 38 | TearDown(); 39 | 40 | // create the test directory 41 | auto ret = system(std::format("mkdir -p {}", test_dir_).c_str()); 42 | EXPECT_EQ(ret, 0) << std::format( 43 | "Failed to create test directory, testDir={}, errno={}, error={}", test_dir_, errno, 44 | strerror(errno)); 45 | } 46 | 47 | void TearDown() override { 48 | } 49 | 50 | std::string get_rand_test_file() { 51 | return std::format("{}/{}", test_dir_, utils::RandomGenerator::RandAlphString(8)); 52 | } 53 | 54 | int open_file(const std::string& file_name) { 55 | // open the file 56 | auto flag = O_TRUNC | O_CREAT | O_RDWR | O_DIRECT; 57 | int fd = open(file_name.c_str(), flag, 0666); 58 | EXPECT_NE(fd, -1) << std::format("Failed to open file, fileName={}, errno={}, error={}", 59 | file_name, errno, strerror(errno)); 60 | 61 | return fd; 62 | } 63 | 64 | void close_file(int fd) { 65 | ASSERT_EQ(close(fd), 0) << std::format("Failed to close file, fd={}, errno={}, error={}", fd, 66 | errno, strerror(errno)); 67 | } 68 | 69 | void remove_file(const std::string& file_name) { 70 | ASSERT_EQ(remove(file_name.c_str()), 0) 71 | << std::format("Failed to remove file, fileName={}, errno={}, error={}", file_name, errno, 72 | strerror(errno)); 73 | } 74 | }; 75 | 76 | TEST_F(AsyncWriteBufferTest, Basic) { 77 | auto test_file = get_rand_test_file(); 78 | auto test_fd = open_file(test_file); 79 | SCOPED_DEFER({ 80 | close_file(test_fd); 81 | Log::Info("Test file={}", test_file); 82 | }); 83 | 84 | auto test_page_size = 512; 85 | auto test_max_batch_size = 8; 86 | AsyncWriteBuffer test_write_buffer(test_fd, test_page_size, test_max_batch_size); 87 | std::vector> bf_holders; 88 | for (int i = 0; i < test_max_batch_size; i++) { 89 | bf_holders.push_back(std::make_unique(test_page_size, i)); 90 | EXPECT_FALSE(test_write_buffer.IsFull()); 91 | // set the payload to the pageId 92 | *reinterpret_cast(bf_holders[i]->bf_->page_.payload_) = i; 93 | test_write_buffer.Add(*bf_holders[i]->bf_); 94 | } 95 | 96 | // now the write buffer should be full 97 | EXPECT_TRUE(test_write_buffer.IsFull()); 98 | 99 | // submit the IO request 100 | auto result = test_write_buffer.SubmitAll(); 101 | ASSERT_TRUE(result) << "Failed to submit IO request, error=" << result.error().ToString(); 102 | EXPECT_EQ(result.value(), test_max_batch_size); 103 | 104 | // wait for the IO request to complete 105 | result = test_write_buffer.WaitAll(); 106 | auto done_requests = result.value(); 107 | EXPECT_EQ(done_requests, test_max_batch_size); 108 | EXPECT_EQ(test_write_buffer.GetPendingRequests(), 0); 109 | 110 | // check the flushed content 111 | test_write_buffer.IterateFlushedBfs( 112 | [](BufferFrame& flushed_bf, uint64_t flushed_psn) { 113 | EXPECT_FALSE(flushed_bf.IsDirty()); 114 | EXPECT_FALSE(flushed_bf.IsFree()); 115 | EXPECT_EQ(flushed_psn, 0); 116 | }, 117 | test_max_batch_size); 118 | 119 | // read the file content 120 | for (int i = 0; i < test_max_batch_size; i++) { 121 | BufferFrameHolder bf_holder(test_page_size, i); 122 | auto ret = pread(test_fd, reinterpret_cast(bf_holder.buffer_.Get() + 512), 123 | test_page_size, test_page_size * i); 124 | EXPECT_EQ(ret, test_page_size); 125 | auto payload = *reinterpret_cast(bf_holder.bf_->page_.payload_); 126 | EXPECT_EQ(payload, i); 127 | } 128 | } 129 | 130 | } // namespace leanstore::storage::test -------------------------------------------------------------------------------- /tests/buffer-manager/page_evictor_test.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/buffer-manager/buffer_manager.hpp" 2 | #include "leanstore/buffer-manager/page_evictor.hpp" 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace leanstore::storage::test { 9 | class PageEvictorTest : public ::testing::Test { 10 | protected: 11 | std::unique_ptr store_; 12 | 13 | PageEvictorTest() = default; 14 | 15 | ~PageEvictorTest() = default; 16 | 17 | void SetUp() override { 18 | auto* cur_test = ::testing::UnitTest::GetInstance()->current_test_info(); 19 | auto cur_test_name = 20 | std::string(cur_test->test_case_name()) + "_" + std::string(cur_test->name()); 21 | const int page_size = 4096; 22 | const int page_header_size = 512; 23 | auto store_dir_str = "/tmp/leanstore/" + cur_test_name; 24 | auto* option = CreateStoreOption(store_dir_str.c_str()); 25 | option->create_from_scratch_ = true; 26 | option->log_level_ = LogLevel::kDebug; 27 | option->worker_threads_ = 2; 28 | option->num_partitions_ = 1; 29 | option->buffer_pool_size_ = 70 * (page_header_size + page_size); 30 | option->free_pct_ = 20; 31 | option->enable_bulk_insert_ = false; 32 | option->enable_eager_gc_ = false; 33 | auto res = LeanStore::Open(option); 34 | ASSERT_TRUE(res); 35 | store_ = std::move(res.value()); 36 | } 37 | }; 38 | 39 | TEST_F(PageEvictorTest, page_evict_basic) { 40 | EXPECT_EQ(store_->buffer_manager_->page_evictors_.size(), 1); 41 | auto& page_evictor = store_->buffer_manager_->page_evictors_[0]; 42 | EXPECT_TRUE(page_evictor->IsStarted()); 43 | EXPECT_EQ(page_evictor->partitions_.size(), 1); 44 | page_evictor->partitions_[0]->NeedMoreFreeBfs(); 45 | page_evictor->PickBufferFramesToCool(*page_evictor->partitions_[0]); 46 | page_evictor->PrepareAsyncWriteBuffer(*page_evictor->partitions_[0]); 47 | page_evictor->FlushAndRecycleBufferFrames(*page_evictor->partitions_[0]); 48 | } 49 | 50 | } // namespace leanstore::storage::test -------------------------------------------------------------------------------- /tests/concurrency/wal_entry_test.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore/concurrency/wal_entry.hpp" 2 | #include "utils/to_json.hpp" 3 | 4 | #include 5 | #include 6 | 7 | namespace leanstore::cr::test { 8 | 9 | class WalEntryTest : public ::testing::Test {}; 10 | 11 | TEST_F(WalEntryTest, Size) { 12 | EXPECT_EQ(sizeof(WalEntry), 1); 13 | EXPECT_EQ(sizeof(WalTxAbort), 9); 14 | EXPECT_EQ(sizeof(WalTxFinish), 9); 15 | EXPECT_EQ(sizeof(WalCarriageReturn), 3); 16 | EXPECT_EQ(sizeof(WalEntryComplex), 57); 17 | } 18 | 19 | TEST_F(WalEntryTest, ToJsonString) { 20 | // WalTxAbort 21 | { 22 | auto wal = WalTxAbort(0); 23 | auto wal_str = utils::ToJsonString(&wal); 24 | EXPECT_TRUE(wal_str.contains("kTxAbort")); 25 | EXPECT_TRUE(wal_str.contains(kType)); 26 | EXPECT_TRUE(wal_str.contains(kTxId)); 27 | } 28 | 29 | // WalTxFinish 30 | { 31 | auto wal = WalTxFinish(0); 32 | auto wal_str = utils::ToJsonString(&wal); 33 | EXPECT_TRUE(wal_str.contains("kTxFinish")); 34 | EXPECT_TRUE(wal_str.contains(kType)); 35 | EXPECT_TRUE(wal_str.contains(kTxId)); 36 | } 37 | 38 | // WalCarriageReturn 39 | { 40 | auto wal = WalCarriageReturn(0); 41 | auto wal_str = utils::ToJsonString(&wal); 42 | EXPECT_TRUE(wal_str.contains("kCarriageReturn")); 43 | EXPECT_TRUE(wal_str.contains(kType)); 44 | } 45 | 46 | // WalEntryComplex 47 | { 48 | auto wal = WalEntryComplex(0, 0, 0, 0, 0, 0, 0, 0); 49 | auto wal_str = utils::ToJsonString(&wal); 50 | EXPECT_TRUE(wal_str.contains("kComplex")); 51 | EXPECT_TRUE(wal_str.contains(kType)); 52 | EXPECT_TRUE(wal_str.contains(kTxId)); 53 | EXPECT_TRUE(wal_str.contains(kWorkerId)); 54 | EXPECT_TRUE(wal_str.contains(kPrevLsn)); 55 | EXPECT_TRUE(wal_str.contains(kPsn)); 56 | EXPECT_TRUE(wal_str.contains(kTreeId)); 57 | EXPECT_TRUE(wal_str.contains(kPageId)); 58 | } 59 | } 60 | 61 | } // namespace leanstore::cr::test -------------------------------------------------------------------------------- /tests/optimistic_guarded_test.cpp: -------------------------------------------------------------------------------- 1 | #include "leanstore-c/store_option.h" 2 | #include "leanstore/buffer-manager/buffer_manager.hpp" 3 | #include "leanstore/concurrency/cr_manager.hpp" 4 | #include "leanstore/lean_store.hpp" 5 | #include "leanstore/sync/optimistic_guarded.hpp" 6 | 7 | #include 8 | 9 | #include 10 | 11 | namespace leanstore::test { 12 | 13 | class OptimisticGuardedTest : public ::testing::Test { 14 | protected: 15 | struct TestPayload { 16 | int64_t a_; 17 | int64_t b_; 18 | }; 19 | 20 | std::unique_ptr store_; 21 | 22 | OptimisticGuardedTest() { 23 | } 24 | 25 | void SetUp() override { 26 | auto* cur_test = ::testing::UnitTest::GetInstance()->current_test_info(); 27 | auto cur_test_name = 28 | std::string(cur_test->test_case_name()) + "_" + std::string(cur_test->name()); 29 | auto store_dir_str = "/tmp/leanstore/" + cur_test_name; 30 | auto* option = CreateStoreOption(store_dir_str.c_str()); 31 | option->create_from_scratch_ = true; 32 | option->worker_threads_ = 2; 33 | auto res = LeanStore::Open(option); 34 | ASSERT_TRUE(res); 35 | store_ = std::move(res.value()); 36 | } 37 | }; 38 | 39 | TEST_F(OptimisticGuardedTest, Set) { 40 | storage::OptimisticGuarded guarded_val({0, 100}); 41 | 42 | // WorkerContext 0, set the guardedVal 100 times 43 | store_->ExecSync(0, [&]() { 44 | for (int64_t i = 0; i < 100; i++) { 45 | guarded_val.Set(TestPayload{i, 100 - i}); 46 | } 47 | }); 48 | 49 | // WorkerContext 1, read the guardedVal 200 times 50 | store_->ExecSync(1, [&]() { 51 | TestPayload copied_val; 52 | auto version = guarded_val.Get(copied_val); 53 | for (int64_t i = 0; i < 200; i++) { 54 | auto curr_version = guarded_val.Get(copied_val); 55 | if (curr_version != version) { 56 | EXPECT_EQ(copied_val.a_ + copied_val.b_, 100); 57 | EXPECT_EQ((curr_version - version) % 2, 0u); 58 | version = curr_version; 59 | } 60 | } 61 | }); 62 | } 63 | 64 | TEST_F(OptimisticGuardedTest, UpdateAttribute) { 65 | storage::OptimisticGuarded guarded_val({0, 100}); 66 | 67 | // WorkerContext 0, update the guardedVal 100 times 68 | store_->ExecSync(0, [&]() { 69 | for (int64_t i = 0; i < 100; i++) { 70 | guarded_val.UpdateAttribute(&TestPayload::a_, i); 71 | } 72 | }); 73 | 74 | // WorkerContext 1, read the guardedVal 200 times 75 | store_->ExecSync(1, [&]() { 76 | TestPayload copied_val; 77 | auto version = guarded_val.Get(copied_val); 78 | for (int64_t i = 0; i < 200; i++) { 79 | auto curr_version = guarded_val.Get(copied_val); 80 | if (curr_version != version) { 81 | EXPECT_EQ(copied_val.b_, 100); 82 | EXPECT_EQ((curr_version - version) % 2, 0u); 83 | version = curr_version; 84 | } 85 | } 86 | }); 87 | } 88 | 89 | } // namespace leanstore::test -------------------------------------------------------------------------------- /tests/tsan.supp: -------------------------------------------------------------------------------- 1 | race:tzset_internal 2 | race:ScopedHybridGuard::GetOptimistic 3 | race:BufferManager::ResolveSwipMayJump 4 | race:CommitTree::Lcb 5 | race:BTreeGeneric::TrySplitMayJump 6 | deadlock:ExclusiveGuardedBufferFrame 7 | race_top:OptimisticGuarded<*>::Set 8 | race_top:OptimisticGuarded<*>::UpdateAttribute 9 | -------------------------------------------------------------------------------- /vcpkg-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "registries": [ 3 | { 4 | "kind": "artifact", 5 | "location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip", 6 | "name": "microsoft" 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | "benchmark", 4 | "gflags", 5 | { 6 | "name": "spdlog", 7 | "version>=": "1.13.0" 8 | }, 9 | "gtest", 10 | "rapidjson", 11 | "gperftools", 12 | { 13 | "name": "cpp-httplib", 14 | "version>=": "0.14.3" 15 | }, 16 | { 17 | "name": "crc32c", 18 | "version>=": "1.1.2#2" 19 | }, 20 | "libunwind" 21 | ], 22 | "builtin-baseline": "4a2c30139309a6e4690ce929df33e85a1706a0e2" 23 | } 24 | --------------------------------------------------------------------------------