Skip to content

Commit 525b102

Browse files
committed
fix: removal of Expiry bit when overriding external strings
The bug: during the override of the existing external string, we called `TieredStorage::Delete` to delete the external reference. This function called CompactObj::Reset that cleared all the attributes on the value, including expiry. The fix: preserve the mask but clear the external state from the object. Fixes #4672 Signed-off-by: Roman Gershman <[email protected]>
1 parent d75cfe8 commit 525b102

5 files changed

+39
-15
lines changed

src/core/compact_object.cc

+2
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,8 @@ void CompactObj::InitRobj(CompactObjType type, unsigned encoding, void* obj) {
875875
}
876876

877877
void CompactObj::SetInt(int64_t val) {
878+
DCHECK(!IsExternal());
879+
878880
if (INT_TAG != taglen_) {
879881
SetMeta(INT_TAG, mask_ & ~kEncMask);
880882
}

src/core/compact_object.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class CompactObj {
140140
// IO_PENDING is set when the tiered storage has issued an i/o request to save the value. It is
141141
// cleared when the io request finishes or is cancelled.
142142
IO_PENDING = 0x20,
143-
143+
144144
// Applied only on keys that should be deleted asynchronously.
145145
// (it can be the same value as IO_PENDING) that is applied only on values.
146146
KEY_ASYNC_DELETE = 0x20,
@@ -364,6 +364,12 @@ class CompactObj {
364364

365365
void SetExternal(size_t offset, uint32_t sz);
366366

367+
// Switches to empty, non-external string.
368+
// Preserves all the attributes.
369+
void RemoveExternal() {
370+
SetMeta(0, mask_);
371+
}
372+
367373
// Assigns a cooling record to the object together with its external slice.
368374
void SetCool(size_t offset, uint32_t serialized_size, detail::TieredColdRecord* record);
369375

src/server/string_family.cc

+6-13
Original file line numberDiff line numberDiff line change
@@ -869,19 +869,6 @@ OpStatus SetCmd::SetExisting(const SetParams& params, DbSlice::Iterator it,
869869
e_it->second = db_slice.FromAbsoluteTime(at_ms);
870870
} else {
871871
// Add new expiry information.
872-
873-
// Note: some consistency checks, following #4672. Once it's resolved we can remove them.
874-
// -------------------------------------------------------------------------------------
875-
ExpireTable* etable = db_slice.GetTables(op_args_.db_cntx.db_index).second;
876-
ExpireIterator check_it = etable->Find(it->first.AsRef());
877-
if (IsValid(check_it)) {
878-
LOG(ERROR) << "Inconsistent state in SetCmd::SetExisting "
879-
<< " key: " << key << ", "
880-
<< "it.key:" << it.key() << ", "
881-
<< "it->first:" << it->first.ToString()
882-
<< " params.prev_val: " << params.prev_val << " " << params.flags;
883-
}
884-
// ------------------------------------------------
885872
db_slice.AddExpire(op_args_.db_cntx.db_index, it, at_ms);
886873
}
887874
} else {
@@ -893,6 +880,8 @@ OpStatus SetCmd::SetExisting(const SetParams& params, DbSlice::Iterator it,
893880
it->first.SetSticky(true);
894881
}
895882

883+
bool has_expire = prime_value.HasExpire();
884+
896885
// Update flags
897886
prime_value.SetFlag(params.memcache_flags != 0);
898887
db_slice.SetMCFlag(op_args_.db_cntx.db_index, it->first.AsRef(), params.memcache_flags);
@@ -902,9 +891,13 @@ OpStatus SetCmd::SetExisting(const SetParams& params, DbSlice::Iterator it,
902891
shard->tiered_storage()->Delete(op_args_.db_cntx.db_index, &prime_value);
903892
}
904893

894+
DCHECK_EQ(has_expire, prime_value.HasExpire());
895+
905896
// overwrite existing entry.
906897
prime_value.SetString(value);
907898

899+
DCHECK_EQ(has_expire, prime_value.HasExpire());
900+
908901
PostEdit(params, key, value, &prime_value);
909902
return OpStatus::OK;
910903
}

src/server/tiered_storage.cc

+3-1
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,8 @@ bool TieredStorage::TryStash(DbIndex dbid, string_view key, PrimeValue* value) {
424424

425425
void TieredStorage::Delete(DbIndex dbid, PrimeValue* value) {
426426
DCHECK(value->IsExternal());
427+
DCHECK(!value->HasStashPending());
428+
427429
++stats_.total_deletes;
428430

429431
tiering::DiskSegment segment = value->GetExternalSlice();
@@ -433,7 +435,7 @@ void TieredStorage::Delete(DbIndex dbid, PrimeValue* value) {
433435
}
434436

435437
// In any case we delete the offloaded segment and reset the value.
436-
value->Reset();
438+
value->RemoveExternal();
437439
op_manager_->DeleteOffloaded(dbid, segment);
438440
}
439441

src/server/tiered_storage_test.cc

+21
Original file line numberDiff line numberDiff line change
@@ -334,4 +334,25 @@ TEST_F(TieredStorageTest, Expiry) {
334334
EXPECT_EQ(resp, val);
335335
}
336336

337+
TEST_F(TieredStorageTest, SetExistingExpire) {
338+
absl::FlagSaver saver;
339+
SetFlag(&FLAGS_tiered_offload_threshold, 0.0f); // offload all values
340+
SetFlag(&FLAGS_tiered_experimental_cooling, false);
341+
342+
const int kNum = 20;
343+
for (size_t i = 0; i < kNum; i++) {
344+
Run({"SETEX", absl::StrCat("k", i), "100", BuildString(256)});
345+
}
346+
ExpectConditionWithinTimeout([&] { return GetMetrics().tiered_stats.total_stashes > 1; });
347+
348+
for (size_t i = 0; i < kNum; i++) {
349+
Run({"SETEX", absl::StrCat("k", i), "100", BuildString(256)});
350+
}
351+
352+
for (size_t i = 0; i < kNum; i++) {
353+
auto resp = Run({"TTL", absl::StrCat("k", i)});
354+
EXPECT_THAT(resp, IntArg(100));
355+
}
356+
}
357+
337358
} // namespace dfly

0 commit comments

Comments
 (0)