feat(recoverable-job): add progress view

This commit is contained in:
Edouard Vanbelle
2026-07-29 08:41:19 +02:00
parent 4336eca4d1
commit 5881968f50
10 changed files with 432 additions and 11 deletions
@@ -69,6 +69,28 @@ impl RecoverableJobHandler for DrivesConsistencyCheck {
DRIVES_CONSISTENCY_JOB_NAME
}
/// Definitive count — one row per drive, table is tiny (dozens per
/// install), COUNT(*) is trivially fast. Enables progress bar on
/// the admin UI.
async fn count_total(&self) -> Option<u64> {
let row: Result<(i64,), sqlx::Error> =
sqlx::query_as("SELECT COUNT(*) FROM storage.drives")
.fetch_one(self.pool.as_ref())
.await;
match row {
Ok((n,)) => Some(n.max(0) as u64),
Err(e) => {
tracing::debug!(
target: "oxicloud::consistency",
event = "drives_consistency.count_total_failed",
error = %e,
"count_total failed — run will not surface a progress bar"
);
None
}
}
}
async fn run_resumable(
&self,
store: &dyn JobStore,
@@ -113,6 +113,30 @@ impl RecoverableJobHandler for FilesConsistencyCheck {
FILES_CONSISTENCY_JOB_NAME
}
/// Definitive count — one row per file. This is the largest table
/// of the trio (millions on big installs); COUNT(*) is still an
/// index-only scan but can take ~seconds. The tradeoff is worth
/// it — an operator staring at a running files_consistency scan
/// wants a bar, and a seconds-scale one-off at run start is
/// invisible compared to the multi-minute scan that follows.
async fn count_total(&self) -> Option<u64> {
let row: Result<(i64,), sqlx::Error> = sqlx::query_as("SELECT COUNT(*) FROM storage.files")
.fetch_one(self.pool.as_ref())
.await;
match row {
Ok((n,)) => Some(n.max(0) as u64),
Err(e) => {
tracing::debug!(
target: "oxicloud::consistency",
event = "files_consistency.count_total_failed",
error = %e,
"count_total failed — run will not surface a progress bar"
);
None
}
}
}
async fn run_resumable(
&self,
store: &dyn JobStore,
@@ -117,6 +117,29 @@ impl RecoverableJobHandler for FoldersConsistencyCheck {
FOLDERS_CONSISTENCY_JOB_NAME
}
/// Definitive count — one row per folder. Larger table than drives
/// but the COUNT(*) is still index-only on PG. On multi-million-row
/// deployments this is ~100ms at run start; acceptable given the
/// progress bar is only rendered when the operator is watching.
async fn count_total(&self) -> Option<u64> {
let row: Result<(i64,), sqlx::Error> =
sqlx::query_as("SELECT COUNT(*) FROM storage.folders")
.fetch_one(self.pool.as_ref())
.await;
match row {
Ok((n,)) => Some(n.max(0) as u64),
Err(e) => {
tracing::debug!(
target: "oxicloud::consistency",
event = "folders_consistency.count_total_failed",
error = %e,
"count_total failed — run will not surface a progress bar"
);
None
}
}
}
async fn run_resumable(
&self,
store: &dyn JobStore,