Integrate redb into Store::load() with health check

- Add db: Option<Database> field to Store
- Store::load() opens redb after replaying capnp logs
- Health check compares node count + spot checks keys
- Rebuilds automatically if db is missing, corrupt, or stale
- Make table definitions public for cross-module access

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-13 18:33:47 -04:00
parent 2caccf875d
commit 6104c63890
3 changed files with 78 additions and 7 deletions

View file

@ -2,7 +2,8 @@
//
// capnp logs are the source of truth; redb provides indexed access.
use super::types::*;
use super::{db, types::*};
use redb::ReadableTableMetadata;
use crate::memory_capnp;
@ -16,7 +17,7 @@ use std::io::{BufReader, Seek};
use std::path::Path;
impl Store {
/// Load store by replaying capnp logs.
/// Load store by replaying capnp logs, then open/verify redb indices.
pub fn load() -> Result<Store> {
let nodes_p = nodes_path();
let rels_p = relations_path();
@ -48,9 +49,60 @@ impl Store {
store.nodes.contains_key(&r.target_key)
);
// Open redb and verify/rebuild indices
let db_p = db_path();
store.db = Some(store.open_or_rebuild_db(&db_p)?);
Ok(store)
}
/// Open redb database, rebuilding if unhealthy.
fn open_or_rebuild_db(&self, path: &Path) -> Result<redb::Database> {
// Try opening existing database
if path.exists() {
match db::open_db(path) {
Ok(database) => {
if self.db_is_healthy(&database)? {
return Ok(database);
}
eprintln!("redb index stale, rebuilding...");
}
Err(e) => {
eprintln!("redb open failed ({}), rebuilding...", e);
}
}
}
// Rebuild from in-memory state
db::rebuild_from_store(path, self)
}
/// Check if redb indices match in-memory state.
fn db_is_healthy(&self, database: &redb::Database) -> Result<bool> {
use redb::ReadableDatabase;
let txn = database.begin_read()?;
// Quick check: node count should match
let nodes_table = txn.open_table(db::NODES)?;
let db_count = nodes_table.len()?;
if db_count != self.nodes.len() as u64 {
return Ok(false);
}
// Spot check: verify a few random nodes exist with matching keys
// (full verification would be too slow)
for (i, key) in self.nodes.keys().enumerate() {
if i >= 10 { break; } // check first 10
if nodes_table.get(key.as_str())?.is_none() {
return Ok(false);
}
}
Ok(true)
}
/// Replay node log, keeping latest version per UUID.
/// Tracks all UUIDs seen per key to detect duplicates.
fn replay_nodes(&mut self, path: &Path) -> Result<()> {