Skip to content

Commit 2d3f88f

Browse files
authored
[test] add e2e test cases for aggressive location pruning (#116)
[test] add e2e test cases for aggressive location pruning [data_storage] introducing the dummy storage backend The dummy storage backend is a local filesystem based storage backend for testing purposes only. The e2e tests for aggressive location pruning need a storage backend with MightExist() actually implemented, but currently no storage backend can satisfy the need. This is where the dummy backend comes into play: it can implement MightExist() without worrying about the read/write path overhead. The dummy backend reuses the LocalStorageSpec for StorageConfig in the proto definition, so "local" should be used instead of "dummy" when constructing requests like AddStorageRequest. Backward-compatibility is naturally handled so no new instance behavior version needs to be introduced: v0-data: key_count v1-data: key_count, usage_data[_,] v2-data: key_count, usage_data[_,dummy] During recovery: ---- | | v0-data | v1-data | v2-data | | -- | ------- | ---------- | ---------- | | v0 | v0,ok | v0,ok(ign) | v0,ok(ign) | | v1 | v0,ok | v1,ok | err,ok(can't rollback) | | v2 | v0,ok | v1,ok(dummy=0) | v2,ok |
1 parent 44ac147 commit 2d3f88f

22 files changed

Lines changed: 1309 additions & 21 deletions

integration_test/reclaimer/BUILD

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
package(default_visibility = ["//integration_test/reclaimer:__subpackages__"])
22

3+
py_test(
4+
name = "location_pruning_test",
5+
srcs = ["location_pruning_test.py"],
6+
tags = ["no-remote-exec"],
7+
data = [
8+
"//kv_cache_manager:kv_cache_manager_bin",
9+
],
10+
deps = [
11+
"//integration_test/testlib:test_base",
12+
"//integration_test/admin_service:http_interface_test",
13+
"//integration_test/meta_service:http_interface_test",
14+
],
15+
)
16+
317
py_test(
418
name = "reclaiming_test",
519
srcs = ["reclaiming_test.py"],

integration_test/reclaimer/location_pruning_test.py

Lines changed: 596 additions & 0 deletions
Large diffs are not rendered by default.

kv_cache_manager/data_storage/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ cc_library(
44
name = "data_storage",
55
srcs = [
66
"data_storage_manager.cc",
7+
"dummy_backend.cc",
78
"hf3fs_backend.cc",
89
"mooncake_backend.cc",
910
"nfs_backend.cc",
1011
],
1112
hdrs = [
1213
"data_storage_backend.h",
1314
"data_storage_manager.h",
15+
"dummy_backend.h",
1416
"hf3fs_backend.h",
1517
"mooncake_backend.h",
1618
"nfs_backend.h",

kv_cache_manager/data_storage/data_storage_manager.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <utility>
66

77
#include "kv_cache_manager/common/logger.h"
8+
#include "kv_cache_manager/data_storage/dummy_backend.h"
89
#include "kv_cache_manager/data_storage/hf3fs_backend.h"
910
#include "kv_cache_manager/data_storage/mooncake_backend.h"
1011
#include "kv_cache_manager/data_storage/nfs_backend.h"
@@ -169,6 +170,8 @@ std::shared_ptr<DataStorageBackend> DataStorageManager::CreateStorageBackend(con
169170
return std::make_shared<TairMempoolBackend>(metrics_registry_);
170171
case DataStorageType::DATA_STORAGE_TYPE_NFS:
171172
return std::make_shared<NfsBackend>(metrics_registry_);
173+
case DataStorageType::DATA_STORAGE_TYPE_DUMMY:
174+
return std::make_shared<DummyBackend>(metrics_registry_);
172175
default:
173176
return nullptr;
174177
}
@@ -260,4 +263,4 @@ std::vector<ErrorCode> DataStorageManager::UnLock(const std::string &unique_name
260263
auto storage_backend = iter->second;
261264
return storage_backend->UnLock(storage_uris);
262265
}
263-
} // namespace kv_cache_manager
266+
} // namespace kv_cache_manager
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#include "kv_cache_manager/data_storage/dummy_backend.h"
2+
3+
#include <algorithm>
4+
#include <cstddef>
5+
#include <filesystem>
6+
#include <fstream>
7+
#include <functional>
8+
#include <iterator>
9+
#include <memory>
10+
#include <string>
11+
#include <system_error>
12+
#include <utility>
13+
#include <vector>
14+
15+
#include "kv_cache_manager/common/error_code.h"
16+
#include "kv_cache_manager/common/hash/hash.h"
17+
#include "kv_cache_manager/common/logger.h"
18+
#include "kv_cache_manager/common/string_util.h"
19+
#include "kv_cache_manager/data_storage/data_storage_uri.h"
20+
#include "kv_cache_manager/metrics/metrics_registry.h"
21+
22+
namespace kv_cache_manager {
23+
24+
DummyBackend::DummyBackend(std::shared_ptr<MetricsRegistry> metrics_registry)
25+
: DataStorageBackend(std::move(metrics_registry)) {}
26+
27+
DataStorageType DummyBackend::GetType() { return DataStorageType::DATA_STORAGE_TYPE_DUMMY; }
28+
29+
bool DummyBackend::Available() { return IsOpen() && IsAvailable(); }
30+
31+
double DummyBackend::GetStorageUsageRatio(const std::string &trace_id) const { return 0.0; }
32+
33+
ErrorCode DummyBackend::DoOpen(const StorageConfig &storage_config, const std::string &trace_id) {
34+
if (const auto cfg = std::dynamic_pointer_cast<DummyStorageSpec>(storage_config.storage_spec())) {
35+
spec_ = *cfg;
36+
} else {
37+
KVCM_LOG_WARN("unexpected config type, storage config: [%s]", storage_config.ToString().c_str());
38+
return ErrorCode::EC_ERROR;
39+
}
40+
if (spec_.root_path().empty()) {
41+
KVCM_LOG_WARN("open dummy backend failed, root_path is empty");
42+
return ErrorCode::EC_ERROR;
43+
}
44+
base_path_ = std::filesystem::path(spec_.root_path());
45+
std::error_code ec;
46+
std::filesystem::create_directories(base_path_, ec);
47+
if (ec) {
48+
KVCM_LOG_WARN("open dummy backend failed, cannot create root_path [%s], msg: [%s]",
49+
base_path_.string().c_str(),
50+
ec.message().c_str());
51+
return ErrorCode::EC_ERROR;
52+
}
53+
KVCM_LOG_INFO("open dummy backend success, config: [%s]", spec_.ToString().c_str());
54+
SetOpen(true);
55+
SetAvailable(true);
56+
return ErrorCode::EC_OK;
57+
}
58+
59+
ErrorCode DummyBackend::Close() {
60+
KVCM_LOG_INFO("close dummy backend");
61+
SetOpen(false);
62+
SetAvailable(false);
63+
return ErrorCode::EC_OK;
64+
}
65+
66+
std::vector<std::pair<ErrorCode, DataStorageUri>> DummyBackend::Create(const std::vector<std::string> &keys,
67+
const std::size_t size_per_key,
68+
const std::string &trace_id,
69+
const std::function<void()> cb) {
70+
std::vector<std::pair<ErrorCode, DataStorageUri>> results;
71+
std::vector<std::vector<std::string>> batches;
72+
73+
auto batch_size = spec_.key_count_per_file();
74+
batch_size = batch_size <= 0 ? 1 : batch_size;
75+
using diff_t = std::vector<std::string>::difference_type;
76+
for (std::size_t start = 0; start < keys.size(); start += batch_size) {
77+
batches.emplace_back(std::next(keys.begin(), static_cast<diff_t>(start)),
78+
std::next(keys.begin(), static_cast<diff_t>(std::min(start + batch_size, keys.size()))));
79+
}
80+
81+
for (auto &batch : batches) {
82+
DataStorageUri storage_uri;
83+
storage_uri.SetProtocol(ToString(GetType()));
84+
85+
if (batch.size() > 1) {
86+
std::string combine_key = StringUtil::Join(batch, "|");
87+
std::string hash_str = StringUtil::Uint64ToHex(Hash64(combine_key.c_str(), combine_key.size(), 42));
88+
storage_uri.SetPath(base_path_ / (batch[0] + "_" + hash_str));
89+
} else {
90+
storage_uri.SetPath(base_path_ / batch[0]);
91+
}
92+
93+
storage_uri.SetParam("size", std::to_string(size_per_key));
94+
95+
for (std::size_t i = 0; i != batch.size(); ++i) {
96+
if (batch_size > 1) {
97+
storage_uri.SetParam("blkid", std::to_string(i));
98+
}
99+
results.emplace_back(ErrorCode::EC_OK, storage_uri);
100+
}
101+
}
102+
103+
if (cb) {
104+
cb();
105+
}
106+
107+
return results;
108+
}
109+
110+
std::vector<ErrorCode> DummyBackend::Delete(const std::vector<DataStorageUri> &storage_uris,
111+
const std::string &trace_id,
112+
const std::function<void()> cb) {
113+
std::vector<ErrorCode> results;
114+
for (auto &uri : storage_uris) {
115+
std::filesystem::path file_path = uri.GetPath();
116+
std::error_code ec;
117+
const bool removed = std::filesystem::remove(file_path, ec);
118+
if (ec) {
119+
KVCM_LOG_ERROR(
120+
"failed to delete file, path: [%s], msg: [%s]", file_path.string().c_str(), ec.message().c_str());
121+
results.push_back(ErrorCode::EC_ERROR);
122+
continue;
123+
}
124+
if (!removed) {
125+
KVCM_LOG_WARN("file not exist, path: [%s]", file_path.string().c_str());
126+
}
127+
results.push_back(ErrorCode::EC_OK);
128+
}
129+
130+
if (cb) {
131+
cb();
132+
}
133+
134+
return results;
135+
}
136+
137+
std::vector<bool> DummyBackend::Exist(const std::vector<DataStorageUri> &storage_uris) {
138+
std::vector<bool> results;
139+
for (auto &uri : storage_uris) {
140+
std::error_code ec;
141+
const bool res = std::filesystem::exists(uri.GetPath(), ec);
142+
if (ec) {
143+
KVCM_LOG_ERROR("std::filesystem::exists call failed, err code: [%d], err msg: [%s], path: [%s]",
144+
ec.value(),
145+
ec.message().c_str(),
146+
uri.GetPath().c_str());
147+
results.push_back(false);
148+
continue;
149+
}
150+
results.push_back(res);
151+
}
152+
return results;
153+
}
154+
155+
std::vector<bool> DummyBackend::MightExist(const std::vector<DataStorageUri> &storage_uris) {
156+
return Exist(storage_uris);
157+
}
158+
159+
std::vector<ErrorCode> DummyBackend::Lock(const std::vector<DataStorageUri> &storage_uris) {
160+
std::vector<ErrorCode> results(storage_uris.size(), ErrorCode::EC_OK);
161+
return results;
162+
}
163+
164+
std::vector<ErrorCode> DummyBackend::UnLock(const std::vector<DataStorageUri> &storage_uris) {
165+
std::vector<ErrorCode> results(storage_uris.size(), ErrorCode::EC_OK);
166+
return results;
167+
}
168+
169+
} // namespace kv_cache_manager
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <filesystem>
5+
#include <functional>
6+
#include <memory>
7+
#include <string>
8+
#include <vector>
9+
10+
#include "kv_cache_manager/common/error_code.h"
11+
#include "kv_cache_manager/data_storage/data_storage_backend.h"
12+
#include "kv_cache_manager/data_storage/data_storage_uri.h"
13+
#include "kv_cache_manager/data_storage/storage_config.h"
14+
15+
namespace kv_cache_manager {
16+
17+
class MetricsRegistry;
18+
19+
class DummyBackend : public DataStorageBackend {
20+
public:
21+
DummyBackend() = delete;
22+
explicit DummyBackend(std::shared_ptr<MetricsRegistry> metrics_registry);
23+
~DummyBackend() override = default;
24+
25+
DataStorageType GetType() override;
26+
bool Available() override;
27+
[[nodiscard]] double GetStorageUsageRatio(const std::string &trace_id) const override;
28+
29+
ErrorCode DoOpen(const StorageConfig &storage_config, const std::string &trace_id) override;
30+
ErrorCode Close() override;
31+
32+
std::vector<std::pair<ErrorCode, DataStorageUri>> Create(const std::vector<std::string> &keys,
33+
std::size_t size_per_key,
34+
const std::string &trace_id,
35+
std::function<void()> cb) override;
36+
std::vector<ErrorCode> Delete(const std::vector<DataStorageUri> &storage_uris,
37+
const std::string &trace_id,
38+
std::function<void()> cb) override;
39+
std::vector<bool> Exist(const std::vector<DataStorageUri> &storage_uris) override;
40+
std::vector<bool> MightExist(const std::vector<DataStorageUri> &storage_uris) override;
41+
std::vector<ErrorCode> Lock(const std::vector<DataStorageUri> &storage_uris) override;
42+
std::vector<ErrorCode> UnLock(const std::vector<DataStorageUri> &storage_uris) override;
43+
44+
private:
45+
DummyStorageSpec spec_;
46+
std::filesystem::path base_path_;
47+
};
48+
49+
} // namespace kv_cache_manager

kv_cache_manager/data_storage/storage_config.cc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ std::string ToString(const DataStorageType &type) {
160160
return "pace";
161161
case DataStorageType::DATA_STORAGE_TYPE_NFS:
162162
return "file";
163+
case DataStorageType::DATA_STORAGE_TYPE_DUMMY:
164+
return "dummy";
163165
default:
164166
return "unrecognized";
165167
}
@@ -176,6 +178,8 @@ DataStorageType ToDataStorageType(const std::string &type) {
176178
return DataStorageType::DATA_STORAGE_TYPE_TAIR_MEMPOOL;
177179
} else if (type == "file") {
178180
return DataStorageType::DATA_STORAGE_TYPE_NFS;
181+
} else if (type == "dummy") {
182+
return DataStorageType::DATA_STORAGE_TYPE_DUMMY;
179183
} else {
180184
return DataStorageType::DATA_STORAGE_TYPE_UNKNOWN;
181185
}
@@ -271,6 +275,38 @@ void NfsStorageSpec::ToRapidWriter(rapidjson::Writer<rapidjson::StringBuffer> &w
271275
Put(writer, "key_count_per_file", key_count_per_file_);
272276
}
273277

278+
// DummyStorageSpec
279+
std::string DummyStorageSpec::ToString() const {
280+
std::ostringstream oss;
281+
oss << "root_path: " << root_path_;
282+
oss << " , key_count_per_file: " << key_count_per_file_;
283+
return oss.str();
284+
}
285+
286+
bool DummyStorageSpec::ValidateRequiredFields(std::string &invalid_fields) const {
287+
bool valid = true;
288+
std::string local_invalid_fields;
289+
if (root_path_.empty()) {
290+
valid = false;
291+
local_invalid_fields += "{root_path}";
292+
}
293+
if (!valid) {
294+
invalid_fields += "{DummyStorageSpec: " + local_invalid_fields + "}";
295+
}
296+
return valid;
297+
}
298+
299+
bool DummyStorageSpec::FromRapidValue(const rapidjson::Value &rapid_value) {
300+
KVCM_JSON_GET_MACRO(rapid_value, "root_path", root_path_);
301+
KVCM_JSON_GET_DEFAULT_MACRO(rapid_value, "key_count_per_file", key_count_per_file_, 1);
302+
return true;
303+
}
304+
305+
void DummyStorageSpec::ToRapidWriter(rapidjson::Writer<rapidjson::StringBuffer> &writer) const noexcept {
306+
Put(writer, "root_path", root_path_);
307+
Put(writer, "key_count_per_file", key_count_per_file_);
308+
}
309+
274310
bool StorageConfig::FromRapidValue(const rapidjson::Value &rapid_value) {
275311
std::string type_str;
276312
KVCM_JSON_GET_MACRO(rapid_value, "type", type_str);
@@ -297,6 +333,10 @@ bool StorageConfig::FromRapidValue(const rapidjson::Value &rapid_value) {
297333
auto tmp = std::make_shared<TairMemPoolStorageSpec>();
298334
KVCM_JSON_GET_MACRO(rapid_value, "storage_spec", tmp);
299335
storage_spec_ = tmp;
336+
} else if (type_ == DataStorageType::DATA_STORAGE_TYPE_DUMMY) {
337+
auto tmp = std::make_shared<DummyStorageSpec>();
338+
KVCM_JSON_GET_MACRO(rapid_value, "storage_spec", tmp);
339+
storage_spec_ = tmp;
300340
} else {
301341
storage_spec_ = nullptr; // 对未知或未支持类型,设为空
302342
}

kv_cache_manager/data_storage/storage_config.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ enum class DataStorageType : uint8_t {
1414
DATA_STORAGE_TYPE_TAIR_MEMPOOL = 3,
1515
DATA_STORAGE_TYPE_NFS = 4,
1616
DATA_STORAGE_TYPE_VCNS_HF3FS = 5,
17+
DATA_STORAGE_TYPE_DUMMY = 6,
1718
COUNT, // as sentinel
1819
};
1920

@@ -173,6 +174,23 @@ class NfsStorageSpec : public StorageSpec {
173174
int32_t key_count_per_file_ = 0;
174175
};
175176

177+
class DummyStorageSpec : public StorageSpec {
178+
public:
179+
bool FromRapidValue(const rapidjson::Value &rapid_value) override;
180+
void ToRapidWriter(rapidjson::Writer<rapidjson::StringBuffer> &writer) const noexcept override;
181+
bool ValidateRequiredFields(std::string &invalid_fields) const override;
182+
183+
std::string ToString() const override;
184+
const std::string &root_path() const { return root_path_; }
185+
void set_root_path(const std::string &root_path) { root_path_ = root_path; }
186+
int32_t key_count_per_file() const { return key_count_per_file_; }
187+
void set_key_count_per_file(int32_t value) { key_count_per_file_ = value; }
188+
189+
private:
190+
std::string root_path_;
191+
int32_t key_count_per_file_ = 0;
192+
};
193+
176194
class StorageConfig : public Jsonizable {
177195
public:
178196
StorageConfig() = default;

kv_cache_manager/data_storage/test/BUILD

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ cc_test(
3939
],
4040
)
4141

42+
cc_test(
43+
name = "DummyBackendTest",
44+
srcs = [
45+
"dummy_backend_test.cc",
46+
],
47+
copts = ["-fno-access-control"],
48+
data = [],
49+
deps = [
50+
"//kv_cache_manager/common:unittest",
51+
"//kv_cache_manager/data_storage",
52+
],
53+
)
54+
4255
cc_test(
4356
name = "StorageConfigTest",
4457
srcs = [

0 commit comments

Comments
 (0)