From 5d314e6923aa9048a5d5a88f60a000dc5c2d66cd Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 26 Feb 2026 00:39:15 +0100 Subject: [PATCH] fix: optimize slow list_assets query for recents loading (#8103) * fix: optimize slow list_assets query with covering index and v2_job join fix Add a covering index on asset(workspace_id, path, kind, created_at DESC, id DESC) with INCLUDE(usage_kind, usage_path) to enable index-only scans for the CTE aggregation. Fix v2_job join to cast asset.usage_path::uuid instead of job.id::text, allowing PostgreSQL to use the job_pkey primary key index instead of seq scanning the entire table. Co-Authored-By: Claude Opus 4.6 * fix: drop redundant asset indexes subsumed by new covering index idx_asset_workspace_created_id and idx_asset_kind_path are fully covered by the new idx_asset_ws_path_kind_recent + the primary key. Verified all asset table queries still have optimal index coverage. Reduces write amplification on inserts. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .../20260225100000_asset_covering_index.down.sql | 5 +++++ .../20260225100000_asset_covering_index.up.sql | 11 +++++++++++ backend/windmill-api-assets/src/lib.rs | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 backend/migrations/20260225100000_asset_covering_index.down.sql create mode 100644 backend/migrations/20260225100000_asset_covering_index.up.sql diff --git a/backend/migrations/20260225100000_asset_covering_index.down.sql b/backend/migrations/20260225100000_asset_covering_index.down.sql new file mode 100644 index 0000000000..12f1b56303 --- /dev/null +++ b/backend/migrations/20260225100000_asset_covering_index.down.sql @@ -0,0 +1,5 @@ +DROP INDEX IF EXISTS idx_asset_ws_path_kind_recent; + +-- Restore the dropped indexes +CREATE INDEX IF NOT EXISTS idx_asset_workspace_created_id ON asset (workspace_id, created_at DESC, id DESC); +CREATE INDEX IF NOT EXISTS idx_asset_kind_path ON asset (workspace_id, kind, path); diff --git a/backend/migrations/20260225100000_asset_covering_index.up.sql b/backend/migrations/20260225100000_asset_covering_index.up.sql new file mode 100644 index 0000000000..cfa388de87 --- /dev/null +++ b/backend/migrations/20260225100000_asset_covering_index.up.sql @@ -0,0 +1,11 @@ +-- Covering index for the list_assets CTE: GROUP BY (path, kind) + MAX(created_at, id) + ORDER BY +-- Includes usage_kind and usage_path to allow full index-only scan (avoiding heap lookups for filter conditions) +CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_asset_ws_path_kind_recent + ON asset (workspace_id, path, kind, created_at DESC, id DESC) + INCLUDE (usage_kind, usage_path); + +-- Drop indexes now subsumed by idx_asset_ws_path_kind_recent: +-- idx_asset_workspace_created_id (workspace_id, created_at DESC, id DESC) - only used by list_assets CTE +-- idx_asset_kind_path (workspace_id, kind, path) - only used by list_assets CTE/outer join, covered by new index + PK +DROP INDEX CONCURRENTLY IF EXISTS idx_asset_workspace_created_id; +DROP INDEX CONCURRENTLY IF EXISTS idx_asset_kind_path; diff --git a/backend/windmill-api-assets/src/lib.rs b/backend/windmill-api-assets/src/lib.rs index 48817cfdd1..d1dfde41e0 100644 --- a/backend/windmill-api-assets/src/lib.rs +++ b/backend/windmill-api-assets/src/lib.rs @@ -144,7 +144,7 @@ async fn list_assets( format!( r#"FROM asset LEFT JOIN v2_job job_cte ON asset.usage_kind = 'job' - AND asset.usage_path = job_cte.id::text + AND job_cte.id = CASE WHEN asset.usage_kind = 'job' THEN asset.usage_path::uuid END AND job_cte.workspace_id = $1"# ) } else { @@ -209,7 +209,7 @@ async fn list_assets( ) = resource.path AND resource.workspace_id = $1 LEFT JOIN v2_job job ON asset.usage_kind = 'job' - AND asset.usage_path = job.id::text + AND job.id = CASE WHEN asset.usage_kind = 'job' THEN asset.usage_path::uuid END AND job.workspace_id = $1 WHERE asset.workspace_id = $1 AND (asset.kind <> 'resource' OR resource.path IS NOT NULL)