├── .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