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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-02-26 00:39:15 +01:00
committed by GitHub
co-authored by Claude Opus 4.6
parent e825640f05
commit 5d314e6923
3 changed files with 18 additions and 2 deletions
@@ -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);
@@ -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;
+2 -2
View File
@@ -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)