Persist backend per database id in admin metadata so restarts and lazy opens always use the correct engine (Sled/Redb)

This commit is contained in:
Maxime Van Hees
2025-09-22 15:29:58 +02:00
parent 8e044a64b7
commit bd34fd092a
3 changed files with 140 additions and 13 deletions

View File

@@ -69,9 +69,38 @@ pub fn open_admin_storage(
if let Some(st) = w.get(base_dir) { if let Some(st) = w.get(base_dir) {
return Ok(st.clone()); return Ok(st.clone());
} }
let st = init_admin_storage(base_dir, backend, admin_secret)?;
// Detect existing 0.db backend by filesystem, if present.
let admin_path = PathBuf::from(base_dir).join("0.db");
let detected = if admin_path.exists() {
if admin_path.is_file() {
Some(options::BackendType::Redb)
} else if admin_path.is_dir() {
Some(options::BackendType::Sled)
} else {
None
}
} else {
None
};
let effective_backend = match detected {
Some(d) if d != backend => {
eprintln!(
"warning: Admin DB 0 at {} appears to be {:?}, but process default is {:?}. Using detected backend.",
admin_path.display(),
d,
backend
);
d
}
Some(d) => d,
None => backend, // First boot: use requested backend to initialize 0.db
};
let st = init_admin_storage(base_dir, effective_backend, admin_secret)?;
w.insert(base_dir.to_string(), st.clone()); w.insert(base_dir.to_string(), st.clone());
return Ok(st); Ok(st)
} }
} }
@@ -122,7 +151,39 @@ pub fn open_data_storage(
return Ok(st.clone()); return Ok(st.clone());
} }
// Determine per-db encryption // Resolve effective backend for this db id:
// 1) Try admin meta "backend" field
// 2) If missing, sniff filesystem (file => Redb, dir => Sled), then persist into admin meta
// 3) Fallback to requested 'backend' (startup default) if nothing else is known
let meta_backend = get_database_backend(base_dir, backend.clone(), admin_secret, id).ok().flatten();
let db_path = PathBuf::from(base_dir).join(format!("{}.db", id));
let sniffed_backend = if db_path.exists() {
if db_path.is_file() {
Some(options::BackendType::Redb)
} else if db_path.is_dir() {
Some(options::BackendType::Sled)
} else {
None
}
} else {
None
};
let effective_backend = meta_backend.clone().or(sniffed_backend).unwrap_or(backend.clone());
// If we had to sniff (i.e., meta missing), persist it for future robustness
if meta_backend.is_none() {
let _ = set_database_backend(base_dir, backend.clone(), admin_secret, id, effective_backend.clone());
}
// Warn if caller-provided backend differs from effective
if effective_backend != backend {
eprintln!(
"notice: Database {} backend resolved to {:?} (caller requested {:?}). Using resolved backend.",
id, effective_backend, backend
);
}
// Determine per-db encryption (from admin meta)
let enc = get_enc_key(base_dir, backend.clone(), admin_secret, id)?; let enc = get_enc_key(base_dir, backend.clone(), admin_secret, id)?;
let should_encrypt = enc.is_some(); let should_encrypt = enc.is_some();
@@ -134,8 +195,8 @@ pub fn open_data_storage(
})?; })?;
} }
// Open storage // Open storage using the effective backend
let storage: Arc<dyn StorageBackend> = match backend { let storage: Arc<dyn StorageBackend> = match effective_backend {
options::BackendType::Redb => Arc::new(Storage::new(&db_file, should_encrypt, enc.as_deref())?), options::BackendType::Redb => Arc::new(Storage::new(&db_file, should_encrypt, enc.as_deref())?),
options::BackendType::Sled => Arc::new(SledStorage::new(&db_file, should_encrypt, enc.as_deref())?), options::BackendType::Sled => Arc::new(SledStorage::new(&db_file, should_encrypt, enc.as_deref())?),
}; };
@@ -217,6 +278,39 @@ pub fn set_database_public(
Ok(()) Ok(())
} }
// Persist per-db backend type in admin metadata (module-scope)
pub fn set_database_backend(
base_dir: &str,
backend: options::BackendType,
admin_secret: &str,
id: u64,
db_backend: options::BackendType,
) -> Result<(), DBError> {
let admin = open_admin_storage(base_dir, backend, admin_secret)?;
let mk = k_meta_db(id);
let val = match db_backend {
options::BackendType::Redb => "Redb",
options::BackendType::Sled => "Sled",
};
let _ = admin.hset(&mk, vec![("backend".to_string(), val.to_string())])?;
Ok(())
}
pub fn get_database_backend(
base_dir: &str,
backend: options::BackendType,
admin_secret: &str,
id: u64,
) -> Result<Option<options::BackendType>, DBError> {
let admin = open_admin_storage(base_dir, backend, admin_secret)?;
let mk = k_meta_db(id);
match admin.hget(&mk, "backend")? {
Some(s) if s == "Redb" => Ok(Some(options::BackendType::Redb)),
Some(s) if s == "Sled" => Ok(Some(options::BackendType::Sled)),
_ => Ok(None),
}
}
// Set database name // Set database name
pub fn set_database_name( pub fn set_database_name(
base_dir: &str, base_dir: &str,

View File

@@ -1,4 +1,4 @@
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum BackendType { pub enum BackendType {
Redb, Redb,
Sled, Sled,

View File

@@ -158,14 +158,42 @@ impl RpcServerImpl {
)); ));
} }
// Create server instance with default options // Resolve effective backend for this db from admin meta or filesystem; fallback to default
let meta_backend = admin_meta::get_database_backend(&self.base_dir, self.backend.clone(), &self.admin_secret, db_id)
.ok()
.flatten();
let db_path = std::path::PathBuf::from(&self.base_dir).join(format!("{}.db", db_id));
let sniffed_backend = if db_path.exists() {
if db_path.is_file() {
Some(crate::options::BackendType::Redb)
} else if db_path.is_dir() {
Some(crate::options::BackendType::Sled)
} else {
None
}
} else {
None
};
let effective_backend = meta_backend.clone().or(sniffed_backend).unwrap_or(self.backend.clone());
if effective_backend != self.backend {
eprintln!(
"notice: get_or_create_server: db {} backend resolved to {:?} (server default {:?})",
db_id, effective_backend, self.backend
);
}
// If we had to sniff (no meta), persist the resolved backend
if meta_backend.is_none() {
let _ = admin_meta::set_database_backend(&self.base_dir, self.backend.clone(), &self.admin_secret, db_id, effective_backend.clone());
}
// Create server instance with resolved backend
let db_option = DBOption { let db_option = DBOption {
dir: self.base_dir.clone(), dir: self.base_dir.clone(),
port: 0, // Not used for RPC-managed databases port: 0, // Not used for RPC-managed databases
debug: false, debug: false,
encryption_key: None, encryption_key: None,
encrypt: false, encrypt: false,
backend: self.backend.clone(), backend: effective_backend,
admin_secret: self.admin_secret.clone(), admin_secret: self.admin_secret.clone(),
}; };
@@ -308,17 +336,22 @@ impl RpcServer for RpcServerImpl {
return Err(jsonrpsee::types::ErrorObjectOwned::owned(-32000, format!("Failed to ensure base dir: {}", e), None::<()>)); return Err(jsonrpsee::types::ErrorObjectOwned::owned(-32000, format!("Failed to ensure base dir: {}", e), None::<()>));
} }
// Create server instance using base_dir and admin secret // Map RPC backend to options backend and persist it in admin meta for this db id
let opt_backend = match backend {
BackendType::Redb => crate::options::BackendType::Redb,
BackendType::Sled => crate::options::BackendType::Sled,
};
admin_meta::set_database_backend(&self.base_dir, self.backend.clone(), &self.admin_secret, db_id, opt_backend.clone())
.map_err(|e| jsonrpsee::types::ErrorObjectOwned::owned(-32000, e.0, None::<()>))?;
// Create server instance using base_dir, chosen backend and admin secret
let option = DBOption { let option = DBOption {
dir: self.base_dir.clone(), dir: self.base_dir.clone(),
port: 0, // Not used for RPC-managed databases port: 0, // Not used for RPC-managed databases
debug: false, debug: false,
encryption_key: None, // per-db key is stored in admin DB 0 encryption_key: None, // per-db key is stored in admin DB 0
encrypt: false, // encryption decided per-db at open time encrypt: false, // encryption decided per-db at open time
backend: match backend { backend: opt_backend,
BackendType::Redb => crate::options::BackendType::Redb,
BackendType::Sled => crate::options::BackendType::Sled,
},
admin_secret: self.admin_secret.clone(), admin_secret: self.admin_secret.clone(),
}; };