fix(crawler): reap dead jobs alongside done ones
The retention reaper deleted only `state = 'done'` rows, so terminal
`dead` jobs (retries exhausted) accumulated in crawler_jobs forever —
slow unbounded table growth. Both states are end-of-life and never
leased again.
Rename `reap_done` → `reap_terminal` and broaden the filter to
`state IN ('done','dead')`; `pending`/`running` stay untouched. Test
extended to assert old done + old dead both reap while fresh terminal and
old-but-active rows survive.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -301,9 +301,9 @@ impl CronContext {
|
||||
}
|
||||
Err(e) => tracing::error!(?e, "cron: enqueue_bookmarked_pending failed"),
|
||||
}
|
||||
match jobs::reap_done(pool, retention_days).await {
|
||||
Ok(n) => tracing::info!(reaped = n, "cron: done-job reaper finished"),
|
||||
Err(e) => tracing::error!(?e, "cron: done-job reaper failed"),
|
||||
match jobs::reap_terminal(pool, retention_days).await {
|
||||
Ok(n) => tracing::info!(reaped = n, "cron: terminal-job reaper finished"),
|
||||
Err(e) => tracing::error!(?e, "cron: terminal-job reaper failed"),
|
||||
}
|
||||
match crate::repo::crawl_metrics::reap(pool, metrics_retention_days).await {
|
||||
Ok(n) => tracing::info!(reaped = n, "cron: crawl-metrics reaper finished"),
|
||||
|
||||
@@ -485,16 +485,19 @@ pub async fn reclaim_orphaned(pool: &PgPool) -> sqlx::Result<u64> {
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
|
||||
/// Delete `done` jobs whose `updated_at` is older than `retention_days`
|
||||
/// days. `0` disables the reaper without touching the table. Returns the
|
||||
/// number of rows removed.
|
||||
pub async fn reap_done(pool: &PgPool, retention_days: u32) -> sqlx::Result<u64> {
|
||||
/// Delete **terminal** jobs (`done` or `dead`) whose `updated_at` is older
|
||||
/// than `retention_days` days. Both states are end-of-life — `done`
|
||||
/// succeeded, `dead` exhausted its retries — and neither is ever leased
|
||||
/// again, so reaping only `done` left `dead` rows to accumulate forever.
|
||||
/// `pending` / `running` are active and never touched. `0` disables the
|
||||
/// reaper without touching the table. Returns the number of rows removed.
|
||||
pub async fn reap_terminal(pool: &PgPool, retention_days: u32) -> sqlx::Result<u64> {
|
||||
if retention_days == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
let result = sqlx::query(
|
||||
"DELETE FROM crawler_jobs \
|
||||
WHERE state = 'done' \
|
||||
WHERE state IN ('done', 'dead') \
|
||||
AND updated_at < now() - ($1::bigint || ' days')::interval",
|
||||
)
|
||||
.bind(retention_days as i64)
|
||||
|
||||
@@ -187,7 +187,7 @@ pub async fn list_ops(
|
||||
}
|
||||
|
||||
/// Delete metric rows older than `retention_days`. `0` disables the reaper
|
||||
/// (returns 0 without touching the table). Mirrors `jobs::reap_done`.
|
||||
/// (returns 0 without touching the table). Mirrors `jobs::reap_terminal`.
|
||||
pub async fn reap(pool: &PgPool, retention_days: u32) -> sqlx::Result<u64> {
|
||||
if retention_days == 0 {
|
||||
return Ok(0);
|
||||
|
||||
@@ -903,9 +903,8 @@ pub struct JobHistoryFilter<'a> {
|
||||
/// manga/chapter/page context (best-effort) so the table can label rows.
|
||||
/// Returns the page slice plus the filtered total for pagination.
|
||||
///
|
||||
/// History depth is bounded by the done-job reaper (`reap_done`): completed
|
||||
/// jobs older than the retention window are gone. Terminal `dead` jobs
|
||||
/// persist until requeued.
|
||||
/// History depth is bounded by the terminal-job reaper (`reap_terminal`):
|
||||
/// `done` and `dead` jobs older than the retention window are gone.
|
||||
pub async fn list_job_history(
|
||||
pool: &PgPool,
|
||||
filter: JobHistoryFilter<'_>,
|
||||
|
||||
Reference in New Issue
Block a user