summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgenik2 <[email protected]>2026-07-15 10:44:11 +0300
committerGitHub <[email protected]>2026-07-15 10:44:11 +0300
commit27df2a9f85c4ce2680e09122fab49dd141dce8a5 (patch)
tree8d6dced8a4e01f4639cd67544faf91540bcec659
parentbed1a355b2932bd108fe3cb8f8b376988d851ead (diff)
Delete ddisk and persistent buffer from dbg api (#46536)
-rw-r--r--ydb/core/blobstorage/ut_blobstorage/ddisk.cpp181
-rw-r--r--ydb/core/mind/bscontroller/ddisk.cpp49
-rw-r--r--ydb/core/protos/blobstorage.proto11
3 files changed, 241 insertions, 0 deletions
diff --git a/ydb/core/blobstorage/ut_blobstorage/ddisk.cpp b/ydb/core/blobstorage/ut_blobstorage/ddisk.cpp
index 4700fffe78a..4efa0db957c 100644
--- a/ydb/core/blobstorage/ut_blobstorage/ddisk.cpp
+++ b/ydb/core/blobstorage/ut_blobstorage/ddisk.cpp
@@ -100,6 +100,54 @@ Y_UNIT_TEST_SUITE(DDisk) {
return responses;
}
+ // Allocate a single direct block group via the new-style DirectBlockGroupOperations
+ // (as opposed to the compat Queries path) so that DDisks/PersistentBuffers can later
+ // be individually manipulated (deleted, reassigned, etc.) by DirectBlockGroupId.
+ NKikimrBlobStorage::TEvControllerAllocateDDiskBlockGroupResult::TDirectBlockGroup
+ DefineDirectBlockGroup(ui64 directBlockGroupId, ui32 numDDisks, ui32 numChunksPerDDisk, ui32 numPersistentBuffers) {
+ auto ev = std::make_unique<TEvBlobStorage::TEvControllerAllocateDDiskBlockGroup>();
+ auto& r = ev->Record;
+ r.SetDDiskPoolName("ddisk_pool");
+ r.SetPersistentBufferDDiskPoolName("ddisk_pool");
+ r.SetTabletId(1);
+ auto *op = r.AddDirectBlockGroupOperations();
+ op->SetDirectBlockGroupId(directBlockGroupId);
+ auto *def = op->MutableDefineDirectBlockGroup();
+ def->SetNumDDisks(numDDisks);
+ def->SetNumChunksPerDDisk(numChunksPerDDisk);
+ def->SetNumPersistentBuffers(numPersistentBuffers);
+ // Use a fresh edge actor for every request: WaitForEdgeActorEvent()
+ // consumes/terminates the edge actor on capture by default (termOnCapture=true),
+ // so it can't be safely reused across multiple requests.
+ TActorId edge = Env.Runtime->AllocateEdgeActor(Env.Settings.ControllerNodeId, __FILE__, __LINE__);
+ Env.Runtime->SendToPipe(MakeBSControllerID(), edge, ev.release(), 0, TTestActorSystem::GetPipeConfigWithRetries());
+ auto response = Env.WaitForEdgeActorEvent<TEvBlobStorage::TEvControllerAllocateDDiskBlockGroupResult>(edge);
+ auto& rr = response->Get()->Record;
+ UNIT_ASSERT_VALUES_EQUAL_C(rr.GetStatus(), NKikimrProto::OK, rr.GetErrorReason());
+ UNIT_ASSERT_VALUES_EQUAL(rr.DirectBlockGroupsSize(), 1);
+ return rr.GetDirectBlockGroups(0);
+ }
+
+ // Send an arbitrary set of DirectBlockGroupOperation commands for a given
+ // direct block group id and return the whole result record (including status).
+ NKikimrBlobStorage::TEvControllerAllocateDDiskBlockGroupResult SendDirectBlockGroupOperation(
+ ui64 directBlockGroupId,
+ std::function<void(NKikimrBlobStorage::TEvControllerAllocateDDiskBlockGroup::TDirectBlockGroupOperation*)> fill) {
+ auto ev = std::make_unique<TEvBlobStorage::TEvControllerAllocateDDiskBlockGroup>();
+ auto& r = ev->Record;
+ r.SetDDiskPoolName("ddisk_pool");
+ r.SetPersistentBufferDDiskPoolName("ddisk_pool");
+ r.SetTabletId(1);
+ auto *op = r.AddDirectBlockGroupOperations();
+ op->SetDirectBlockGroupId(directBlockGroupId);
+ fill(op);
+ // Fresh edge actor, see comment in DefineDirectBlockGroup() above.
+ TActorId edge = Env.Runtime->AllocateEdgeActor(Env.Settings.ControllerNodeId, __FILE__, __LINE__);
+ Env.Runtime->SendToPipe(MakeBSControllerID(), edge, ev.release(), 0, TTestActorSystem::GetPipeConfigWithRetries());
+ auto response = Env.WaitForEdgeActorEvent<TEvBlobStorage::TEvControllerAllocateDDiskBlockGroupResult>(edge);
+ return response->Get()->Record;
+ }
+
void GreetDDisks() {
Creds.TabletId = 1;
Creds.Generation = 1;
@@ -1364,4 +1412,137 @@ Y_UNIT_TEST_SUITE(DDisk) {
UNIT_ASSERT_C(foundReleasedChunk,
"At least one chunk that held persistent-buffer data must have been released back to PDisk");
}
+
+ // ─────────────────────────────────────────────────────────────────────────
+ // Tests for TDirectBlockGroupOperation::DeletePersistentBuffers and
+ // TDirectBlockGroupOperation::DeleteDDisks (ddisk.cpp handling of
+ // op.GetDeletePersistentBuffers() / op.GetDeleteDDisks()).
+ // ─────────────────────────────────────────────────────────────────────────
+
+ Y_UNIT_TEST(DeletePersistentBufferRemovesEntry) {
+ TDDiskTestContext f;
+ auto group = f.DefineDirectBlockGroup(/*directBlockGroupId=*/1, /*numDDisks=*/2,
+ /*numChunksPerDDisk=*/1, /*numPersistentBuffers=*/2);
+ UNIT_ASSERT_VALUES_EQUAL(group.DDiskIdSize(), 2);
+ UNIT_ASSERT_VALUES_EQUAL(group.PersistentBufferDDiskIdSize(), 2);
+
+ const auto pbToDelete = group.GetPersistentBufferDDiskId(0);
+
+ auto rr = f.SendDirectBlockGroupOperation(1, [&](auto *op) {
+ auto *cmd = op->AddDeletePersistentBuffers();
+ cmd->MutablePersistentBufferId()->CopyFrom(pbToDelete);
+ });
+ UNIT_ASSERT_VALUES_EQUAL_C(rr.GetStatus(), NKikimrProto::OK, rr.GetErrorReason());
+ UNIT_ASSERT_VALUES_EQUAL(rr.DirectBlockGroupsSize(), 1);
+
+ const auto& updated = rr.GetDirectBlockGroups(0);
+ UNIT_ASSERT_VALUES_EQUAL(updated.PersistentBufferDDiskIdSize(), 1);
+ // the remaining persistent buffer must be the one we did NOT delete
+ UNIT_ASSERT(TDDiskId(updated.GetPersistentBufferDDiskId(0)) == TDDiskId(group.GetPersistentBufferDDiskId(1)));
+ // DDisks themselves are untouched by persistent buffer deletion
+ UNIT_ASSERT_VALUES_EQUAL(updated.DDiskIdSize(), 2);
+ }
+
+ Y_UNIT_TEST(DeletePersistentBufferNotFoundReturnsError) {
+ TDDiskTestContext f;
+ f.DefineDirectBlockGroup(/*directBlockGroupId=*/1, /*numDDisks=*/2,
+ /*numChunksPerDDisk=*/1, /*numPersistentBuffers=*/1);
+
+ auto rr = f.SendDirectBlockGroupOperation(1, [&](auto *op) {
+ auto *cmd = op->AddDeletePersistentBuffers();
+ // bogus, never-allocated identifier
+ cmd->MutablePersistentBufferId()->SetNodeId(999);
+ cmd->MutablePersistentBufferId()->SetPDiskId(999);
+ cmd->MutablePersistentBufferId()->SetDDiskSlotId(999);
+ });
+ UNIT_ASSERT_VALUES_EQUAL(rr.GetStatus(), NKikimrProto::ERROR);
+ UNIT_ASSERT_STRING_CONTAINS(rr.GetErrorReason(), "PersistentBuffer not found");
+ }
+
+ Y_UNIT_TEST(DeleteDDiskRemovesEntry) {
+ TDDiskTestContext f;
+ auto group = f.DefineDirectBlockGroup(/*directBlockGroupId=*/1, /*numDDisks=*/3,
+ /*numChunksPerDDisk=*/1, /*numPersistentBuffers=*/1);
+ UNIT_ASSERT_VALUES_EQUAL(group.DDiskIdSize(), 3);
+
+ const auto ddiskToDelete = group.GetDDiskId(2); // delete the last one so trimming kicks in
+
+ auto rr = f.SendDirectBlockGroupOperation(1, [&](auto *op) {
+ auto *cmd = op->AddDeleteDDisks();
+ cmd->MutableDDiskId()->CopyFrom(ddiskToDelete);
+ });
+ UNIT_ASSERT_VALUES_EQUAL_C(rr.GetStatus(), NKikimrProto::OK, rr.GetErrorReason());
+ UNIT_ASSERT_VALUES_EQUAL(rr.DirectBlockGroupsSize(), 1);
+
+ const auto& updated = rr.GetDirectBlockGroups(0);
+ // the trailing empty item gets trimmed away entirely
+ UNIT_ASSERT_VALUES_EQUAL(updated.DDiskIdSize(), 2);
+ for (ui32 i = 0; i < 2; ++i) {
+ UNIT_ASSERT(TDDiskId(updated.GetDDiskId(i)) == TDDiskId(group.GetDDiskId(i)));
+ }
+ // persistent buffers are untouched by DDisk deletion
+ UNIT_ASSERT_VALUES_EQUAL(updated.PersistentBufferDDiskIdSize(), 1);
+ }
+
+ Y_UNIT_TEST(DeleteDDiskNotFoundReturnsError) {
+ TDDiskTestContext f;
+ f.DefineDirectBlockGroup(/*directBlockGroupId=*/1, /*numDDisks=*/2,
+ /*numChunksPerDDisk=*/1, /*numPersistentBuffers=*/1);
+
+ auto rr = f.SendDirectBlockGroupOperation(1, [&](auto *op) {
+ auto *cmd = op->AddDeleteDDisks();
+ cmd->MutableDDiskId()->SetNodeId(999);
+ cmd->MutableDDiskId()->SetPDiskId(999);
+ cmd->MutableDDiskId()->SetDDiskSlotId(999);
+ });
+ UNIT_ASSERT_VALUES_EQUAL(rr.GetStatus(), NKikimrProto::ERROR);
+ UNIT_ASSERT_STRING_CONTAINS(rr.GetErrorReason(), "DDisk not found");
+ }
+
+ Y_UNIT_TEST(DeleteMiddleDDiskRemovesEntry) {
+ TDDiskTestContext f;
+ auto group = f.DefineDirectBlockGroup(/*directBlockGroupId=*/1, /*numDDisks=*/3,
+ /*numChunksPerDDisk=*/1, /*numPersistentBuffers=*/1);
+ UNIT_ASSERT_VALUES_EQUAL(group.DDiskIdSize(), 3);
+
+ const auto ddiskToDelete = group.GetDDiskId(1); // middle one
+
+ auto rr = f.SendDirectBlockGroupOperation(1, [&](auto *op) {
+ auto *cmd = op->AddDeleteDDisks();
+ cmd->MutableDDiskId()->CopyFrom(ddiskToDelete);
+ });
+ UNIT_ASSERT_VALUES_EQUAL_C(rr.GetStatus(), NKikimrProto::OK, rr.GetErrorReason());
+ UNIT_ASSERT_VALUES_EQUAL(rr.DirectBlockGroupsSize(), 1);
+
+ const auto& updated = rr.GetDirectBlockGroups(0);
+ // the deleted middle entry must be removed from the list entirely --
+ // no hole is left behind, and the remaining entries keep their relative order
+ UNIT_ASSERT_VALUES_EQUAL(updated.DDiskIdSize(), 2);
+ UNIT_ASSERT(TDDiskId(updated.GetDDiskId(0)) == TDDiskId(group.GetDDiskId(0)));
+ UNIT_ASSERT(TDDiskId(updated.GetDDiskId(1)) == TDDiskId(group.GetDDiskId(2)));
+ // persistent buffers are untouched by DDisk deletion
+ UNIT_ASSERT_VALUES_EQUAL(updated.PersistentBufferDDiskIdSize(), 1);
+ }
+
+ Y_UNIT_TEST(DeleteAllPersistentBuffersAndDDisks) {
+ TDDiskTestContext f;
+ auto group = f.DefineDirectBlockGroup(/*directBlockGroupId=*/1, /*numDDisks=*/2,
+ /*numChunksPerDDisk=*/1, /*numPersistentBuffers=*/2);
+
+ auto rr = f.SendDirectBlockGroupOperation(1, [&](auto *op) {
+ for (const auto& pb : group.GetPersistentBufferDDiskId()) {
+ op->AddDeletePersistentBuffers()->MutablePersistentBufferId()->CopyFrom(pb);
+ }
+ for (const auto& dd : group.GetDDiskId()) {
+ op->AddDeleteDDisks()->MutableDDiskId()->CopyFrom(dd);
+ }
+ });
+ UNIT_ASSERT_VALUES_EQUAL_C(rr.GetStatus(), NKikimrProto::OK, rr.GetErrorReason());
+ UNIT_ASSERT_VALUES_EQUAL(rr.DirectBlockGroupsSize(), 1);
+
+ const auto& updated = rr.GetDirectBlockGroups(0);
+ UNIT_ASSERT_VALUES_EQUAL(updated.PersistentBufferDDiskIdSize(), 0);
+ // both DDisks removed and the last one had zero claim -> full trim
+ UNIT_ASSERT_VALUES_EQUAL(updated.DDiskIdSize(), 0);
+ }
}
diff --git a/ydb/core/mind/bscontroller/ddisk.cpp b/ydb/core/mind/bscontroller/ddisk.cpp
index 17feacd7ab1..f6e4f36c3f0 100644
--- a/ydb/core/mind/bscontroller/ddisk.cpp
+++ b/ydb/core/mind/bscontroller/ddisk.cpp
@@ -544,6 +544,55 @@ namespace NKikimr::NBsController {
changes = true;
}
+ for (const auto& cmd : op.GetDeletePersistentBuffers()) {
+ const TDDiskId pbId(cmd.GetPersistentBufferId());
+ auto it = std::ranges::find(persistentBufferIds, pbId);
+
+ if (it == persistentBufferIds.end()) {
+ ythrow yexception() << "PersistentBuffer not found";
+ }
+ getPersistentBufferPool().ReleasePersistentBuffer(*it);
+ {
+ auto& [chunks, refs] = vslotUpdates[it->GetKey()];
+ --refs;
+ }
+ const size_t index = it - persistentBufferIds.begin();
+ persistentBufferIds.erase(it);
+ persistentBufferDDiskId->erase(persistentBufferDDiskId->begin() + index);
+ changes = true;
+ }
+
+ for (const auto& cmd : op.GetDeleteDDisks()) {
+ const TDDiskId ddiskId(cmd.GetDDiskId());
+ int index = -1;
+ for (int i = 0; i < ddiskRecord->size(); ++i) {
+ const auto& rec = ddiskRecord->Get(i);
+ if (rec.HasDDiskId() && TDDiskId(rec.GetDDiskId()) == ddiskId) {
+ index = i;
+ break;
+ }
+ }
+ if (index < 0) {
+ ythrow yexception() << "DDisk not found";
+ }
+
+ auto *item = ddiskRecord->Mutable(index);
+ const ui32 currentNumChunks = item->GetNumChunksClaimed();
+
+ getDDiskPool().ReleaseDDisk(ddiskId, currentNumChunks);
+
+ auto it = std::ranges::find(ddiskIds, ddiskId);
+ Y_ABORT_UNLESS(it != ddiskIds.end());
+ std::swap(*it, ddiskIds.back());
+ ddiskIds.pop_back();
+
+ auto& [chunks, refs] = vslotUpdates[TVSlotId(ddiskId.GetKey())];
+ chunks -= currentNumChunks;
+
+ ddiskRecord->erase(ddiskRecord->begin() + index);
+ changes = true;
+ }
+
// trim excessive items
while (!ddiskRecord->empty() && ddiskRecord->rbegin()->GetNumChunksClaimed() == 0) {
ddiskRecord->RemoveLast();
diff --git a/ydb/core/protos/blobstorage.proto b/ydb/core/protos/blobstorage.proto
index 9cadc0aca1f..a83698da532 100644
--- a/ydb/core/protos/blobstorage.proto
+++ b/ydb/core/protos/blobstorage.proto
@@ -1647,6 +1647,17 @@ message TEvControllerAllocateDDiskBlockGroup {
repeated uint32 PreferredNodeIds = 2; // in order of descending preference
}
repeated TReassignPersistentBuffer ReassignPersistentBuffers = 5;
+
+ message TDeletePersistentBuffer {
+ optional NKikimrBlobStorage.NDDisk.TDDiskId PersistentBufferId = 1;
+ }
+ repeated TDeletePersistentBuffer DeletePersistentBuffers = 6;
+
+ message TDeleteDDisk {
+ optional NKikimrBlobStorage.NDDisk.TDDiskId DDiskId = 1;
+ }
+ repeated TDeleteDDisk DeleteDDisks = 7;
+
}
repeated TDirectBlockGroupOperation DirectBlockGroupOperations = 5;
}