Skip to content

Commit f5041e9

Browse files
add lazy repair
1 parent bf70596 commit f5041e9

3 files changed

Lines changed: 42 additions & 4 deletions

File tree

src/storage/compact_filter.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,9 @@ bool SubKeyFilter::Filter([[maybe_unused]] int level, const Slice &key, const Sl
171171
}
172172
// Check if field has expiration and if it's expired (with lazy delete buffer)
173173
if (field_value.expire > 0 && field_value.expire <= lazy_expired_ts) {
174-
return true;
174+
*new_value = HASH_FIELD_STUB_VALUE;
175+
*modified = true;
176+
return false;
175177
}
176178
return false;
177179
}

src/types/redis_hash.cc

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
#include <cctype>
2727
#include <cmath>
2828
#include <random>
29+
#include <thread>
2930
#include <utility>
3031

3132
#include "db_util.h"
33+
#include "logging.h"
3234
#include "parse_util.h"
3335
#include "sample_helper.h"
3436

@@ -66,6 +68,7 @@ rocksdb::Status Hash::Get(engine::Context &ctx, const Slice &user_key, const Sli
6668
return rocksdb::Status::Corruption("failed to decode hash field value");
6769
}
6870
if (field_value.IsExpired()) {
71+
AsyncRepairHash(ns_key, field, metadata);
6972
return rocksdb::Status::NotFound();
7073
}
7174
*value = field_value.value.ToString();
@@ -245,6 +248,7 @@ rocksdb::Status Hash::MGet(engine::Context &ctx, const Slice &user_key, const st
245248
return rocksdb::Status::Corruption("failed to decode hash field value");
246249
}
247250
if (field_value.IsExpired()) {
251+
AsyncRepairHash(ns_key, fields[i], metadata);
248252
values->emplace_back("");
249253
statuses->emplace_back(rocksdb::Status::NotFound());
250254
} else {
@@ -394,16 +398,17 @@ rocksdb::Status Hash::RangeByLex(engine::Context &ctx, const Slice &user_key, co
394398
}
395399
int64_t pos = 0;
396400
for (; iter->Valid() && iter->key().starts_with(prefix_key); (!spec.reversed ? iter->Next() : iter->Prev())) {
401+
InternalKey ikey(iter->key(), storage_->IsSlotIdEncoded());
397402
// Decode and check expiration
398403
HashFieldValue field_value;
399404
if (!HashFieldValue::Decode(iter->value(), &field_value)) {
400405
continue; // Skip corrupted values
401406
}
402407
if (field_value.IsExpired()) {
408+
AsyncRepairHash(ns_key, ikey.GetSubKey(), metadata);
403409
continue; // Skip expired fields
404410
}
405411

406-
InternalKey ikey(iter->key(), storage_->IsSlotIdEncoded());
407412
if (spec.reversed) {
408413
if (ikey.GetSubKey().ToString() < spec.min || (spec.minex && ikey.GetSubKey().ToString() == spec.min)) {
409414
break;
@@ -445,22 +450,22 @@ rocksdb::Status Hash::GetAll(engine::Context &ctx, const Slice &user_key, std::v
445450

446451
auto iter = util::UniqueIterator(ctx, read_options);
447452
for (iter->Seek(prefix_key); iter->Valid() && iter->key().starts_with(prefix_key); iter->Next()) {
453+
InternalKey ikey(iter->key(), storage_->IsSlotIdEncoded());
448454
// Decode and check expiration for all fetch types
449455
HashFieldValue field_value;
450456
if (!HashFieldValue::Decode(iter->value(), &field_value)) {
451457
continue; // Skip corrupted values
452458
}
453459
if (field_value.IsExpired()) {
460+
AsyncRepairHash(ns_key, ikey.GetSubKey(), metadata);
454461
continue; // Skip expired fields
455462
}
456463

457464
if (type == HashFetchType::kOnlyKey) {
458-
InternalKey ikey(iter->key(), storage_->IsSlotIdEncoded());
459465
field_values->emplace_back(ikey.GetSubKey().ToString(), "");
460466
} else if (type == HashFetchType::kOnlyValue) {
461467
field_values->emplace_back("", field_value.value.ToString());
462468
} else {
463-
InternalKey ikey(iter->key(), storage_->IsSlotIdEncoded());
464469
field_values->emplace_back(ikey.GetSubKey().ToString(), field_value.value.ToString());
465470
}
466471
}
@@ -674,4 +679,33 @@ rocksdb::Status Hash::PersistFields(engine::Context &ctx, const Slice &user_key,
674679
return rocksdb::Status::OK();
675680
}
676681

682+
void Hash::AsyncRepairHash(const std::string &ns_key, const Slice &field, const HashMetadata &metadata) const {
683+
auto repair_task = [storage = storage_, ns_key, field_str = field.ToString(), version = metadata.version,
684+
size = metadata.size]() {
685+
engine::Context ctx(storage);
686+
auto batch = storage->GetWriteBatchBase();
687+
std::string sub_key = InternalKey(ns_key, field_str, version, storage->IsSlotIdEncoded()).Encode();
688+
batch->Delete(sub_key);
689+
690+
// Use Merge to decrement the size atomically. This may temporarily result in inconsistent
691+
// metadata if multiple repairs run concurrently or if the size reaches 0, leaving an empty
692+
// hash. However, this is acceptable as:
693+
// 1. The inconsistency is temporary and will be corrected by subsequent operations
694+
// 2. Empty hashes with size=0 will be cleaned up during the next compaction
695+
// 3. Re-reading metadata would add overhead and complexity without eliminating all race conditions
696+
HashMetadata new_metadata(false);
697+
new_metadata.size = -1;
698+
std::string bytes;
699+
new_metadata.Encode(&bytes);
700+
batch->Merge(storage->GetCFHandle(ColumnFamilyID::Metadata), ns_key, bytes);
701+
702+
auto s = storage->Write(ctx, storage->DefaultWriteOptions(), batch->GetWriteBatch());
703+
if (!s.ok()) {
704+
error("Failed to async repair hash field: {}", s.ToString());
705+
}
706+
};
707+
708+
std::thread(repair_task).detach();
709+
}
710+
677711
} // namespace redis

src/types/redis_hash.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ constexpr uint8_t HASH_FIELD_FLAG_EXPIRE = 0x01;
6060
// Using two bytes makes it extremely unlikely to conflict with existing binary values
6161
constexpr uint8_t HASH_FIELD_ENCODING_MARKER_1 = 0xFF;
6262
constexpr uint8_t HASH_FIELD_ENCODING_MARKER_2 = 0xFE;
63+
const std::string HASH_FIELD_STUB_VALUE = "\xFE";
6364

6465
// HashFieldValue handles encoding/decoding of hash field values with optional expiration
6566
// Legacy format (backward compatible): [raw value]
@@ -197,6 +198,7 @@ class Hash : public SubKeyScanner {
197198

198199
private:
199200
rocksdb::Status GetMetadata(engine::Context &ctx, const Slice &ns_key, HashMetadata *metadata);
201+
void AsyncRepairHash(const std::string &ns_key, const Slice &field, const HashMetadata &metadata) const;
200202

201203
friend struct FieldValueRetriever;
202204
};

0 commit comments

Comments
 (0)