mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
Skip the blob activation disk preflight when no mount matches the database
available_space_for_path returning None aborted startup with a fatal UnknownFilesystem error, even on a fresh install with zero legacy rows. Hosts with tmpfs or squashfs roots, network-filesystem data paths, or an unreadable mount table would fail every boot with no operator override, while the resource sampler already treats the identical condition as benign (supported: false) and keeps running. The preflight now logs a warning and is skipped when free space cannot be determined; the import, verification, and integrity checks still run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
bccc5750a4
commit
e1abecc9f4
2 changed files with 40 additions and 31 deletions
|
|
@ -59,8 +59,10 @@ On startup, Fabro activates SQLite as the only live content-addressed blob
|
|||
store before it opens routes, schedulers, workers, webhooks, reapers, or the
|
||||
ready callback. The activation inventories the exact legacy SlateDB blob
|
||||
prefix, checks disk headroom sized to the rows not yet imported (a warm
|
||||
restart with nothing left to import only needs a small fixed headroom),
|
||||
imports in bounded transactions, compares every legacy blob byte-for-byte
|
||||
restart with nothing left to import only needs a small fixed headroom; on
|
||||
filesystems whose free space cannot be determined the check is skipped with
|
||||
a warning), imports in bounded transactions, compares every legacy blob
|
||||
byte-for-byte
|
||||
with SQLite, runs a live SQLite integrity check, and completes a final WAL
|
||||
checkpoint. Boots that import new rows additionally re-verify every legacy
|
||||
blob against SQLite and validate every SQLite blob row independently. Any
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use sqlx::Connection as _;
|
|||
use sqlx::sqlite::{SqliteConnectOptions, SqliteConnection};
|
||||
use tokio::fs;
|
||||
use tokio::task::{JoinError, spawn_blocking};
|
||||
use tracing::{debug, info};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use crate::server::resource_sampler;
|
||||
|
||||
|
|
@ -52,8 +52,6 @@ pub(crate) enum BlobActivationError {
|
|||
},
|
||||
#[error("activation backup integrity check did not return exactly one ok result at {path}")]
|
||||
BackupIntegrityFailed { path: PathBuf },
|
||||
#[error("no filesystem mount matched the SQLite database path {path}")]
|
||||
UnknownFilesystem { path: PathBuf },
|
||||
#[error("reading SQLite file metadata at {path}")]
|
||||
SqliteMetadata {
|
||||
path: PathBuf,
|
||||
|
|
@ -150,33 +148,42 @@ pub(crate) async fn activate_blob_storage(
|
|||
validate_backup(&backup_path).await?;
|
||||
}
|
||||
let backup_required = inventory.rows > 0 && !backup_exists;
|
||||
let available_free_bytes = resource_sampler::available_space_for_path(&canonical_path)
|
||||
.ok_or_else(|| BlobActivationError::UnknownFilesystem {
|
||||
path: canonical_path.clone(),
|
||||
})?;
|
||||
let backup_reserve = if backup_required {
|
||||
sqlite_file_set_bytes(&canonical_path).await?
|
||||
// The resource sampler treats a path with no matching mount as an
|
||||
// unsupported-but-benign condition (tmpfs or squashfs roots, network
|
||||
// filesystems, an unreadable mount table), so the preflight does too:
|
||||
// skipping the capacity check must not block a boot the import itself
|
||||
// could complete.
|
||||
if let Some(available_free_bytes) = resource_sampler::available_space_for_path(&canonical_path)
|
||||
{
|
||||
let backup_reserve = if backup_required {
|
||||
sqlite_file_set_bytes(&canonical_path).await?
|
||||
} else {
|
||||
0
|
||||
};
|
||||
// Only the rows the import still has to copy need new space; rows
|
||||
// already present in SQLite cost nothing on a warm restart.
|
||||
let required_free_bytes = compute_disk_preflight(
|
||||
inventory.pending_bytes,
|
||||
backup_reserve,
|
||||
available_free_bytes,
|
||||
)?;
|
||||
debug!(
|
||||
legacy_rows = inventory.rows,
|
||||
legacy_bytes = inventory.bytes,
|
||||
pending_rows = inventory.pending_rows,
|
||||
pending_bytes = inventory.pending_bytes,
|
||||
backup_required,
|
||||
backup_reserve,
|
||||
required_free_bytes,
|
||||
available_free_bytes,
|
||||
"Checked SQLite blob activation disk capacity"
|
||||
);
|
||||
} else {
|
||||
0
|
||||
};
|
||||
// Only the rows the import still has to copy need new space; rows already
|
||||
// present in SQLite cost nothing on a warm restart.
|
||||
let required_free_bytes = compute_disk_preflight(
|
||||
inventory.pending_bytes,
|
||||
backup_reserve,
|
||||
available_free_bytes,
|
||||
)?;
|
||||
debug!(
|
||||
legacy_rows = inventory.rows,
|
||||
legacy_bytes = inventory.bytes,
|
||||
pending_rows = inventory.pending_rows,
|
||||
pending_bytes = inventory.pending_bytes,
|
||||
backup_required,
|
||||
backup_reserve,
|
||||
required_free_bytes,
|
||||
available_free_bytes,
|
||||
"Checked SQLite blob activation disk capacity"
|
||||
);
|
||||
warn!(
|
||||
database_path = %canonical_path.display(),
|
||||
"No filesystem mount matched the SQLite database path; skipping the blob activation disk preflight"
|
||||
);
|
||||
}
|
||||
|
||||
let retained_backup = if backup_exists {
|
||||
Some(backup_path)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue