Skip to content

Commit 28b74ec

Browse files
authored
[Filestore] do not release barrier if unconfirmed data already deleted (#6916)
### Notes Do not release barrier if unconfirmed data already deleted Added ai-assisted UT Case that happened in test: ``` • tablet restarted • connection established with client • client made WriteData • AddDataUnconfirmed page faulted • Client disconnected -> Unconfirmed data started deletion • AddDataUnconfirmed restarted ``` ### Issue #2293
1 parent b647e50 commit 28b74ec

3 files changed

Lines changed: 189 additions & 1 deletion

File tree

cloud/filestore/libs/storage/tablet/tablet_actor_adddata_unconfirmed.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ bool TIndexTabletActor::PrepareTx_AddDataUnconfirmed(
3535
}
3636

3737
if (DeletionQueue.contains(args.CommitId)) {
38+
args.RejectedByDeletion = true;
3839
args.Error = MakeError(E_REJECTED, "Already deleted");
3940
LOG_WARN(
4041
ctx,
@@ -83,7 +84,8 @@ void TIndexTabletActor::CompleteTx_AddDataUnconfirmed(
8384
TABLET_VERIFY(inProgressIt != UnconfirmedDataInProgress.end());
8485

8586
const ui64 requestBytes = inProgressIt->second.Data.GetLength();
86-
const bool deletionInProgress = DeletionQueue.contains(args.CommitId);
87+
const bool deletionInProgress =
88+
args.RejectedByDeletion || DeletionQueue.contains(args.CommitId);
8789

8890
Y_DEFER
8991
{

cloud/filestore/libs/storage/tablet/tablet_tx.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2413,6 +2413,12 @@ struct TTxIndexTablet
24132413
{
24142414
const ui64 CommitId;
24152415

2416+
// Set in PrepareTx when the request is rejected because the commitId
2417+
// is already in DeletionQueue. In that case DeleteUnconfirmedData
2418+
// owns the collect barrier and CompleteTx must not release it, even
2419+
// if the commitId is gone from DeletionQueue by then.
2420+
bool RejectedByDeletion = false;
2421+
24162422
TAddDataUnconfirmed(
24172423
TRequestInfoPtr requestInfo,
24182424
const NProtoPrivate::TGenerateBlobIdsRequest& request,

cloud/filestore/libs/storage/tablet/tablet_ut_unconfirmed_data.cpp

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
#include <contrib/ydb/core/base/blobstorage.h>
1212
#include <contrib/ydb/core/base/logoblob.h>
1313
#include <contrib/ydb/core/mind/local.h>
14+
#include <contrib/ydb/core/tablet_flat/shared_cache_events.h>
1415
#include <contrib/ydb/core/testlib/basics/storage.h>
16+
#include <contrib/ydb/core/testlib/tx_helpers.h>
1517

1618
#include <library/cpp/testing/unittest/registar.h>
1719

@@ -180,6 +182,76 @@ ui64 RebootTabletAndCreateHandle(TIndexTabletClient& tablet, ui64 nodeId)
180182
return CreateHandle(tablet, nodeId);
181183
}
182184

185+
// Moves the Nodes table from the executor memtable into an SST and shrinks
186+
// the private cache so that subsequent node reads page-fault (the shared
187+
// cache is expected to be disabled via TSharedCacheConfig). The generations
188+
// must stay in sync with CreateIndexTablePolicy: the executor forbids
189+
// decreasing the level count.
190+
void CompactNodesTable(
191+
TTestEnv& env,
192+
TIndexTabletClient& tablet,
193+
ui64 tabletId,
194+
ui64 nodeId)
195+
{
196+
auto& runtime = env.GetRuntime();
197+
198+
NTabletFlatScheme::TSchemeChanges scheme;
199+
TString err;
200+
const TString policyChange = Sprintf(R"___(
201+
Delta {
202+
DeltaType: SetCompactionPolicy
203+
TableId: %u
204+
CompactionPolicy {
205+
InMemSizeToSnapshot: 1
206+
InMemStepsToSnapshot: 1
207+
InMemForceStepsToSnapshot: 1
208+
InMemForceSizeToSnapshot: 1
209+
Generation {
210+
GenerationId: 0
211+
SizeToCompact: 67108864
212+
CountToCompact: 8
213+
ForceCountToCompact: 24
214+
ForceSizeToCompact: 134217728
215+
ResourceBrokerTask: "compaction_gen1"
216+
KeepInCache: false
217+
}
218+
Generation {
219+
GenerationId: 1
220+
SizeToCompact: 335544320
221+
CountToCompact: 5
222+
ForceCountToCompact: 15
223+
ForceSizeToCompact: 671088640
224+
ResourceBrokerTask: "compaction_gen2"
225+
KeepInCache: false
226+
}
227+
Generation {
228+
GenerationId: 2
229+
SizeToCompact: 1677721600
230+
CountToCompact: 5
231+
ForceCountToCompact: 10
232+
ForceSizeToCompact: 3355443200
233+
ResourceBrokerTask: "compaction_gen3"
234+
KeepInCache: false
235+
}
236+
}
237+
}
238+
)___", TIndexTabletSchema::Nodes::TableId);
239+
const auto status = NKikimr::LocalSchemeTx(
240+
runtime,
241+
tabletId,
242+
policyChange,
243+
false,
244+
scheme,
245+
err);
246+
UNIT_ASSERT_VALUES_EQUAL_C(NKikimrProto::OK, status, err);
247+
248+
env.UpdatePrivateCacheSize(tabletId, 1);
249+
250+
// Touch the Nodes table to trigger the memtable snapshot
251+
tablet.SetNodeAttr(TSetNodeAttrArgs(nodeId).SetUid(1));
252+
runtime.DispatchEvents({}, TDuration::MilliSeconds(100));
253+
}
254+
183255
} // namespace
184256

185257
////////////////////////////////////////////////////////////////////////////////
@@ -910,6 +982,114 @@ Y_UNIT_TEST_SUITE(TIndexTabletTest_UnconfirmedData)
910982
AssertStorageStats(tablet, 0, 0);
911983
}
912984

985+
// AddDataUnconfirmed prepares after the commitId got into DeletionQueue
986+
// (rejected as already deleted) and completes after DeleteUnconfirmedData
987+
// has both released the collect barrier and erased the commitId from
988+
// DeletionQueue. CompleteTx must not release the barrier again.
989+
Y_UNIT_TEST(ShouldNotReleaseCollectBarrierTwiceWhenRejectedAsAlreadyDeleted)
990+
{
991+
constexpr ui32 block = 4_KB;
992+
993+
NProto::TStorageConfig storageConfig;
994+
storageConfig.SetWriteBlobThreshold(1);
995+
storageConfig.SetAddingUnconfirmedDataEnabled(true);
996+
storageConfig.SetUnconfirmedDataCountHardLimit(10);
997+
998+
NKikimr::NSharedCache::TSharedCacheConfig sharedCacheConfig;
999+
sharedCacheConfig.SetMemoryLimit(0);
1000+
1001+
TTestEnv env({}, std::move(storageConfig), &sharedCacheConfig);
1002+
ui32 nodeIdx = env.AddDynamicNode();
1003+
ui64 tabletId = env.BootIndexTablet(nodeIdx);
1004+
1005+
auto& runtime = env.GetRuntime();
1006+
1007+
TIndexTabletClient tablet(runtime, nodeIdx, tabletId);
1008+
tablet.InitSession("client", "session");
1009+
1010+
auto id = CreateNode(tablet, TCreateNodeArgs::File(RootNodeId, "test"));
1011+
ui64 handle = CreateHandle(tablet, id);
1012+
1013+
// Force the Nodes table into an SST and keep both caches cold so
1014+
// that ReadNode in PrepareTx_AddDataUnconfirmed page-faults
1015+
CompactNodesTable(env, tablet, tabletId, id);
1016+
1017+
TVector<TAutoPtr<IEventHandle>> heldPageLoads;
1018+
TVector<TAutoPtr<IEventHandle>> heldCommitResults;
1019+
bool holdPageLoads = true;
1020+
runtime.SetEventFilter(
1021+
[&](auto& runtime, auto& event)
1022+
{
1023+
Y_UNUSED(runtime);
1024+
switch (event->GetTypeRewrite()) {
1025+
case TEvTablet::EvCommitResult:
1026+
heldCommitResults.push_back(event.Release());
1027+
return true;
1028+
case NKikimr::NSharedCache::TEvResult::EventType:
1029+
if (holdPageLoads) {
1030+
heldPageLoads.push_back(event.Release());
1031+
return true;
1032+
}
1033+
break;
1034+
}
1035+
return false;
1036+
});
1037+
1038+
// AddDataUnconfirmed parks in PrepareTx waiting for the held page
1039+
// load
1040+
auto gbi = tablet.GenerateBlobIds(id, handle, 0, block);
1041+
UNIT_ASSERT(gbi->Record.GetUnconfirmedFlowEnabled());
1042+
1043+
runtime.DispatchEvents(
1044+
TDispatchOptions{
1045+
.CustomFinalCondition = [&]()
1046+
{ return !heldPageLoads.empty(); }},
1047+
TDuration::Seconds(1));
1048+
UNIT_ASSERT_C(
1049+
!heldPageLoads.empty(),
1050+
"Expected AddDataUnconfirmed to page-fault on Nodes read");
1051+
1052+
// The disconnect handler puts the commitId into DeletionQueue and
1053+
// runs DeleteUnconfirmedData, which releases the collect barrier.
1054+
// Its CompleteTx (DeletionQueue cleanup) is blocked on the held
1055+
// commit result.
1056+
tablet.DisconnectPipe();
1057+
runtime.DispatchEvents(
1058+
TDispatchOptions{
1059+
.CustomFinalCondition = [&]()
1060+
{ return !heldCommitResults.empty(); }},
1061+
TDuration::Seconds(1));
1062+
UNIT_ASSERT_C(
1063+
!heldCommitResults.empty(),
1064+
"Expected DeleteUnconfirmedData commit result to be held");
1065+
1066+
// AddDataUnconfirmed now prepares and gets rejected: the commitId is
1067+
// still in DeletionQueue
1068+
holdPageLoads = false;
1069+
for (auto it = heldPageLoads.rbegin(); it != heldPageLoads.rend();
1070+
++it)
1071+
{
1072+
runtime.PushFront(*it);
1073+
}
1074+
heldPageLoads.clear();
1075+
runtime.DispatchEvents({}, TDuration::MilliSeconds(100));
1076+
1077+
// DeleteUnconfirmedData completes and erases the commitId from
1078+
// DeletionQueue, then the rejected AddDataUnconfirmed completes
1079+
runtime.SetEventFilter(TTestActorRuntimeBase::DefaultFilterFunc);
1080+
for (auto it = heldCommitResults.rbegin();
1081+
it != heldCommitResults.rend();
1082+
++it)
1083+
{
1084+
runtime.PushFront(*it);
1085+
}
1086+
heldCommitResults.clear();
1087+
runtime.DispatchEvents({}, TDuration::MilliSeconds(100));
1088+
1089+
tablet.ReconnectPipe();
1090+
AssertStorageStats(tablet, 0, 0);
1091+
}
1092+
9131093
Y_UNIT_TEST(ShouldIgnoreUnconfirmedDataTimeoutAfterConfirm)
9141094
{
9151095
constexpr ui32 block = 4_KB;

0 commit comments

Comments
 (0)