storage/kv_engine/engine_composite.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <algorithm> | ||
| 4 | #include <array> | ||
| 5 | #include <chrono> | ||
| 6 | #include <cstring> | ||
| 7 | #include <filesystem> | ||
| 8 | #include <fstream> | ||
| 9 | #include <limits> | ||
| 10 | #include <memory> | ||
| 11 | #include <mutex> | ||
| 12 | #include <shared_mutex> | ||
| 13 | #include <stdexcept> | ||
| 14 | #include <string> | ||
| 15 | #include <unordered_set> | ||
| 16 | #include <utility> | ||
| 17 | #include <vector> | ||
| 18 | |||
| 19 | #include "base/factory.h" | ||
| 20 | #include "storage/index/dram/extendible_hash_index.h" | ||
| 21 | #include "storage/index/dram/pet_hash_index.h" | ||
| 22 | #include "storage/index/dram/unordered_map_index.h" | ||
| 23 | #include "storage/index/utils/hash.h" | ||
| 24 | #include "storage/kv_engine/base_kv.h" | ||
| 25 | #include "storage/value_store/dram_value_store.h" | ||
| 26 | #include "storage/value_store/hybrid_value_store.h" | ||
| 27 | #include "storage/value_store/ssd_value_store.h" | ||
| 28 | |||
| 29 | class KVEngineComposite : public BaseKV { | ||
| 30 | public: | ||
| 31 | KVEngineComposite(std::unique_ptr<Index> index, | ||
| 32 | std::unique_ptr<ValueStore> value_store, | ||
| 33 | int num_threads = 0) | ||
| 34 | : BaseKV(BaseKVConfig{}), | ||
| 35 | index_(std::move(index)), | ||
| 36 | value_store_(std::move(value_store)), | ||
| 37 | num_threads_(num_threads) {} | ||
| 38 | |||
| 39 | 1100 | explicit KVEngineComposite(const BaseKVConfig& config) : BaseKV(config) { | |
| 40 | 1100 | config_ = config; | |
| 41 | 1100 | const auto& j = config.json_config_; | |
| 42 |
3/6✓ Branch 1 taken 1100 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1100 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1100 times.
✗ Branch 8 not taken.
|
1100 | const std::string index_type = j.at("index").at("type").get<std::string>(); |
| 43 |
3/6✓ Branch 1 taken 1100 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1100 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1100 times.
✗ Branch 8 not taken.
|
1100 | const std::string value_type = j.at("value").at("type").get<std::string>(); |
| 44 | using IF = base::Factory<Index, const BaseKVConfig&>; | ||
| 45 | using VF = base::Factory<ValueStore, const BaseKVConfig&>; | ||
| 46 |
1/2✓ Branch 1 taken 1100 times.
✗ Branch 2 not taken.
|
1100 | index_.reset(IF::NewInstance(index_type, config)); |
| 47 |
2/2✓ Branch 1 taken 1098 times.
✓ Branch 2 taken 2 times.
|
1100 | value_store_.reset(VF::NewInstance(value_type, config)); |
| 48 | 1098 | num_threads_ = config.num_threads_; | |
| 49 | 1098 | default_value_size_hint_ = | |
| 50 |
2/4✓ Branch 1 taken 1098 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1098 times.
✗ Branch 5 not taken.
|
1098 | j.at("value").value("default_value_size_hint", 0); |
| 51 |
3/6✓ Branch 1 taken 1098 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1098 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1098 times.
|
1098 | if (!index_ || !value_store_) { |
| 52 | ✗ | throw std::runtime_error("failed to create KVEngine components"); | |
| 53 | } | ||
| 54 | 1112 | } | |
| 55 | |||
| 56 | 200902 | void Get(const uint64_t key, std::string& value, unsigned tid) override { | |
| 57 | (void)tid; | ||
| 58 | 200902 | Value_t handle = kValueHandleNone; | |
| 59 |
1/2✓ Branch 2 taken 200902 times.
✗ Branch 3 not taken.
|
200902 | index_->Get(key, handle); |
| 60 |
2/2✓ Branch 0 taken 5996 times.
✓ Branch 1 taken 194906 times.
|
200902 | if (handle == kValueHandleNone) { |
| 61 | 5996 | value.clear(); | |
| 62 | 148798 | return; | |
| 63 | } | ||
| 64 |
3/4✓ Branch 2 taken 194906 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 142802 times.
✓ Branch 5 taken 52104 times.
|
194906 | if (const char* ptr = value_store_->DirectPtr(handle)) { |
| 65 |
2/4✓ Branch 2 taken 142802 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 142802 times.
✗ Branch 6 not taken.
|
142802 | value.resize(value_store_->SlotCapacity(handle)); |
| 66 | 142802 | std::memcpy(value.data(), ptr, value.size()); | |
| 67 | 142802 | return; | |
| 68 | } | ||
| 69 |
2/4✓ Branch 2 taken 52104 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 52104 times.
✗ Branch 6 not taken.
|
52104 | value.resize(value_store_->SlotCapacity(handle)); |
| 70 | const size_t actual = | ||
| 71 |
1/2✓ Branch 4 taken 52104 times.
✗ Branch 5 not taken.
|
52104 | value_store_->Read(handle, value.data(), value.size()); |
| 72 |
1/2✓ Branch 1 taken 52104 times.
✗ Branch 2 not taken.
|
52104 | value.resize(actual); |
| 73 | } | ||
| 74 | |||
| 75 | 8 | bool Exists(const uint64_t key, unsigned tid) override { | |
| 76 | (void)tid; | ||
| 77 | 8 | Value_t handle = kValueHandleNone; | |
| 78 |
1/2✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | index_->Get(key, handle); |
| 79 | 8 | return handle != kValueHandleNone; | |
| 80 | } | ||
| 81 | |||
| 82 | 354558 | void Put(const uint64_t key, | |
| 83 | const std::string_view& value, | ||
| 84 | unsigned tid) override { | ||
| 85 |
1/2✓ Branch 1 taken 354558 times.
✗ Branch 2 not taken.
|
354558 | std::shared_lock<std::shared_mutex> checkpoint_lock(checkpoint_mu_); |
| 86 |
1/2✓ Branch 3 taken 354558 times.
✗ Branch 4 not taken.
|
354558 | PutInternal(key, value.data(), value.size(), tid, true); |
| 87 | 354558 | } | |
| 88 | |||
| 89 | 294 | void BatchPut(base::ConstArray<uint64_t> keys, | |
| 90 | std::vector<base::ConstArray<float>>* values, | ||
| 91 | unsigned tid) override { | ||
| 92 |
3/6✓ Branch 0 taken 294 times.
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 294 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 294 times.
|
294 | if (values == nullptr || keys.Size() != static_cast<int>(values->size())) { |
| 93 | ✗ | LOG(FATAL) << "KVEngine::BatchPut size mismatch"; | |
| 94 | } | ||
| 95 | (void)tid; | ||
| 96 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 294 times.
|
294 | if (keys.Size() == 0) { |
| 97 | ✗ | return; | |
| 98 | } | ||
| 99 |
1/2✓ Branch 1 taken 294 times.
✗ Branch 2 not taken.
|
294 | std::shared_lock<std::shared_mutex> checkpoint_lock(checkpoint_mu_); |
| 100 | |||
| 101 | 294 | std::unordered_set<uint64_t> seen_keys; | |
| 102 |
1/2✓ Branch 2 taken 294 times.
✗ Branch 3 not taken.
|
294 | seen_keys.reserve(static_cast<size_t>(keys.Size())); |
| 103 | 294 | bool has_duplicate_key = false; | |
| 104 |
2/2✓ Branch 1 taken 742 times.
✓ Branch 2 taken 294 times.
|
1036 | for (int i = 0; i < keys.Size(); ++i) { |
| 105 |
2/4✓ Branch 2 taken 742 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 742 times.
|
742 | if (!seen_keys.insert(keys[i]).second) { |
| 106 | ✗ | has_duplicate_key = true; | |
| 107 | ✗ | break; | |
| 108 | } | ||
| 109 | } | ||
| 110 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 294 times.
|
294 | if (has_duplicate_key) { |
| 111 | ✗ | for (int i = 0; i < keys.Size(); ++i) { | |
| 112 | ✗ | const auto& item = (*values)[i]; | |
| 113 | ✗ | PutInternal(keys[i], | |
| 114 | ✗ | item.Data(), | |
| 115 | ✗ | static_cast<size_t>(item.Size()) * sizeof(float), | |
| 116 | tid, | ||
| 117 | false); | ||
| 118 | } | ||
| 119 | ✗ | return; | |
| 120 | } | ||
| 121 | |||
| 122 | struct PutItem { | ||
| 123 | uint64_t key = 0; | ||
| 124 | ValueStore::WriteSpec spec{}; | ||
| 125 | }; | ||
| 126 | 294 | std::vector<PutItem> items; | |
| 127 |
1/2✓ Branch 2 taken 294 times.
✗ Branch 3 not taken.
|
294 | items.reserve(static_cast<size_t>(keys.Size())); |
| 128 | |||
| 129 |
2/2✓ Branch 1 taken 742 times.
✓ Branch 2 taken 294 times.
|
1036 | for (int i = 0; i < keys.Size(); ++i) { |
| 130 | 742 | const auto& item = (*values)[i]; | |
| 131 | 742 | const void* data = item.Data(); | |
| 132 | 742 | const size_t size = static_cast<size_t>(item.Size()) * sizeof(float); | |
| 133 |
1/2✓ Branch 2 taken 742 times.
✗ Branch 3 not taken.
|
742 | items.push_back(PutItem{keys[i], ValueStore::WriteSpec{data, size}}); |
| 134 | } | ||
| 135 | |||
| 136 | 294 | std::vector<ValueStore::WriteSpec> specs; | |
| 137 |
1/2✓ Branch 2 taken 294 times.
✗ Branch 3 not taken.
|
294 | specs.reserve(items.size()); |
| 138 |
2/2✓ Branch 5 taken 742 times.
✓ Branch 6 taken 294 times.
|
1036 | for (const auto& item : items) { |
| 139 |
1/2✓ Branch 1 taken 742 times.
✗ Branch 2 not taken.
|
742 | specs.push_back(item.spec); |
| 140 | } | ||
| 141 |
1/2✓ Branch 2 taken 294 times.
✗ Branch 3 not taken.
|
294 | const auto new_handles = value_store_->BatchAllocAndWrite(specs); |
| 142 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 294 times.
|
294 | if (new_handles.size() != items.size()) { |
| 143 | ✗ | LOG(FATAL) << "KVEngine::BatchPut allocation result size mismatch"; | |
| 144 | } | ||
| 145 |
2/2✓ Branch 1 taken 742 times.
✓ Branch 2 taken 294 times.
|
1036 | for (size_t i = 0; i < items.size(); ++i) { |
| 146 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 742 times.
|
742 | if (new_handles[i] == kValueHandleNone) { |
| 147 | ✗ | LOG(FATAL) << "KVEngine batch value allocation failed, key=" | |
| 148 | ✗ | << items[i].key << " size=" << items[i].spec.size; | |
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 |
2/2✓ Branch 1 taken 742 times.
✓ Branch 2 taken 294 times.
|
1036 | for (size_t i = 0; i < items.size(); ++i) { |
| 153 |
1/2✓ Branch 4 taken 742 times.
✗ Branch 5 not taken.
|
742 | Value_t old_handle = index_->Put(items[i].key, new_handles[i], tid); |
| 154 |
2/2✓ Branch 0 taken 216 times.
✓ Branch 1 taken 526 times.
|
742 | if (old_handle != kValueHandleNone) { |
| 155 |
1/2✓ Branch 2 taken 216 times.
✗ Branch 3 not taken.
|
216 | value_store_->Retire(old_handle); |
| 156 | } | ||
| 157 | } | ||
| 158 |
1/2✓ Branch 1 taken 294 times.
✗ Branch 2 not taken.
|
294 | TrackKeys(keys); |
| 159 |
2/4✓ Branch 4 taken 294 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 294 times.
✗ Branch 8 not taken.
|
294 | } |
| 160 | |||
| 161 | 1536 | void BatchGet(base::ConstArray<uint64_t> keys, | |
| 162 | std::vector<base::ConstArray<float>>* values, | ||
| 163 | unsigned tid) override { | ||
| 164 | (void)tid; | ||
| 165 |
1/2✓ Branch 2 taken 1536 times.
✗ Branch 3 not taken.
|
1536 | values->resize(keys.Size()); |
| 166 |
2/2✓ Branch 0 taken 252 times.
✓ Branch 1 taken 1284 times.
|
1536 | thread_local std::vector<Value_t> handles; |
| 167 |
2/2✓ Branch 0 taken 252 times.
✓ Branch 1 taken 1284 times.
|
1536 | thread_local std::vector<std::vector<float>> buffers; |
| 168 |
1/2✓ Branch 2 taken 1536 times.
✗ Branch 3 not taken.
|
1536 | handles.assign(keys.Size(), kValueHandleNone); |
| 169 | 1536 | buffers.clear(); | |
| 170 |
1/2✓ Branch 2 taken 1536 times.
✗ Branch 3 not taken.
|
1536 | buffers.resize(keys.Size()); |
| 171 | |||
| 172 |
1/2✓ Branch 1 taken 1536 times.
✗ Branch 2 not taken.
|
1536 | if (keys.Size() > 0) { |
| 173 |
1/2✓ Branch 3 taken 1536 times.
✗ Branch 4 not taken.
|
1536 | index_->BatchGet(keys, handles.data(), tid); |
| 174 | } | ||
| 175 | 1536 | std::vector<uint64_t> batch_handles; | |
| 176 | 1536 | std::vector<size_t> batch_indices; | |
| 177 |
1/2✓ Branch 2 taken 1536 times.
✗ Branch 3 not taken.
|
1536 | batch_handles.reserve(static_cast<size_t>(keys.Size())); |
| 178 |
1/2✓ Branch 2 taken 1536 times.
✗ Branch 3 not taken.
|
1536 | batch_indices.reserve(static_cast<size_t>(keys.Size())); |
| 179 |
2/2✓ Branch 1 taken 67426 times.
✓ Branch 2 taken 1536 times.
|
68962 | for (int i = 0; i < keys.Size(); ++i) { |
| 180 |
2/2✓ Branch 1 taken 324 times.
✓ Branch 2 taken 67102 times.
|
67426 | if (handles[i] == kValueHandleNone) { |
| 181 | 324 | (*values)[i] = base::ConstArray<float>(); | |
| 182 | 324 | continue; | |
| 183 | } | ||
| 184 |
3/4✓ Branch 3 taken 67102 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 45542 times.
✓ Branch 6 taken 21560 times.
|
67102 | if (const char* ptr = value_store_->DirectPtr(handles[i])) { |
| 185 |
1/2✓ Branch 3 taken 45542 times.
✗ Branch 4 not taken.
|
45542 | const size_t bytes = value_store_->SlotCapacity(handles[i]); |
| 186 | 45542 | (*values)[i] = base::ConstArray<float>( | |
| 187 | reinterpret_cast<float*>(const_cast<char*>(ptr)), | ||
| 188 | 45542 | bytes / sizeof(float)); | |
| 189 | 45542 | continue; | |
| 190 | 45542 | } | |
| 191 |
1/2✓ Branch 2 taken 21560 times.
✗ Branch 3 not taken.
|
21560 | batch_handles.push_back(handles[i]); |
| 192 |
1/2✓ Branch 1 taken 21560 times.
✗ Branch 2 not taken.
|
21560 | batch_indices.push_back(static_cast<size_t>(i)); |
| 193 | } | ||
| 194 | |||
| 195 | 1536 | std::vector<ValueStore::ReadResult> batch_results; | |
| 196 |
2/2✓ Branch 1 taken 410 times.
✓ Branch 2 taken 1126 times.
|
1536 | if (!batch_handles.empty()) { |
| 197 |
1/2✓ Branch 2 taken 410 times.
✗ Branch 3 not taken.
|
410 | value_store_->BatchRead(batch_handles, batch_results); |
| 198 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 410 times.
|
410 | if (batch_results.size() != batch_indices.size()) { |
| 199 | ✗ | LOG(FATAL) << "KVEngine::BatchGet read result size mismatch"; | |
| 200 | } | ||
| 201 |
2/2✓ Branch 1 taken 21560 times.
✓ Branch 2 taken 410 times.
|
21970 | for (size_t i = 0; i < batch_indices.size(); ++i) { |
| 202 | 21560 | const size_t idx = batch_indices[i]; | |
| 203 | 21560 | const auto& result = batch_results[i]; | |
| 204 |
1/2✓ Branch 3 taken 21560 times.
✗ Branch 4 not taken.
|
21560 | buffers[idx].resize(result.data.size() / sizeof(float)); |
| 205 |
1/2✓ Branch 1 taken 21560 times.
✗ Branch 2 not taken.
|
21560 | if (!result.data.empty()) { |
| 206 | 43120 | std::memcpy( | |
| 207 | 21560 | buffers[idx].data(), result.data.data(), result.data.size()); | |
| 208 | } | ||
| 209 | 21560 | (*values)[idx] = | |
| 210 | 43120 | base::ConstArray<float>(buffers[idx].data(), buffers[idx].size()); | |
| 211 | } | ||
| 212 | } | ||
| 213 | 1536 | } | |
| 214 | |||
| 215 | 122 | bool BatchGetFlat(base::ConstArray<uint64_t> keys, | |
| 216 | float* values, | ||
| 217 | int64_t num_rows, | ||
| 218 | int64_t embedding_dim, | ||
| 219 | unsigned tid, | ||
| 220 | BatchGetFlatStats* stats = nullptr) override { | ||
| 221 |
4/8✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 122 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 122 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 122 times.
|
244 | if (values == nullptr || num_rows < 0 || embedding_dim <= 0 || |
| 222 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 122 times.
|
122 | keys.Size() != static_cast<size_t>(num_rows)) { |
| 223 | ✗ | return false; | |
| 224 | } | ||
| 225 | 122 | const size_t row_bytes = static_cast<size_t>(embedding_dim) * sizeof(float); | |
| 226 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 106 times.
|
122 | thread_local std::vector<Value_t> handles; |
| 227 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 106 times.
|
122 | thread_local std::vector<char> read_buffer; |
| 228 |
1/2✓ Branch 2 taken 122 times.
✗ Branch 3 not taken.
|
122 | handles.assign(keys.Size(), kValueHandleNone); |
| 229 | const auto index_lookup_start = | ||
| 230 | 54 | stats != nullptr ? std::chrono::steady_clock::now() | |
| 231 |
2/2✓ Branch 0 taken 54 times.
✓ Branch 1 taken 68 times.
|
122 | : std::chrono::steady_clock::time_point{}; |
| 232 |
1/2✓ Branch 1 taken 122 times.
✗ Branch 2 not taken.
|
122 | if (keys.Size() > 0) { |
| 233 |
1/2✓ Branch 3 taken 122 times.
✗ Branch 4 not taken.
|
122 | index_->BatchGet(keys, handles.data(), tid); |
| 234 | } | ||
| 235 |
2/2✓ Branch 0 taken 54 times.
✓ Branch 1 taken 68 times.
|
122 | if (stats != nullptr) { |
| 236 | 54 | stats->index_lookup_ns = static_cast<std::uint64_t>( | |
| 237 |
1/2✓ Branch 1 taken 54 times.
✗ Branch 2 not taken.
|
54 | std::chrono::duration_cast< std::chrono::nanoseconds>( |
| 238 |
0/2✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
108 | std::chrono::steady_clock::now() - index_lookup_start) |
| 239 | 54 | .count()); | |
| 240 | } | ||
| 241 | |||
| 242 | 122 | std::uint64_t missing_zero_fill_ns = 0; | |
| 243 | 122 | std::uint64_t missing_rows = 0; | |
| 244 | const auto row_copy_start = | ||
| 245 | 54 | stats != nullptr ? std::chrono::steady_clock::now() | |
| 246 |
2/2✓ Branch 0 taken 54 times.
✓ Branch 1 taken 68 times.
|
122 | : std::chrono::steady_clock::time_point{}; |
| 247 |
4/4✓ Branch 0 taken 12 times.
✓ Branch 1 taken 110 times.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 110 times.
|
134 | if (default_value_size_hint_ == row_bytes && |
| 248 |
2/4✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
|
24 | value_store_->ReadFlatFixedRows( |
| 249 | 12 | handles.data(), | |
| 250 | static_cast<size_t>(num_rows), | ||
| 251 | values, | ||
| 252 | row_bytes, | ||
| 253 | &missing_rows)) { | ||
| 254 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (stats != nullptr) { |
| 255 | ✗ | stats->zero_fill_ns = 0; | |
| 256 | ✗ | stats->row_copy_ns = static_cast<std::uint64_t>( | |
| 257 | ✗ | std::chrono::duration_cast< std::chrono::nanoseconds>( | |
| 258 | ✗ | std::chrono::steady_clock::now() - row_copy_start) | |
| 259 | ✗ | .count()); | |
| 260 | ✗ | stats->missing_rows = missing_rows; | |
| 261 | } | ||
| 262 | 12 | return true; | |
| 263 | } | ||
| 264 |
2/2✓ Branch 0 taken 218 times.
✓ Branch 1 taken 54 times.
|
272 | for (int64_t row = 0; row < num_rows; ++row) { |
| 265 | 218 | const Value_t handle = handles[static_cast<size_t>(row)]; | |
| 266 | 218 | float* dst = values + row * embedding_dim; | |
| 267 |
2/2✓ Branch 0 taken 54 times.
✓ Branch 1 taken 164 times.
|
218 | if (handle == kValueHandleNone) { |
| 268 | const auto missing_zero_start = | ||
| 269 | 54 | stats != nullptr ? std::chrono::steady_clock::now() | |
| 270 |
1/2✓ Branch 0 taken 54 times.
✗ Branch 1 not taken.
|
54 | : std::chrono::steady_clock::time_point{}; |
| 271 | 54 | std::memset(dst, 0, row_bytes); | |
| 272 |
1/2✓ Branch 0 taken 54 times.
✗ Branch 1 not taken.
|
54 | if (stats != nullptr) { |
| 273 | 54 | missing_zero_fill_ns += static_cast<std::uint64_t>( | |
| 274 |
1/2✓ Branch 1 taken 54 times.
✗ Branch 2 not taken.
|
54 | std::chrono::duration_cast< std::chrono::nanoseconds>( |
| 275 |
0/2✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
108 | std::chrono::steady_clock::now() - missing_zero_start) |
| 276 | 54 | .count()); | |
| 277 | } | ||
| 278 | 54 | ++missing_rows; | |
| 279 | 54 | continue; | |
| 280 | 54 | } | |
| 281 |
3/4✓ Branch 2 taken 164 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 110 times.
✓ Branch 5 taken 54 times.
|
164 | if (const char* ptr = value_store_->DirectPtr(handle)) { |
| 282 |
1/2✓ Branch 0 taken 110 times.
✗ Branch 1 not taken.
|
110 | if (default_value_size_hint_ != row_bytes) { |
| 283 |
1/2✓ Branch 2 taken 110 times.
✗ Branch 3 not taken.
|
110 | const size_t slot_bytes = value_store_->SlotCapacity(handle); |
| 284 |
2/2✓ Branch 0 taken 38 times.
✓ Branch 1 taken 72 times.
|
110 | if (slot_bytes != row_bytes) { |
| 285 |
4/8✓ Branch 1 taken 38 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 38 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 38 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 38 times.
✗ Branch 11 not taken.
|
76 | LOG(ERROR) << "KVEngine::BatchGetFlat row size mismatch row=" << row |
| 286 |
2/4✓ Branch 1 taken 38 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 38 times.
✗ Branch 6 not taken.
|
38 | << " key=" << keys[static_cast<int>(row)] |
| 287 |
2/4✓ Branch 1 taken 38 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 38 times.
✗ Branch 5 not taken.
|
38 | << " expected_bytes=" << row_bytes |
| 288 |
2/4✓ Branch 1 taken 38 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 38 times.
✗ Branch 5 not taken.
|
38 | << " actual_bytes=" << slot_bytes; |
| 289 | 38 | return false; | |
| 290 | } | ||
| 291 | } | ||
| 292 | 72 | std::memcpy(dst, ptr, row_bytes); | |
| 293 | } else { | ||
| 294 |
1/2✓ Branch 1 taken 54 times.
✗ Branch 2 not taken.
|
54 | read_buffer.resize(row_bytes + sizeof(float)); |
| 295 | const size_t actual = | ||
| 296 |
1/2✓ Branch 4 taken 54 times.
✗ Branch 5 not taken.
|
54 | value_store_->Read(handle, read_buffer.data(), read_buffer.size()); |
| 297 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 36 times.
|
54 | if (actual != row_bytes) { |
| 298 |
4/8✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 18 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 18 times.
✗ Branch 11 not taken.
|
36 | LOG(ERROR) << "KVEngine::BatchGetFlat read size mismatch row=" << row |
| 299 |
2/4✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 18 times.
✗ Branch 6 not taken.
|
18 | << " key=" << keys[static_cast<int>(row)] |
| 300 |
2/4✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
|
18 | << " expected_bytes=" << row_bytes |
| 301 |
2/4✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
|
18 | << " actual_bytes=" << actual; |
| 302 | 18 | return false; | |
| 303 | } | ||
| 304 | 36 | std::memcpy(dst, read_buffer.data(), row_bytes); | |
| 305 | } | ||
| 306 | } | ||
| 307 |
1/2✓ Branch 0 taken 54 times.
✗ Branch 1 not taken.
|
54 | if (stats != nullptr) { |
| 308 | 54 | stats->zero_fill_ns = missing_zero_fill_ns; | |
| 309 | 54 | stats->row_copy_ns = static_cast<std::uint64_t>( | |
| 310 |
1/2✓ Branch 1 taken 54 times.
✗ Branch 2 not taken.
|
54 | std::chrono::duration_cast< std::chrono::nanoseconds>( |
| 311 |
0/2✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
54 | std::chrono::steady_clock::now() - row_copy_start) |
| 312 | 54 | .count()); | |
| 313 | 54 | stats->missing_rows = missing_rows; | |
| 314 | } | ||
| 315 | 54 | return true; | |
| 316 | } | ||
| 317 | |||
| 318 | ✗ | bool BatchGetIndexOnly(base::ConstArray<uint64_t> keys, | |
| 319 | unsigned tid, | ||
| 320 | BatchGetFlatStats* stats = nullptr) override { | ||
| 321 | ✗ | thread_local std::vector<Value_t> handles; | |
| 322 | ✗ | handles.assign(keys.Size(), kValueHandleNone); | |
| 323 | ✗ | if (keys.Size() > 0) { | |
| 324 | ✗ | index_->BatchGet(keys, handles.data(), tid); | |
| 325 | } | ||
| 326 | ✗ | if (stats != nullptr) { | |
| 327 | ✗ | std::uint64_t missing_rows = 0; | |
| 328 | ✗ | for (const Value_t handle : handles) { | |
| 329 | ✗ | if (handle == kValueHandleNone) { | |
| 330 | ✗ | ++missing_rows; | |
| 331 | } | ||
| 332 | } | ||
| 333 | ✗ | stats->missing_rows = missing_rows; | |
| 334 | } | ||
| 335 | ✗ | return true; | |
| 336 | } | ||
| 337 | |||
| 338 | 24 | bool BatchGetDirectFixedRows( | |
| 339 | base::ConstArray<uint64_t> keys, | ||
| 340 | int64_t num_rows, | ||
| 341 | int64_t embedding_dim, | ||
| 342 | unsigned tid, | ||
| 343 | std::vector<DirectFixedRow>* rows, | ||
| 344 | BatchGetFlatStats* stats = nullptr) override { | ||
| 345 |
4/8✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 24 times.
|
48 | if (rows == nullptr || num_rows < 0 || embedding_dim <= 0 || |
| 346 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
|
24 | keys.Size() != static_cast<size_t>(num_rows)) { |
| 347 | ✗ | return false; | |
| 348 | } | ||
| 349 | 24 | const size_t row_bytes = static_cast<size_t>(embedding_dim) * sizeof(float); | |
| 350 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
|
24 | if (default_value_size_hint_ != row_bytes) { |
| 351 | ✗ | return false; | |
| 352 | } | ||
| 353 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 16 times.
|
24 | thread_local std::vector<Value_t> handles; |
| 354 |
1/2✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
|
24 | handles.assign(keys.Size(), kValueHandleNone); |
| 355 | const auto index_lookup_start = | ||
| 356 | ✗ | stats != nullptr ? std::chrono::steady_clock::now() | |
| 357 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
|
24 | : std::chrono::steady_clock::time_point{}; |
| 358 |
1/2✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
|
24 | if (keys.Size() > 0) { |
| 359 |
1/2✓ Branch 3 taken 24 times.
✗ Branch 4 not taken.
|
24 | index_->BatchGet(keys, handles.data(), tid); |
| 360 | } | ||
| 361 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
|
24 | if (stats != nullptr) { |
| 362 | ✗ | stats->index_lookup_ns = static_cast<std::uint64_t>( | |
| 363 | ✗ | std::chrono::duration_cast< std::chrono::nanoseconds>( | |
| 364 | ✗ | std::chrono::steady_clock::now() - index_lookup_start) | |
| 365 | ✗ | .count()); | |
| 366 | } | ||
| 367 | |||
| 368 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 16 times.
|
24 | thread_local std::vector<ValueStore::DirectFixedRow> store_rows; |
| 369 |
1/2✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
|
24 | store_rows.resize(static_cast<size_t>(num_rows)); |
| 370 | 24 | uint64_t missing_rows = 0; | |
| 371 |
2/4✓ Branch 3 taken 24 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 24 times.
|
48 | if (!value_store_->GetDirectFixedRows( |
| 372 | 24 | handles.data(), | |
| 373 | static_cast<size_t>(num_rows), | ||
| 374 | row_bytes, | ||
| 375 | store_rows.data(), | ||
| 376 | &missing_rows)) { | ||
| 377 | ✗ | return false; | |
| 378 | } | ||
| 379 |
1/2✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
|
24 | rows->resize(store_rows.size()); |
| 380 |
2/2✓ Branch 1 taken 46 times.
✓ Branch 2 taken 24 times.
|
70 | for (size_t i = 0; i < store_rows.size(); ++i) { |
| 381 | 46 | (*rows)[i] = DirectFixedRow{ | |
| 382 | 46 | store_rows[i].data, store_rows[i].size, store_rows[i].missing}; | |
| 383 | } | ||
| 384 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
|
24 | if (stats != nullptr) { |
| 385 | ✗ | stats->missing_rows = missing_rows; | |
| 386 | } | ||
| 387 | 24 | return true; | |
| 388 | } | ||
| 389 | |||
| 390 | ✗ | RDMABackingRegion GetRDMABackingRegion() const override { | |
| 391 | ✗ | if (!value_store_) { | |
| 392 | ✗ | return {}; | |
| 393 | } | ||
| 394 | return RDMABackingRegion{ | ||
| 395 | ✗ | value_store_->RDMABackingData(), value_store_->RDMABackingSize()}; | |
| 396 | } | ||
| 397 | |||
| 398 | 24 | bool ApplySgdUpdateFlat( | |
| 399 | base::ConstArray<uint64_t> keys, | ||
| 400 | const float* grads, | ||
| 401 | int64_t num_rows, | ||
| 402 | int64_t embedding_dim, | ||
| 403 | float learning_rate, | ||
| 404 | uint8_t tag, | ||
| 405 | unsigned tid) override { | ||
| 406 |
4/8✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 24 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 24 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 24 times.
|
24 | if (grads == nullptr || keys.Size() != static_cast<size_t>(num_rows) || |
| 407 | embedding_dim <= 0) { | ||
| 408 | ✗ | return false; | |
| 409 | } | ||
| 410 |
1/2✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
|
24 | std::shared_lock<std::shared_mutex> checkpoint_lock(checkpoint_mu_); |
| 411 | 24 | const int tag_bits = static_cast<int>(sizeof(tag) * 8); | |
| 412 | 24 | const int shift = static_cast<int>(sizeof(uint64_t) * 8) - tag_bits; | |
| 413 | 24 | const uint64_t key_mask = ~0ULL >> tag_bits; | |
| 414 | 24 | const size_t row_bytes = static_cast<size_t>(embedding_dim) * sizeof(float); | |
| 415 | |||
| 416 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 16 times.
|
24 | thread_local std::vector<uint64_t> tagged_keys; |
| 417 |
1/2✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
|
24 | tagged_keys.resize(static_cast<size_t>(num_rows)); |
| 418 |
2/2✓ Branch 0 taken 46 times.
✓ Branch 1 taken 24 times.
|
70 | for (int64_t r = 0; r < num_rows; ++r) { |
| 419 | 46 | tagged_keys[static_cast<size_t>(r)] = | |
| 420 | 92 | (static_cast<uint64_t>(tag) << shift) | | |
| 421 | 46 | (keys[static_cast<size_t>(r)] & key_mask); | |
| 422 | } | ||
| 423 | |||
| 424 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 16 times.
|
24 | thread_local std::vector<DirectFixedRow> rows; |
| 425 |
2/4✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 24 times.
|
24 | if (!BatchGetDirectFixedRows( |
| 426 | base::ConstArray<uint64_t>(tagged_keys), | ||
| 427 | num_rows, | ||
| 428 | embedding_dim, | ||
| 429 | tid, | ||
| 430 | &rows)) { | ||
| 431 | ✗ | return false; | |
| 432 | } | ||
| 433 | |||
| 434 |
1/2✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
|
24 | std::vector<float> missing_row(static_cast<size_t>(embedding_dim)); |
| 435 |
2/2✓ Branch 0 taken 46 times.
✓ Branch 1 taken 24 times.
|
70 | for (int64_t r = 0; r < num_rows; ++r) { |
| 436 | 46 | const auto& row = rows[static_cast<size_t>(r)]; | |
| 437 | 46 | const float* grad = grads + r * embedding_dim; | |
| 438 |
2/2✓ Branch 0 taken 20 times.
✓ Branch 1 taken 26 times.
|
46 | if (row.missing) { |
| 439 |
2/2✓ Branch 0 taken 584 times.
✓ Branch 1 taken 20 times.
|
604 | for (int64_t c = 0; c < embedding_dim; ++c) { |
| 440 | 584 | missing_row[static_cast<size_t>(c)] = -learning_rate * grad[c]; | |
| 441 | } | ||
| 442 |
1/2✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
|
20 | PutInternal(tagged_keys[static_cast<size_t>(r)], |
| 443 | 20 | missing_row.data(), | |
| 444 | row_bytes, | ||
| 445 | tid, | ||
| 446 | false); | ||
| 447 | 20 | continue; | |
| 448 | 20 | } | |
| 449 | |||
| 450 | 26 | float* value = reinterpret_cast<float*>(const_cast<char*>(row.data)); | |
| 451 | 26 | #pragma omp simd | |
| 452 | for (int64_t c = 0; c < embedding_dim; ++c) { | ||
| 453 | 608 | value[c] -= learning_rate * grad[c]; | |
| 454 | } | ||
| 455 | } | ||
| 456 | 24 | return true; | |
| 457 | 24 | } | |
| 458 | |||
| 459 | 56 | void BulkLoad(base::ConstArray<uint64_t> keys, const void* value) override { | |
| 460 | 56 | const auto& j = config_.json_config_; | |
| 461 |
2/4✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 56 times.
✗ Branch 5 not taken.
|
56 | const size_t value_size = j.at("value").value("default_value_size_hint", 0); |
| 462 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
|
56 | if (value_size == 0) { |
| 463 | ✗ | LOG(FATAL) << "KVEngine::BulkLoad requires value_size hint"; | |
| 464 | } | ||
| 465 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 56 times.
|
56 | if (keys.Size() == 0) { |
| 466 | ✗ | return; | |
| 467 | } | ||
| 468 |
1/2✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
|
56 | std::shared_lock<std::shared_mutex> checkpoint_lock(checkpoint_mu_); |
| 469 | 56 | const char* data = reinterpret_cast<const char*>(value); | |
| 470 | 56 | std::vector<ValueStore::WriteSpec> specs; | |
| 471 |
1/2✓ Branch 2 taken 56 times.
✗ Branch 3 not taken.
|
56 | specs.reserve(static_cast<size_t>(keys.Size())); |
| 472 |
2/2✓ Branch 1 taken 434 times.
✓ Branch 2 taken 56 times.
|
490 | for (int i = 0; i < keys.Size(); ++i) { |
| 473 |
1/2✓ Branch 1 taken 434 times.
✗ Branch 2 not taken.
|
434 | specs.push_back(ValueStore::WriteSpec{data + i * value_size, value_size}); |
| 474 | } | ||
| 475 |
1/2✓ Branch 2 taken 56 times.
✗ Branch 3 not taken.
|
56 | std::vector<uint64_t> handles = value_store_->BatchAllocAndWrite(specs); |
| 476 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 56 times.
|
56 | if (handles.size() != static_cast<size_t>(keys.Size())) { |
| 477 | ✗ | LOG(FATAL) << "KVEngine::BulkLoad allocation result size mismatch"; | |
| 478 | } | ||
| 479 |
2/2✓ Branch 1 taken 434 times.
✓ Branch 2 taken 56 times.
|
490 | for (int i = 0; i < keys.Size(); ++i) { |
| 480 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 434 times.
|
434 | if (handles[static_cast<size_t>(i)] == kValueHandleNone) { |
| 481 | ✗ | LOG(FATAL) << "KVEngine bulk value allocation failed, key=" << keys[i] | |
| 482 | ✗ | << " size=" << value_size; | |
| 483 | } | ||
| 484 | } | ||
| 485 |
1/2✓ Branch 3 taken 56 times.
✗ Branch 4 not taken.
|
56 | index_->BatchPut(keys, handles.data(), 0); |
| 486 |
1/2✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
|
56 | TrackKeys(keys); |
| 487 | 56 | } | |
| 488 | |||
| 489 | 4 | bool SaveCheckpoint(const std::string& file, | |
| 490 | const std::string& metadata) override { | ||
| 491 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | std::unique_lock<std::shared_mutex> checkpoint_lock(checkpoint_mu_); |
| 492 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
4 | if (file.empty()) { |
| 493 | ✗ | LOG(ERROR) << "KVEngine checkpoint path is empty"; | |
| 494 | ✗ | return false; | |
| 495 | } | ||
| 496 | |||
| 497 | 4 | std::vector<uint64_t> keys; | |
| 498 | { | ||
| 499 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | std::lock_guard<std::mutex> lock(checkpoint_keys_mu_); |
| 500 |
1/2✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
|
4 | keys.assign(checkpoint_keys_.begin(), checkpoint_keys_.end()); |
| 501 | 4 | } | |
| 502 |
1/2✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
|
4 | std::sort(keys.begin(), keys.end()); |
| 503 | |||
| 504 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | const std::filesystem::path checkpoint_path(file); |
| 505 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | std::filesystem::path temp_path(file); |
| 506 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | temp_path += ".tmp"; |
| 507 | 4 | std::error_code error; | |
| 508 | 4 | std::filesystem::remove(temp_path, error); | |
| 509 | |||
| 510 |
1/2✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | std::ofstream output(temp_path, std::ios::binary | std::ios::trunc); |
| 511 |
2/4✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
|
4 | if (!output) { |
| 512 | ✗ | LOG(ERROR) << "Failed to open checkpoint temp file: " << temp_path; | |
| 513 | ✗ | return false; | |
| 514 | } | ||
| 515 | |||
| 516 | 4 | const uint64_t metadata_size = static_cast<uint64_t>(metadata.size()); | |
| 517 | 4 | const uint64_t record_count = static_cast<uint64_t>(keys.size()); | |
| 518 | 4 | uint64_t checksum = kCheckpointChecksumSeed; | |
| 519 | 4 | UpdateChecksum(&checksum, kCheckpointMagic.data(), kCheckpointMagic.size()); | |
| 520 | 4 | UpdateChecksumPod(&checksum, kCheckpointVersion); | |
| 521 | 4 | UpdateChecksumPod(&checksum, metadata_size); | |
| 522 | 4 | UpdateChecksumPod(&checksum, record_count); | |
| 523 | 4 | UpdateChecksum(&checksum, metadata.data(), metadata.size()); | |
| 524 | bool write_ok = | ||
| 525 |
1/2✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
|
4 | WriteBytes(output, kCheckpointMagic.data(), kCheckpointMagic.size()) && |
| 526 |
2/4✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
|
4 | WritePod(output, kCheckpointVersion) && |
| 527 |
5/10✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
|
12 | WritePod(output, metadata_size) && WritePod(output, record_count) && |
| 528 |
2/4✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
|
4 | WriteBytes(output, metadata.data(), metadata.size()); |
| 529 |
2/2✓ Branch 5 taken 8 times.
✓ Branch 6 taken 4 times.
|
12 | for (const uint64_t key : keys) { |
| 530 | 8 | std::string value; | |
| 531 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | Get(key, value, 0); |
| 532 |
2/4✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
|
8 | if (!Exists(key, 0)) { |
| 533 | ✗ | LOG(ERROR) << "KVEngine checkpoint tracked key is missing: " << key; | |
| 534 | ✗ | write_ok = false; | |
| 535 | ✗ | break; | |
| 536 | } | ||
| 537 | 8 | const uint64_t value_size = static_cast<uint64_t>(value.size()); | |
| 538 | 8 | UpdateChecksumPod(&checksum, key); | |
| 539 | 8 | UpdateChecksumPod(&checksum, value_size); | |
| 540 | 8 | UpdateChecksum(&checksum, value.data(), value.size()); | |
| 541 | 8 | write_ok = | |
| 542 |
5/10✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 8 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 8 times.
✗ Branch 11 not taken.
|
16 | write_ok && WritePod(output, key) && WritePod(output, value_size) && |
| 543 |
2/4✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
|
8 | WriteBytes(output, value.data(), value.size()); |
| 544 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (!write_ok) { |
| 545 | ✗ | break; | |
| 546 | } | ||
| 547 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | } |
| 548 |
3/6✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
|
4 | write_ok = write_ok && WritePod(output, checksum); |
| 549 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | output.flush(); |
| 550 |
3/6✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
|
4 | write_ok = write_ok && output.good(); |
| 551 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | output.close(); |
| 552 |
3/6✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
|
4 | write_ok = write_ok && !output.fail(); |
| 553 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (!write_ok) { |
| 554 | ✗ | LOG(ERROR) << "Failed to write checkpoint temp file: " << temp_path; | |
| 555 | ✗ | std::filesystem::remove(temp_path, error); | |
| 556 | ✗ | return false; | |
| 557 | } | ||
| 558 | |||
| 559 | 4 | error.clear(); | |
| 560 | 4 | std::filesystem::rename(temp_path, checkpoint_path, error); | |
| 561 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
4 | if (error) { |
| 562 | ✗ | LOG(ERROR) << "Failed to publish checkpoint " << checkpoint_path << ": " | |
| 563 | ✗ | << error.message(); | |
| 564 | ✗ | std::filesystem::remove(temp_path, error); | |
| 565 | ✗ | return false; | |
| 566 | } | ||
| 567 | 4 | return true; | |
| 568 | 4 | } | |
| 569 | |||
| 570 | 12 | bool LoadCheckpoint(const std::string& file, | |
| 571 | const std::string& expected_metadata) override { | ||
| 572 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | std::unique_lock<std::shared_mutex> checkpoint_lock(checkpoint_mu_); |
| 573 | { | ||
| 574 |
1/2✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
|
12 | std::lock_guard<std::mutex> lock(checkpoint_keys_mu_); |
| 575 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 10 times.
|
12 | if (!checkpoint_keys_.empty()) { |
| 576 |
3/6✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
|
2 | LOG(ERROR) << "KVEngine checkpoint load requires an empty engine"; |
| 577 | 2 | return false; | |
| 578 | } | ||
| 579 |
2/2✓ Branch 1 taken 10 times.
✓ Branch 2 taken 2 times.
|
12 | } |
| 580 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
|
10 | if (file.empty()) { |
| 581 | ✗ | LOG(ERROR) << "KVEngine checkpoint path is empty"; | |
| 582 | ✗ | return false; | |
| 583 | } | ||
| 584 | |||
| 585 | 10 | std::error_code error; | |
| 586 |
1/2✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
|
10 | const std::uintmax_t file_size = std::filesystem::file_size(file, error); |
| 587 |
3/6✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 10 times.
|
10 | if (error || file_size > std::numeric_limits<uint64_t>::max()) { |
| 588 | ✗ | LOG(ERROR) << "Failed to inspect checkpoint file: " << file; | |
| 589 | ✗ | return false; | |
| 590 | } | ||
| 591 | 10 | uint64_t remaining = static_cast<uint64_t>(file_size); | |
| 592 |
1/2✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
|
10 | std::ifstream input(file, std::ios::binary); |
| 593 |
2/4✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
|
10 | if (!input) { |
| 594 | ✗ | LOG(ERROR) << "Failed to open checkpoint file: " << file; | |
| 595 | ✗ | return false; | |
| 596 | } | ||
| 597 | |||
| 598 | 10 | std::array<char, kCheckpointMagic.size()> magic{}; | |
| 599 | 10 | uint32_t version = 0; | |
| 600 | 10 | uint64_t metadata_size = 0; | |
| 601 | 10 | uint64_t record_count = 0; | |
| 602 |
1/2✓ Branch 3 taken 10 times.
✗ Branch 4 not taken.
|
10 | if (!ReadBytes(input, magic.data(), magic.size(), &remaining) || |
| 603 |
2/4✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
✗ Branch 4 not taken.
|
10 | !ReadPod(input, &version, &remaining) || |
| 604 |
2/4✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
✗ Branch 4 not taken.
|
10 | !ReadPod(input, &metadata_size, &remaining) || |
| 605 |
2/4✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
✗ Branch 4 not taken.
|
10 | !ReadPod(input, &record_count, &remaining) || |
| 606 |
4/6✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
|
10 | magic != kCheckpointMagic || version != kCheckpointVersion || |
| 607 |
4/6✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 8 times.
|
28 | metadata_size > remaining || |
| 608 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
|
8 | metadata_size > std::numeric_limits<size_t>::max()) { |
| 609 |
4/8✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
|
2 | LOG(ERROR) << "Invalid or truncated checkpoint header: " << file; |
| 610 | 2 | return false; | |
| 611 | } | ||
| 612 | |||
| 613 |
1/2✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | std::string metadata(static_cast<size_t>(metadata_size), '\0'); |
| 614 |
2/4✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
|
8 | if (!ReadBytes(input, metadata.data(), metadata_size, &remaining)) { |
| 615 | ✗ | LOG(ERROR) << "Truncated checkpoint metadata: " << file; | |
| 616 | ✗ | return false; | |
| 617 | } | ||
| 618 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 6 times.
|
8 | if (metadata != expected_metadata) { |
| 619 |
4/8✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
|
2 | LOG(ERROR) << "Checkpoint metadata mismatch: " << file; |
| 620 | 2 | return false; | |
| 621 | } | ||
| 622 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (remaining < sizeof(uint64_t)) { |
| 623 | ✗ | LOG(ERROR) << "Checkpoint checksum is missing: " << file; | |
| 624 | ✗ | return false; | |
| 625 | } | ||
| 626 |
2/4✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
|
12 | if (record_count > std::numeric_limits<size_t>::max() || |
| 627 | 6 | record_count > | |
| 628 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | (remaining - sizeof(uint64_t)) / (2 * sizeof(uint64_t))) { |
| 629 | ✗ | LOG(ERROR) << "Invalid checkpoint record count: " << file; | |
| 630 | ✗ | return false; | |
| 631 | } | ||
| 632 | |||
| 633 | 6 | uint64_t checksum = kCheckpointChecksumSeed; | |
| 634 | 6 | UpdateChecksum(&checksum, magic.data(), magic.size()); | |
| 635 | 6 | UpdateChecksumPod(&checksum, version); | |
| 636 | 6 | UpdateChecksumPod(&checksum, metadata_size); | |
| 637 | 6 | UpdateChecksumPod(&checksum, record_count); | |
| 638 | 6 | UpdateChecksum(&checksum, metadata.data(), metadata.size()); | |
| 639 | 6 | std::vector<std::pair<uint64_t, std::string>> records; | |
| 640 |
1/2✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
|
6 | records.reserve(static_cast<size_t>(record_count)); |
| 641 | 6 | std::unordered_set<uint64_t> seen_keys; | |
| 642 |
1/2✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
|
6 | seen_keys.reserve(static_cast<size_t>(record_count)); |
| 643 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 6 times.
|
16 | for (uint64_t i = 0; i < record_count; ++i) { |
| 644 | 10 | uint64_t key = 0; | |
| 645 | 10 | uint64_t value_size = 0; | |
| 646 |
1/2✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
|
10 | if (!ReadPod(input, &key, &remaining) || |
| 647 |
3/6✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
✗ Branch 6 not taken.
|
10 | !ReadPod(input, &value_size, &remaining) || value_size > remaining || |
| 648 |
3/6✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 10 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 10 times.
|
30 | value_size > std::numeric_limits<size_t>::max() || |
| 649 |
2/4✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
|
10 | !seen_keys.insert(key).second) { |
| 650 | ✗ | LOG(ERROR) << "Invalid or truncated checkpoint record: " << file; | |
| 651 | ✗ | return false; | |
| 652 | } | ||
| 653 |
1/2✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
|
10 | std::string value(static_cast<size_t>(value_size), '\0'); |
| 654 |
2/4✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
|
10 | if (!ReadBytes(input, value.data(), value_size, &remaining)) { |
| 655 | ✗ | LOG(ERROR) << "Truncated checkpoint value: " << file; | |
| 656 | ✗ | return false; | |
| 657 | } | ||
| 658 | 10 | UpdateChecksumPod(&checksum, key); | |
| 659 | 10 | UpdateChecksumPod(&checksum, value_size); | |
| 660 | 10 | UpdateChecksum(&checksum, value.data(), value.size()); | |
| 661 |
1/2✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
|
10 | records.emplace_back(key, std::move(value)); |
| 662 |
1/2✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
|
10 | } |
| 663 | 6 | uint64_t saved_checksum = 0; | |
| 664 |
6/8✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 4 times.
|
6 | if (!ReadPod(input, &saved_checksum, &remaining) || remaining != 0) { |
| 665 |
4/8✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
|
2 | LOG(ERROR) << "Checkpoint contains trailing data: " << file; |
| 666 | 2 | return false; | |
| 667 | } | ||
| 668 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | if (saved_checksum != checksum) { |
| 669 |
4/8✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
|
2 | LOG(ERROR) << "Checkpoint checksum mismatch: " << file; |
| 670 | 2 | return false; | |
| 671 | } | ||
| 672 | |||
| 673 |
2/2✓ Branch 5 taken 6 times.
✓ Branch 6 taken 2 times.
|
8 | for (const auto& record : records) { |
| 674 | 6 | PutInternal( | |
| 675 |
1/2✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | record.first, record.second.data(), record.second.size(), 0, false); |
| 676 | } | ||
| 677 | 2 | return true; | |
| 678 | 12 | } | |
| 679 | |||
| 680 | 14 | uint64_t CheckpointRecordCount() const override { | |
| 681 |
1/2✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
|
14 | std::shared_lock<std::shared_mutex> checkpoint_lock(checkpoint_mu_); |
| 682 |
1/2✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
|
14 | std::lock_guard<std::mutex> lock(checkpoint_keys_mu_); |
| 683 | 28 | return static_cast<uint64_t>(checkpoint_keys_.size()); | |
| 684 | 14 | } | |
| 685 | |||
| 686 | ✗ | void Util() override { | |
| 687 | ✗ | LOG(INFO) << "KVEngine index utilization=" << index_->Utilization() | |
| 688 | ✗ | << " value=" << value_store_->GetInfo(); | |
| 689 | ✗ | } | |
| 690 | |||
| 691 | ✗ | void DebugInfo() const override { | |
| 692 | ✗ | index_->DebugInfo(); | |
| 693 | ✗ | LOG(INFO) << value_store_->GetInfo(); | |
| 694 | ✗ | } | |
| 695 | |||
| 696 | ✗ | std::string ExtraResultFields() const override { | |
| 697 | ✗ | return value_store_ ? value_store_->ExtraResultFields() : ""; | |
| 698 | } | ||
| 699 | |||
| 700 | private: | ||
| 701 | 48 | static bool WriteBytes(std::ofstream& output, const void* data, size_t size) { | |
| 702 | 48 | if (size > | |
| 703 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
|
48 | static_cast<size_t>((std::numeric_limits<std::streamsize>::max)())) { |
| 704 | ✗ | return false; | |
| 705 | } | ||
| 706 |
1/2✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
|
48 | if (size != 0) { |
| 707 | 48 | output.write(static_cast<const char*>(data), | |
| 708 | static_cast<std::streamsize>(size)); | ||
| 709 | } | ||
| 710 | 48 | return output.good(); | |
| 711 | } | ||
| 712 | |||
| 713 | template <typename T> | ||
| 714 | 32 | static bool WritePod(std::ofstream& output, const T& value) { | |
| 715 | 32 | return WriteBytes(output, &value, sizeof(value)); | |
| 716 | } | ||
| 717 | |||
| 718 | 84 | static bool ReadBytes( | |
| 719 | std::ifstream& input, void* data, uint64_t size, uint64_t* remaining) { | ||
| 720 |
5/6✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 82 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 82 times.
|
166 | if (remaining == nullptr || size > *remaining || |
| 721 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 82 times.
|
82 | size > static_cast<uint64_t>( |
| 722 | 82 | (std::numeric_limits<std::streamsize>::max)())) { | |
| 723 | 2 | return false; | |
| 724 | } | ||
| 725 |
1/2✓ Branch 0 taken 82 times.
✗ Branch 1 not taken.
|
82 | if (size != 0) { |
| 726 | 82 | input.read(static_cast<char*>(data), static_cast<std::streamsize>(size)); | |
| 727 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 82 times.
|
82 | if (!input) { |
| 728 | ✗ | return false; | |
| 729 | } | ||
| 730 | } | ||
| 731 | 82 | *remaining -= size; | |
| 732 | 82 | return true; | |
| 733 | } | ||
| 734 | |||
| 735 | template <typename T> | ||
| 736 | 56 | static bool ReadPod(std::ifstream& input, T* value, uint64_t* remaining) { | |
| 737 | 56 | return ReadBytes(input, value, sizeof(*value), remaining); | |
| 738 | } | ||
| 739 | |||
| 740 | static void | ||
| 741 | 104 | UpdateChecksum(uint64_t* checksum, const void* data, size_t size) { | |
| 742 |
1/2✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
|
104 | if (size != 0) { |
| 743 | 104 | *checksum = xxhash(data, size, *checksum); | |
| 744 | } | ||
| 745 | 104 | } | |
| 746 | |||
| 747 | template <typename T> | ||
| 748 | 66 | static void UpdateChecksumPod(uint64_t* checksum, const T& value) { | |
| 749 | 66 | UpdateChecksum(checksum, &value, sizeof(value)); | |
| 750 | 66 | } | |
| 751 | |||
| 752 | 354584 | void TrackKey(uint64_t key) { | |
| 753 |
1/2✓ Branch 1 taken 354584 times.
✗ Branch 2 not taken.
|
354584 | std::lock_guard<std::mutex> lock(checkpoint_keys_mu_); |
| 754 |
1/2✓ Branch 1 taken 354584 times.
✗ Branch 2 not taken.
|
354584 | checkpoint_keys_.insert(key); |
| 755 | 354584 | } | |
| 756 | |||
| 757 | 350 | void TrackKeys(base::ConstArray<uint64_t> keys) { | |
| 758 |
1/2✓ Branch 1 taken 350 times.
✗ Branch 2 not taken.
|
350 | std::lock_guard<std::mutex> lock(checkpoint_keys_mu_); |
| 759 |
2/2✓ Branch 1 taken 1176 times.
✓ Branch 2 taken 350 times.
|
1526 | for (int i = 0; i < keys.Size(); ++i) { |
| 760 |
1/2✓ Branch 2 taken 1176 times.
✗ Branch 3 not taken.
|
1176 | checkpoint_keys_.insert(keys[i]); |
| 761 | } | ||
| 762 | 350 | } | |
| 763 | |||
| 764 | 354584 | void PutInternal(uint64_t key, | |
| 765 | const void* data, | ||
| 766 | size_t size, | ||
| 767 | unsigned tid, | ||
| 768 | bool emit_fence) { | ||
| 769 | (void)tid; | ||
| 770 | (void)emit_fence; | ||
| 771 | 354584 | Value_t new_handle = value_store_->AllocAndWrite(data, size); | |
| 772 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 354584 times.
|
354584 | if (new_handle == kValueHandleNone) { |
| 773 | ✗ | LOG(FATAL) << "KVEngine value allocation failed, key=" << key | |
| 774 | ✗ | << " size=" << size; | |
| 775 | return; | ||
| 776 | } | ||
| 777 | 354584 | Value_t old_handle = index_->Put(key, new_handle, tid); | |
| 778 |
2/2✓ Branch 0 taken 156926 times.
✓ Branch 1 taken 197658 times.
|
354584 | if (old_handle != kValueHandleNone) { |
| 779 | 156926 | value_store_->Retire(old_handle); | |
| 780 | } | ||
| 781 | 354584 | TrackKey(key); | |
| 782 | } | ||
| 783 | |||
| 784 | inline static constexpr std::array<char, 8> kCheckpointMagic = { | ||
| 785 | 'R', 'S', 'K', 'V', 'C', 'P', '0', '1'}; | ||
| 786 | inline static constexpr uint32_t kCheckpointVersion = 2; | ||
| 787 | inline static constexpr uint64_t kCheckpointChecksumSeed = | ||
| 788 | 0x9e3779b97f4a7c15ULL; | ||
| 789 | |||
| 790 | BaseKVConfig config_; | ||
| 791 | std::unique_ptr<Index> index_; | ||
| 792 | std::unique_ptr<ValueStore> value_store_; | ||
| 793 | int num_threads_ = 0; | ||
| 794 | size_t default_value_size_hint_ = 0; | ||
| 795 | mutable std::shared_mutex checkpoint_mu_; | ||
| 796 | mutable std::mutex checkpoint_keys_mu_; | ||
| 797 | std::unordered_set<uint64_t> checkpoint_keys_; | ||
| 798 | }; | ||
| 799 | |||
| 800 | FACTORY_REGISTER( | ||
| 801 | BaseKV, KVEngineComposite, KVEngineComposite, const BaseKVConfig&); | ||
| 802 |