Skip to content

Commit 23d3292

Browse files
committed
Refine checksum API and finish-write contract
1 parent 38cbd25 commit 23d3292

13 files changed

Lines changed: 267 additions & 454 deletions

File tree

docs/design/data_integrity.md

Lines changed: 130 additions & 225 deletions
Large diffs are not rendered by default.

kv_cache_manager/client/include/common.h

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <map>
44
#include <memory>
55
#include <string>
6+
#include <utility>
67
#include <variant>
78
#include <vector>
89

@@ -253,36 +254,36 @@ struct MatchMetaResult {
253254
};
254255

255256
struct FinishWriteOptions {
256-
const std::vector<int64_t> *checksums{nullptr};
257+
std::vector<int64_t> checksums;
257258

258-
static FinishWriteOptions WithChecksums(const std::vector<int64_t> &checksums) {
259+
static FinishWriteOptions WithChecksums(std::vector<int64_t> checksums) {
259260
FinishWriteOptions options;
260-
options.checksums = &checksums;
261+
options.checksums = std::move(checksums);
261262
return options;
262263
}
263264
};
264265

265266
struct LoadKvCachesOptions {
266267
std::shared_ptr<TransferTraceInfo> trace_info{nullptr};
267-
const std::vector<int64_t> *expected_checksums{nullptr};
268+
std::vector<int64_t> expected_checksums;
268269

269270
static LoadKvCachesOptions WithTraceInfo(std::shared_ptr<TransferTraceInfo> trace_info) {
270271
LoadKvCachesOptions options;
271272
options.trace_info = trace_info;
272273
return options;
273274
}
274275

275-
static LoadKvCachesOptions VerifyWith(const std::vector<int64_t> &checksums) {
276+
static LoadKvCachesOptions VerifyWith(std::vector<int64_t> checksums) {
276277
LoadKvCachesOptions options;
277-
options.expected_checksums = &checksums;
278+
options.expected_checksums = std::move(checksums);
278279
return options;
279280
}
280281

281-
static LoadKvCachesOptions VerifyWith(const std::vector<int64_t> &checksums,
282+
static LoadKvCachesOptions VerifyWith(std::vector<int64_t> checksums,
282283
std::shared_ptr<TransferTraceInfo> trace_info) {
283284
LoadKvCachesOptions options;
284285
options.trace_info = trace_info;
285-
options.expected_checksums = &checksums;
286+
options.expected_checksums = std::move(checksums);
286287
return options;
287288
}
288289
};

kv_cache_manager/client/src/internal/stub/grpc_stub.cc

Lines changed: 2 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -90,25 +90,6 @@ GenLocations(const google::protobuf::RepeatedPtrField<::kv_cache_manager::proto:
9090
return result;
9191
}
9292

93-
kv_cache_manager::ClientErrorCode
94-
GenCacheLocation(const kv_cache_manager::Locations &locations,
95-
google::protobuf::RepeatedPtrField<::kv_cache_manager::proto::meta::CacheLocation> *proto_locations) {
96-
if (locations.empty()) {
97-
return kv_cache_manager::ClientErrorCode::ER_OK;
98-
}
99-
for (const auto &location : locations) {
100-
auto *cache_location = proto_locations->Add();
101-
cache_location->set_type(::kv_cache_manager::proto::meta::StorageType::ST_UNSPECIFIED);
102-
cache_location->set_spec_size(-1);
103-
for (const auto &location_spec : location) {
104-
auto *spec = cache_location->add_location_specs();
105-
spec->set_name(location_spec.spec_name);
106-
spec->set_uri(location_spec.uri);
107-
}
108-
}
109-
return kv_cache_manager::ClientErrorCode::ER_OK;
110-
}
111-
11293
template <typename ProtoMessage>
11394
inline std::enable_if_t<std::is_base_of_v<google::protobuf::Message, ProtoMessage>>
11495
SetCommonInfo(ProtoMessage &proto_message, const std::string &trace_id, const std::string &instance_id) {
@@ -433,30 +414,8 @@ ClientErrorCode GrpcStub::FinishWriteCache(const std::string &trace_id,
433414
proto::meta::FinishWriteCacheRequest request;
434415
SetCommonInfo(request, trace_id, instance_id);
435416
request.set_write_session_id(write_session_id);
436-
auto proto_locations = request.mutable_locations();
437-
auto ec = GenCacheLocation(locations, proto_locations);
438-
if (ec != ER_OK) {
439-
KVCM_LOG_DEBUG("finish write cache failed, write_session_id: %s, block_mask: %s, locations: %s",
440-
write_session_id.c_str(),
441-
DebugStringUtil::ToString(success_block).c_str(),
442-
DebugStringUtil::ToString(locations).c_str());
443-
return ec;
444-
}
445-
// Fill each checksum into the parallel CacheLocation slot. Length mismatch is a
446-
// client-side bug -- fail loudly instead of dropping checksums silently, otherwise
447-
// the server commits the write with checksum=0 while the caller believes the
448-
// checksum was reported and an undetectable read corruption window opens.
449-
if (!checksums.empty()) {
450-
if (static_cast<int>(checksums.size()) != proto_locations->size()) {
451-
KVCM_LOG_ERROR("checksums size [%zu] mismatches locations size [%d]; refuse to send "
452-
"FinishWriteCache without checksums",
453-
checksums.size(),
454-
proto_locations->size());
455-
return ER_INVALID_PARAMS;
456-
}
457-
for (int i = 0; i < proto_locations->size(); ++i) {
458-
proto_locations->Mutable(i)->set_checksum(checksums[i]);
459-
}
417+
for (auto checksum : checksums) {
418+
request.add_checksums(checksum);
460419
}
461420
ProtoConvert::BlockMaskToProto(success_block, request.mutable_success_blocks());
462421
grpc::ClientContext context;

kv_cache_manager/client/src/internal/stub/test/grpc_stub_test.cc

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,60 @@ TEST_F(GrpcStubTest, TestFinishWriteCacheSuccess) {
475475
}
476476
}
477477

478+
TEST_F(GrpcStubTest, TestFinishWriteCacheWithChecksums) {
479+
auto expected = std::pair<ClientErrorCode, std::string>(ER_OK, default_storage_configs);
480+
ASSERT_EQ(expected,
481+
stub_->RegisterInstance(
482+
"trace1", "default", "instance1", 64, createLocationSpecInfos(2), createModelDeployment(2, 1), {}));
483+
std::string write_session_id;
484+
Locations target_locations;
485+
{
486+
auto [success, write_location] = stub_->StartWriteCache("trace2", "instance1", {1, 2, 3, 4}, {}, {}, 1000000);
487+
ASSERT_EQ(ER_OK, success);
488+
write_session_id = write_location.write_session_id;
489+
target_locations = write_location.locations;
490+
}
491+
{
492+
BlockMask success_block = static_cast<size_t>(4);
493+
Locations unrelated_locations = {{{"tp0", "file://caller-owned-location-subset"}}};
494+
const std::vector<int64_t> checksums = {0x11, 0x22, 0x33, 0x44};
495+
ASSERT_EQ(ER_OK,
496+
stub_->FinishWriteCache(
497+
"trace3", "instance1", write_session_id, success_block, unrelated_locations, checksums));
498+
}
499+
{
500+
auto [success, result] = stub_->GetCacheLocation("trace4",
501+
"instance1",
502+
QueryType::QT_PREFIX_MATCH,
503+
{1, 2, 3, 4},
504+
{},
505+
static_cast<size_t>(0),
506+
{},
507+
MatchLocationOptions::WithChecksums());
508+
ASSERT_EQ(ER_OK, success);
509+
ExpectLocationsEq(target_locations, result.locations);
510+
ASSERT_EQ((std::vector<int64_t>{0x11, 0x22, 0x33, 0x44}), result.checksums);
511+
}
512+
}
513+
514+
TEST_F(GrpcStubTest, TestFinishWriteCacheRejectsChecksumSizeMismatch) {
515+
auto expected = std::pair<ClientErrorCode, std::string>(ER_OK, default_storage_configs);
516+
ASSERT_EQ(expected,
517+
stub_->RegisterInstance(
518+
"trace1", "default", "instance1", 64, createLocationSpecInfos(2), createModelDeployment(2, 1), {}));
519+
std::string write_session_id;
520+
{
521+
auto [success, write_location] = stub_->StartWriteCache("trace2", "instance1", {1, 2, 3, 4}, {}, {}, 1000000);
522+
ASSERT_EQ(ER_OK, success);
523+
write_session_id = write_location.write_session_id;
524+
}
525+
{
526+
BlockMask success_block = static_cast<size_t>(4);
527+
ASSERT_EQ(ER_SERVICE_INVALID_ARGUMENT,
528+
stub_->FinishWriteCache("trace3", "instance1", write_session_id, success_block, {}, {0x11}));
529+
}
530+
}
531+
478532
TEST_F(GrpcStubTest, TestFinishWriteCacheFail) {
479533
auto expected = std::pair<ClientErrorCode, std::string>(ER_OK, default_storage_configs);
480534
ASSERT_EQ(expected,

kv_cache_manager/client/src/internal/util/checksum_verify_util.h

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,18 @@
77
namespace kv_cache_manager {
88

99
// Walk the batch and report the indices of every block whose checksum disagrees.
10-
// The caller uses these indices to log per-block diagnostics or publish a
11-
// per-block ChecksumMismatchEvent.
10+
// The caller uses these indices to log per-block diagnostics.
1211
//
1312
// Sentinels: expected[i] == 0 means "no checksum was stored for this block"
1413
// (legacy data or legacy client). Such entries are skipped in both stages.
1514
//
16-
// strict_mode is kept for API compatibility with earlier revisions; verification
17-
// is always per-block so the function cannot accept XOR-cancelled batches.
1815
struct ChecksumVerifyResult {
1916
bool mismatch = false;
2017
std::vector<std::size_t> faulty_indices; // populated only when mismatch == true
2118
};
2219

2320
inline ChecksumVerifyResult VerifyBatchChecksums(const std::vector<std::int64_t> &expected,
24-
const std::vector<std::int64_t> &actual,
25-
bool strict_mode) {
26-
(void)strict_mode;
21+
const std::vector<std::int64_t> &actual) {
2722
ChecksumVerifyResult result;
2823
if (expected.size() != actual.size()) {
2924
result.mismatch = true;

kv_cache_manager/client/src/internal/util/test/checksum_verify_util_test.cc

Lines changed: 24 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,116 +8,89 @@ using namespace kv_cache_manager;
88
class ChecksumVerifyUtilTest : public TESTBASE {};
99

1010
// 所有 block 一致 -> mismatch=false, faulty_indices 空。
11-
TEST_F(ChecksumVerifyUtilTest, FastPathAllMatch) {
11+
TEST_F(ChecksumVerifyUtilTest, AllMatch) {
1212
std::vector<int64_t> expected = {0x1111, 0x2222, 0x3333};
1313
std::vector<int64_t> actual = expected;
14-
auto r = VerifyBatchChecksums(expected, actual, /*strict_mode=*/false);
14+
auto r = VerifyBatchChecksums(expected, actual);
1515
EXPECT_FALSE(r.mismatch);
1616
EXPECT_TRUE(r.faulty_indices.empty());
1717
}
1818

1919
// 检测到不匹配后应该回填 faulty_indices 让上层逐块打日志。
20-
TEST_F(ChecksumVerifyUtilTest, FastPathDetectsMismatchAndLocatesIndex) {
20+
TEST_F(ChecksumVerifyUtilTest, DetectsMismatchAndLocatesIndex) {
2121
std::vector<int64_t> expected = {0x1111, 0x2222, 0x3333};
2222
std::vector<int64_t> actual = {0x1111, 0xFFFF, 0x3333}; // block #1 错
23-
auto r = VerifyBatchChecksums(expected, actual, /*strict_mode=*/false);
23+
auto r = VerifyBatchChecksums(expected, actual);
2424
ASSERT_TRUE(r.mismatch);
2525
ASSERT_EQ(r.faulty_indices.size(), 1u);
2626
EXPECT_EQ(r.faulty_indices[0], 1u);
2727
}
2828

2929
// 多个错位 block:把所有错的都列出来。
30-
TEST_F(ChecksumVerifyUtilTest, FastPathListsAllFaultyBlocks) {
30+
TEST_F(ChecksumVerifyUtilTest, ListsAllFaultyBlocks) {
3131
std::vector<int64_t> expected = {0x1111, 0x2222, 0x3333, 0x4444};
3232
std::vector<int64_t> actual = {0xAAAA, 0x2222, 0xBBBB, 0x4444}; // #0, #2 错
33-
auto r = VerifyBatchChecksums(expected, actual, /*strict_mode=*/false);
33+
auto r = VerifyBatchChecksums(expected, actual);
3434
ASSERT_TRUE(r.mismatch);
3535
ASSERT_EQ(r.faulty_indices.size(), 2u);
3636
EXPECT_EQ(r.faulty_indices[0], 0u);
3737
EXPECT_EQ(r.faulty_indices[1], 2u);
3838
}
3939

4040
// expected[i] == 0 是 sentinel (legacy data / legacy client),跳过比对。
41-
TEST_F(ChecksumVerifyUtilTest, FastPathSentinelZeroIsSkipped) {
41+
TEST_F(ChecksumVerifyUtilTest, SentinelZeroIsSkipped) {
4242
std::vector<int64_t> expected = {0x1111, 0, 0x3333}; // block #1 没有 checksum
4343
std::vector<int64_t> actual = {0x1111, 0xDEADBEEF, 0x3333}; // #1 的 actual 不会被比较
44-
auto r = VerifyBatchChecksums(expected, actual, /*strict_mode=*/false);
44+
auto r = VerifyBatchChecksums(expected, actual);
4545
EXPECT_FALSE(r.mismatch);
4646
}
4747

4848
// 全 sentinel:没有可比较的项,等同于全 match。
49-
TEST_F(ChecksumVerifyUtilTest, FastPathAllSentinelsTreatedAsMatch) {
49+
TEST_F(ChecksumVerifyUtilTest, AllSentinelsTreatedAsMatch) {
5050
std::vector<int64_t> expected = {0, 0, 0};
5151
std::vector<int64_t> actual = {0xAA, 0xBB, 0xCC};
52-
auto r = VerifyBatchChecksums(expected, actual, /*strict_mode=*/false);
52+
auto r = VerifyBatchChecksums(expected, actual);
5353
EXPECT_FALSE(r.mismatch);
5454
}
5555

5656
// Size mismatch (上层 bug): mismatch=true 且 faulty_indices 空 -> 上层走 size 错误日志。
5757
TEST_F(ChecksumVerifyUtilTest, SizeMismatchReturnsMismatchWithoutIndices) {
5858
std::vector<int64_t> expected = {0x1111, 0x2222};
5959
std::vector<int64_t> actual = {0x1111};
60-
auto r = VerifyBatchChecksums(expected, actual, /*strict_mode=*/false);
60+
auto r = VerifyBatchChecksums(expected, actual);
6161
EXPECT_TRUE(r.mismatch);
6262
EXPECT_TRUE(r.faulty_indices.empty());
6363
}
6464

65-
// strict_mode 参数保留兼容;当前实现始终逐块比对。
66-
TEST_F(ChecksumVerifyUtilTest, StrictModeMatchesFastFallback) {
67-
std::vector<int64_t> expected = {0x1111, 0, 0x3333, 0x4444};
68-
std::vector<int64_t> actual = {0xAAAA, 0x2222, 0x3333, 0xBBBB}; // #0, #3 错;#1 是 sentinel
69-
auto r = VerifyBatchChecksums(expected, actual, /*strict_mode=*/true);
70-
ASSERT_TRUE(r.mismatch);
71-
ASSERT_EQ(r.faulty_indices.size(), 2u);
72-
EXPECT_EQ(r.faulty_indices[0], 0u);
73-
EXPECT_EQ(r.faulty_indices[1], 3u);
74-
}
75-
76-
// Strict mode + all match: 仍然 mismatch=false。
77-
TEST_F(ChecksumVerifyUtilTest, StrictModeAllMatch) {
78-
std::vector<int64_t> expected = {0x1111, 0x2222, 0x3333};
79-
std::vector<int64_t> actual = expected;
80-
auto r = VerifyBatchChecksums(expected, actual, /*strict_mode=*/true);
81-
EXPECT_FALSE(r.mismatch);
82-
}
83-
8465
// Block swap (读串): expected=[A,B], actual=[B,A]. 必须识别并回填两个 faulty index。
85-
TEST_F(ChecksumVerifyUtilTest, FastPathCatchesBlockSwap) {
66+
TEST_F(ChecksumVerifyUtilTest, CatchesBlockSwap) {
8667
std::vector<int64_t> expected = {0xAAAA, 0xBBBB};
8768
std::vector<int64_t> actual = {0xBBBB, 0xAAAA};
88-
auto r_fast = VerifyBatchChecksums(expected, actual, /*strict_mode=*/false);
89-
ASSERT_TRUE(r_fast.mismatch);
90-
ASSERT_EQ(r_fast.faulty_indices.size(), 2u);
91-
EXPECT_EQ(r_fast.faulty_indices[0], 0u);
92-
EXPECT_EQ(r_fast.faulty_indices[1], 1u);
93-
// Strict 一致。
94-
auto r_strict = VerifyBatchChecksums(expected, actual, /*strict_mode=*/true);
95-
ASSERT_TRUE(r_strict.mismatch);
96-
EXPECT_EQ(r_strict.faulty_indices.size(), 2u);
69+
auto r = VerifyBatchChecksums(expected, actual);
70+
ASSERT_TRUE(r.mismatch);
71+
ASSERT_EQ(r.faulty_indices.size(), 2u);
72+
EXPECT_EQ(r.faulty_indices[0], 0u);
73+
EXPECT_EQ(r.faulty_indices[1], 1u);
9774
}
9875

99-
// Same-delta 成对突变:每个 block 都被同一 delta 改写 (expected=[A,B],
100-
// actual=[A^X, B^X])。老 XOR fast 会 delta 对消而漏;逐块比对必须识别
76+
// Same-delta 成对突变:每个 block 都被同一 delta 改写
77+
// 逐块比对不能让这种 batch 通过
10178
TEST_F(ChecksumVerifyUtilTest, DetectsSameDeltaPairedMutation) {
10279
constexpr int64_t kDelta = 0x0F0F0F0F0F0F0F0FLL;
10380
std::vector<int64_t> expected = {0xAAAA, 0xBBBB};
10481
std::vector<int64_t> actual = {0xAAAA ^ kDelta, 0xBBBB ^ kDelta};
105-
auto r_fast = VerifyBatchChecksums(expected, actual, /*strict_mode=*/false);
106-
ASSERT_TRUE(r_fast.mismatch);
107-
EXPECT_EQ(r_fast.faulty_indices.size(), 2u);
108-
auto r_strict = VerifyBatchChecksums(expected, actual, /*strict_mode=*/true);
109-
ASSERT_TRUE(r_strict.mismatch);
110-
EXPECT_EQ(r_strict.faulty_indices.size(), 2u);
82+
auto r = VerifyBatchChecksums(expected, actual);
83+
ASSERT_TRUE(r.mismatch);
84+
EXPECT_EQ(r.faulty_indices.size(), 2u);
11185
}
11286

113-
// High-bit 成对突变:奇数乘法聚合也会让最高位 delta 在偶数个 block 中抵消。
114-
// 逐块比对不能接受这种 batch。
87+
// High-bit 成对突变:逐块比对不能接受这种 batch。
11588
TEST_F(ChecksumVerifyUtilTest, DetectsHighBitPairedMutation) {
11689
constexpr int64_t kHighBit = static_cast<int64_t>(0x8000000000000000ULL);
11790
std::vector<int64_t> expected = {0x1111, 0x2222, 0x3333};
11891
std::vector<int64_t> actual = {0x1111 ^ kHighBit, 0x2222 ^ kHighBit, 0x3333};
11992

120-
auto r = VerifyBatchChecksums(expected, actual, /*strict_mode=*/false);
93+
auto r = VerifyBatchChecksums(expected, actual);
12194
ASSERT_TRUE(r.mismatch);
12295
ASSERT_EQ(r.faulty_indices.size(), 2u);
12396
EXPECT_EQ(r.faulty_indices[0], 0u);

kv_cache_manager/client/src/meta_client_impl.cc

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,17 +174,16 @@ ClientErrorCode MetaClientImpl::FinishWrite(const std::string &trace_id,
174174
const BlockMask &success_block,
175175
const Locations &locations,
176176
const FinishWriteOptions &options) {
177-
const std::vector<int64_t> empty_checksums;
178-
const auto &checksums = options.checksums == nullptr ? empty_checksums : *options.checksums;
179177
KVCM_LOG_DEBUG("finish write with trace_id [%s], write_session_id [%s], block_mask %s, locations %s, "
180178
"checksums_size %zu",
181179
trace_id.c_str(),
182180
write_session_id.c_str(),
183181
DebugStringUtil::ToString(success_block).c_str(),
184182
DebugStringUtil::ToString(locations).c_str(),
185-
checksums.size());
183+
options.checksums.size());
186184
const std::string &instance_id = CHECK_INSTANCE_STUB();
187-
return stub_->FinishWriteCache(trace_id, instance_id, write_session_id, success_block, locations, checksums);
185+
return stub_->FinishWriteCache(
186+
trace_id, instance_id, write_session_id, success_block, locations, options.checksums);
188187
}
189188

190189
ClientErrorCode MetaClientImpl::RemoveCache(const std::string &trace_id,

0 commit comments

Comments
 (0)