diff --git a/backend/migrations/20260228000000_v2_job_completed_failure_index.down.sql b/backend/migrations/20260228000000_v2_job_completed_failure_index.down.sql new file mode 100644 index 0000000000..ad1c0922f9 --- /dev/null +++ b/backend/migrations/20260228000000_v2_job_completed_failure_index.down.sql @@ -0,0 +1 @@ +DROP INDEX IF EXISTS ix_v2_job_completed_failure_workspace; diff --git a/backend/migrations/20260228000000_v2_job_completed_failure_index.up.sql b/backend/migrations/20260228000000_v2_job_completed_failure_index.up.sql new file mode 100644 index 0000000000..1e669f0a33 --- /dev/null +++ b/backend/migrations/20260228000000_v2_job_completed_failure_index.up.sql @@ -0,0 +1,7 @@ +-- Partial index for fast failure/canceled filtering on the runs page. +-- When failures are sparse (<1%) this avoids scanning millions of successful jobs. +-- The query orders by completed_at DESC (switched from created_at when success=false), +-- so this index provides both filtering and ordering in a single scan. +CREATE INDEX IF NOT EXISTS ix_v2_job_completed_failure_workspace + ON v2_job_completed (workspace_id, completed_at DESC) + WHERE status IN ('failure', 'canceled'); diff --git a/backend/windmill-api-jobs/src/query.rs b/backend/windmill-api-jobs/src/query.rs index 6be9487191..d8128d90e7 100644 --- a/backend/windmill-api-jobs/src/query.rs +++ b/backend/windmill-api-jobs/src/query.rs @@ -556,7 +556,10 @@ pub fn list_completed_jobs_query( let mut sqlb = SqlBuilder::select_from("v2_job_completed") .fields(fields) .order_by( - if lq.completed_before.is_some() || lq.completed_after.is_some() { + if lq.completed_before.is_some() + || lq.completed_after.is_some() + || lq.success == Some(false) + { "v2_job_completed.completed_at" } else { "v2_job.created_at" diff --git a/backend/windmill-api/src/db.rs b/backend/windmill-api/src/db.rs index 1c4b15fea8..8b0fb44dfe 100644 --- a/backend/windmill-api/src/db.rs +++ b/backend/windmill-api/src/db.rs @@ -81,6 +81,9 @@ lazy_static::lazy_static! { (20260225100000, include_str!( "../../migrations/20260225100000_asset_covering_index.up.sql" ).replace("CREATE INDEX", "CREATE INDEX CONCURRENTLY").replace("DROP INDEX", "DROP INDEX CONCURRENTLY")), + (20260228000000, include_str!( + "../../migrations/20260228000000_v2_job_completed_failure_index.up.sql" + ).replace("CREATE INDEX", "CREATE INDEX CONCURRENTLY")), ].into_iter().collect(); }