pw_kvs: Count erases + HeavyMaintenance
- Add counting of sectors erased. This count is only since instance
construction.
- Add HeavyMaintenance to be heavy and aggressive about garbage
collecting sectors with valid data.
- Don't erase sectors when doing garbage collection of sectors that are
already erased.
Change-Id: I4fb1647c1920cdc64a7a1029078575068a9156ce
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/20960
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: David Rogers <davidrogers@google.com>
diff --git a/pw_kvs/key_value_store.cc b/pw_kvs/key_value_store.cc
index 71da378..752590d 100644
--- a/pw_kvs/key_value_store.cc
+++ b/pw_kvs/key_value_store.cc
@@ -53,7 +53,7 @@
options_(options),
initialized_(InitializationState::kNotInitialized),
error_detected_(false),
- error_stats_({}),
+ internal_stats_({}),
last_transaction_id_(0) {}
Status KeyValueStore::Init() {
@@ -97,12 +97,12 @@
if (options_.recovery != ErrorRecovery::kManual) {
size_t pre_fix_redundancy_errors =
- error_stats_.missing_redundant_entries_recovered;
+ internal_stats_.missing_redundant_entries_recovered;
Status recovery_status = FixErrors();
if (recovery_status.ok()) {
if (metadata_result == Status::OutOfRange()) {
- error_stats_.missing_redundant_entries_recovered =
+ internal_stats_.missing_redundant_entries_recovered =
pre_fix_redundancy_errors;
INF("KVS init: Redundancy level successfully updated");
} else {
@@ -301,9 +301,10 @@
StorageStats stats{};
const size_t sector_size = partition_.sector_size_bytes();
bool found_empty_sector = false;
- stats.corrupt_sectors_recovered = error_stats_.corrupt_sectors_recovered;
+ stats.sector_erase_count = internal_stats_.sector_erase_count;
+ stats.corrupt_sectors_recovered = internal_stats_.corrupt_sectors_recovered;
stats.missing_redundant_entries_recovered =
- error_stats_.missing_redundant_entries_recovered;
+ internal_stats_.missing_redundant_entries_recovered;
for (const SectorDescriptor& sector : sectors_) {
stats.in_use_bytes += sector.valid_bytes();
@@ -867,7 +868,7 @@
return Status::Ok();
}
-Status KeyValueStore::FullMaintenance() {
+Status KeyValueStore::FullMaintenanceHelper(MaintenanceType maintenance_type) {
if (initialized_ == InitializationState::kNotInitialized) {
return Status::FailedPrecondition();
}
@@ -897,7 +898,8 @@
// Is bytes in use over the threshold.
StorageStats stats = GetStorageStats();
bool over_usage_threshold = stats.in_use_bytes > threshold_bytes;
- bool force_gc = over_usage_threshold || (update_status.size() > 0);
+ bool heavy = (maintenance_type == MaintenanceType::kHeavy);
+ bool force_gc = heavy || over_usage_threshold || (update_status.size() > 0);
// TODO: look in to making an iterator method for cycling through sectors
// starting from last_new_sector_.
@@ -982,6 +984,7 @@
SectorDescriptor& sector_to_gc,
std::span<const Address> reserved_addresses) {
DBG(" Garbage Collect sector %u", sectors_.Index(sector_to_gc));
+
// Step 1: Move any valid entries in the GC sector to other sectors
if (sector_to_gc.valid_bytes() != 0) {
for (EntryMetadata& metadata : entry_cache_) {
@@ -998,9 +1001,12 @@
}
// Step 2: Reinitialize the sector
- sector_to_gc.mark_corrupt();
- PW_TRY(partition_.Erase(sectors_.BaseAddress(sector_to_gc), 1));
- sector_to_gc.set_writable_bytes(partition_.sector_size_bytes());
+ if (!sector_to_gc.Empty(partition_.sector_size_bytes())) {
+ sector_to_gc.mark_corrupt();
+ internal_stats_.sector_erase_count++;
+ PW_TRY(partition_.Erase(sectors_.BaseAddress(sector_to_gc), 1));
+ sector_to_gc.set_writable_bytes(partition_.sector_size_bytes());
+ }
DBG(" Garbage Collect sector %u complete", sectors_.Index(sector_to_gc));
return Status::Ok();
@@ -1098,7 +1104,7 @@
DBG(" Found sector %u with corruption", sectors_.Index(sector));
Status sector_status = GarbageCollectSector(sector, {});
if (sector_status.ok()) {
- error_stats_.corrupt_sectors_recovered += 1;
+ internal_stats_.corrupt_sectors_recovered += 1;
} else if (repair_status.ok() ||
repair_status == Status::ResourceExhausted()) {
repair_status = sector_status;
@@ -1156,7 +1162,7 @@
unsigned(redundancy()));
Status fill_status = AddRedundantEntries(metadata);
if (fill_status.ok()) {
- error_stats_.missing_redundant_entries_recovered += 1;
+ internal_stats_.missing_redundant_entries_recovered += 1;
DBG(" Key missing copies added");
} else {
DBG(" Failed to add key missing copies");
diff --git a/pw_kvs/key_value_store_test.cc b/pw_kvs/key_value_store_test.cc
index 2820793..140d174 100644
--- a/pw_kvs/key_value_store_test.cc
+++ b/pw_kvs/key_value_store_test.cc
@@ -439,6 +439,35 @@
EXPECT_EQ(kvs_.size(), 1u);
}
+TEST_F(LargeEmptyInitializedKvs, FullMaintenance) {
+ const uint8_t kValue1 = 0xDA;
+ const uint8_t kValue2 = 0x12;
+
+ // Write a key and write again with a different value, resulting in a stale
+ // entry from the first write.
+ ASSERT_EQ(Status::Ok(), kvs_.Put(keys[0], kValue1));
+ ASSERT_EQ(Status::Ok(), kvs_.Put(keys[0], kValue2));
+ EXPECT_EQ(kvs_.size(), 1u);
+
+ KeyValueStore::StorageStats stats = kvs_.GetStorageStats();
+ EXPECT_EQ(stats.sector_erase_count, 0u);
+ EXPECT_GT(stats.reclaimable_bytes, 0u);
+
+ // Do regular FullMaintenance, which should not touch the sector with valid
+ // data.
+ EXPECT_EQ(Status::Ok(), kvs_.FullMaintenance());
+ stats = kvs_.GetStorageStats();
+ EXPECT_EQ(stats.sector_erase_count, 0u);
+ EXPECT_GT(stats.reclaimable_bytes, 0u);
+
+ // Do aggressive FullMaintenance, which should GC the sector with valid data,
+ // resulting in no reclaimable bytes and an erased sector.
+ EXPECT_EQ(Status::Ok(), kvs_.HeavyMaintenance());
+ stats = kvs_.GetStorageStats();
+ EXPECT_EQ(stats.sector_erase_count, 1u);
+ EXPECT_EQ(stats.reclaimable_bytes, 0u);
+}
+
TEST(InMemoryKvs, Put_MaxValueSize) {
// Create and erase the fake flash.
Flash flash;
diff --git a/pw_kvs/public/pw_kvs/key_value_store.h b/pw_kvs/public/pw_kvs/key_value_store.h
index 6988d6f..de4249e 100644
--- a/pw_kvs/public/pw_kvs/key_value_store.h
+++ b/pw_kvs/public/pw_kvs/key_value_store.h
@@ -180,10 +180,26 @@
StatusWithSize ValueSize(std::string_view key) const;
// Perform all maintenance possible, including all neeeded repairing of
- // corruption and garbage collection of all reclaimable space in the KVS. When
- // configured for manual recovery, this is the only way KVS repair is
- // triggered.
- Status FullMaintenance();
+ // corruption and garbage collection of reclaimable space in the KVS. When
+ // configured for manual recovery, this (along with FullMaintenance) is the
+ // only way KVS repair is triggered.
+ //
+ // - Heavy garbage collection of all reclaimable space, regardless of valid
+ // data in the sector.
+ Status HeavyMaintenance() {
+ return FullMaintenanceHelper(MaintenanceType::kHeavy);
+ }
+
+ // Perform all maintenance possible, including all neeeded repairing of
+ // corruption and garbage collection of reclaimable space in the KVS. When
+ // configured for manual recovery, this (along with HeavyMaintenance) is the
+ // only way KVS repair is triggered.
+ //
+ // - Regular will not garbage collect sectors with valid data unless the KVS
+ // is mostly full.
+ Status FullMaintenance() {
+ return FullMaintenanceHelper(MaintenanceType::kRegular);
+ }
// Perform a portion of KVS maintenance. If configured for at least lazy
// recovery, will do any needed repairing of corruption. Does garbage
@@ -292,6 +308,7 @@
size_t writable_bytes;
size_t in_use_bytes;
size_t reclaimable_bytes;
+ size_t sector_erase_count;
size_t corrupt_sectors_recovered;
size_t missing_redundant_entries_recovered;
};
@@ -445,6 +462,21 @@
KeyValueStore::Address& address,
std::span<const Address> addresses_to_skip);
+ // Perform all maintenance possible, including all neeeded repairing of
+ // corruption and garbage collection of reclaimable space in the KVS. When
+ // configured for manual recovery, this is the only way KVS repair is
+ // triggered.
+ //
+ // - Regular will not garbage collect sectors with valid data unless the KVS
+ // is mostly full.
+ // - Heavy will garbage collect all reclaimable space regardless of valid data
+ // in the sector.
+ enum class MaintenanceType {
+ kRegular,
+ kHeavy,
+ };
+ Status FullMaintenanceHelper(MaintenanceType maintenance_type);
+
// Find and garbage collect a singe sector that does not include an address to
// skip.
Status GarbageCollect(std::span<const Address> addresses_to_skip);
@@ -519,11 +551,12 @@
// make it mutable.
mutable bool error_detected_;
- struct ErrorStats {
+ struct InternalStats {
+ size_t sector_erase_count;
size_t corrupt_sectors_recovered;
size_t missing_redundant_entries_recovered;
};
- ErrorStats error_stats_;
+ InternalStats internal_stats_;
uint32_t last_transaction_id_;
};