mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-05 16:03:47 +00:00
* perf: index the suspended-job resume test instead of filtering it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SEUq14Wz4cC2NzcRyo6CNj * fix: keep the legacy suspended index until the replacement is recorded Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SEUq14Wz4cC2NzcRyo6CNj * perf: drop the redundant suspend_until column from the suspended index Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SEUq14Wz4cC2NzcRyo6CNj --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
78 lines
3.3 KiB
Rust
78 lines
3.3 KiB
Rust
//! Pins the plan of the suspended-job pull. Its resume test degrades silently: once the
|
|
//! query expression and `queue_suspended_v2` stop matching, Postgres still returns the right
|
|
//! job, just by falling back to a heap filter and fetching one tuple per suspended row on
|
|
//! every worker poll. No functional test can see that, so assert on the plan instead.
|
|
|
|
use serde_json::Value;
|
|
use sqlx::{Pool, Postgres};
|
|
use windmill_common::worker::make_suspended_pull_query;
|
|
|
|
/// Depth-first walk of an `EXPLAIN (FORMAT JSON)` plan tree.
|
|
fn nodes(plan: &Value, out: &mut Vec<Value>) {
|
|
out.push(plan.clone());
|
|
for child in plan["Plans"].as_array().unwrap_or(&vec![]) {
|
|
nodes(child, out);
|
|
}
|
|
}
|
|
|
|
#[sqlx::test(fixtures("base"))]
|
|
async fn suspended_pull_tests_resume_time_inside_the_index(
|
|
db: Pool<Postgres>,
|
|
) -> anyhow::Result<()> {
|
|
sqlx::query(
|
|
"INSERT INTO v2_job_queue (id, workspace_id, created_at, scheduled_for, running, suspend, suspend_until, tag)
|
|
SELECT gen_random_uuid(), 'test-workspace', now() - make_interval(secs => i),
|
|
now(), true, 1 + (i % 3), now() + interval '7 day', 'flow'
|
|
FROM generate_series(1, 2000) i",
|
|
)
|
|
.execute(&db)
|
|
.await?;
|
|
sqlx::query("ANALYZE v2_job_queue").execute(&db).await?;
|
|
|
|
// Both plans are cheap on a 2000-row table, and which one wins there says nothing
|
|
// about a queue with a large suspended backlog. Force the index path, which is the
|
|
// one production takes, and assert on how it evaluates the resume test.
|
|
let mut conn = db.acquire().await?;
|
|
sqlx::query("SET enable_seqscan = off")
|
|
.execute(&mut *conn)
|
|
.await?;
|
|
let version: String = sqlx::query_scalar("SELECT version()")
|
|
.fetch_one(&mut *conn)
|
|
.await?;
|
|
// FORMAT JSON rather than the default: `Index Cond` and `Filter` are separate keys on the
|
|
// node, so this does not ride on EXPLAIN's line layout staying put across a major bump.
|
|
let explained: Value = sqlx::query_scalar(&format!(
|
|
"EXPLAIN (FORMAT JSON) {}",
|
|
make_suspended_pull_query(&["flow".to_string()])
|
|
))
|
|
.bind("test-worker")
|
|
.fetch_one(&mut *conn)
|
|
.await?;
|
|
|
|
let mut all = vec![];
|
|
nodes(&explained[0]["Plan"], &mut all);
|
|
let pretty = serde_json::to_string_pretty(&explained)?;
|
|
let scan = all
|
|
.iter()
|
|
.find(|n| n["Index Name"] == "queue_suspended_v2")
|
|
.unwrap_or_else(|| {
|
|
panic!("suspended pull did not scan queue_suspended_v2 on {version}:\n{pretty}")
|
|
});
|
|
// Only `Index Cond` is checked against the index tuple, so that is where the resume test
|
|
// has to land — as a `Filter` it would cost a heap fetch per suspended row. The residual
|
|
// `suspend_until IS NOT NULL` filter is not that: it is always true for rows the partial
|
|
// index holds, and only ever runs on the row LIMIT 1 already fetched.
|
|
let cond = scan["Index Cond"].as_str().unwrap_or_else(|| {
|
|
panic!("no Index Cond on the suspended pull scan on {version}:\n{pretty}")
|
|
});
|
|
assert!(
|
|
cond.contains("CASE WHEN"),
|
|
"resume test is not an index condition on {version}:\n{pretty}"
|
|
);
|
|
assert!(
|
|
!scan["Filter"].as_str().unwrap_or("").contains("CASE WHEN"),
|
|
"resume test fell back to a heap filter on {version}:\n{pretty}"
|
|
);
|
|
Ok(())
|
|
}
|