[block-ref-repair] Block refcount recalculation and repair

- We always recalculate the reference count of a block before deleting
  it locally, to make sure that it is indeed zero.

- If we had to fetch a remote block but we were not able to get it,
  check that refcount is indeed > 0.

- Repair procedure that checks everything
This commit is contained in:
Alex Auvolat
2024-03-19 11:04:20 +01:00
parent 0038ca8a78
commit dc0b78cdb8
10 changed files with 285 additions and 8 deletions
+8
View File
@@ -247,6 +247,14 @@ impl Garage {
#[cfg(feature = "k2v")]
let k2v = GarageK2V::new(system.clone(), &db, meta_rep_param);
// ---- setup block refcount recalculation ----
// this function can be used to fix inconsistencies in the RC table
block_manager.set_recalc_rc(vec![
block_ref_recount_fn(&block_ref_table),
// other functions could be added here if we had other tables
// that hold references to data blocks
]);
// -- done --
Ok(Arc::new(Self {
config,
+39
View File
@@ -3,8 +3,12 @@ use std::sync::Arc;
use garage_db as db;
use garage_util::data::*;
use garage_util::error::*;
use garage_util::migrate::Migrate;
use garage_block::CalculateRefcount;
use garage_table::crdt::Crdt;
use garage_table::replication::TableShardedReplication;
use garage_table::*;
use garage_block::manager::*;
@@ -84,3 +88,38 @@ impl TableSchema for BlockRefTable {
filter.apply(entry.deleted.get())
}
}
pub fn block_ref_recount_fn(
block_ref_table: &Arc<Table<BlockRefTable, TableShardedReplication>>,
) -> CalculateRefcount {
let table = Arc::downgrade(block_ref_table);
Box::new(move |tx: &db::Transaction, block: &Hash| {
let table = table
.upgrade()
.ok_or_message("cannot upgrade weak ptr to block_ref_table")
.map_err(db::TxError::Abort)?;
Ok(calculate_refcount(&table, tx, block)?)
})
}
fn calculate_refcount(
block_ref_table: &Table<BlockRefTable, TableShardedReplication>,
tx: &db::Transaction,
block: &Hash,
) -> db::TxResult<usize, Error> {
let mut result = 0;
for entry in tx.range(&block_ref_table.data.store, block.as_slice()..)? {
let (key, value) = entry?;
if &key[..32] != block.as_slice() {
break;
}
let value = BlockRef::decode(&value)
.ok_or_message("could not decode block_ref")
.map_err(db::TxError::Abort)?;
assert_eq!(value.block, *block);
if !value.deleted.get() {
result += 1;
}
}
Ok(result)
}