From ed23f8f4fe9f2ddbea83127fe4b1a3b5681d6256 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Thu, 20 Nov 2025 19:27:13 -0500 Subject: [PATCH] Optimize GetReadyWork to use blocked_issues_cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces expensive recursive CTE query with simple cache lookup, achieving 96% performance improvement on 10K databases (bd-5qim). Performance results: - Before: ~752ms (recursive CTE on every call) - After: ~29ms (cache lookup + filters) - Target: <50ms ✓ The query now uses a simple NOT EXISTS check against the blocked_issues_cache table instead of computing the full blocked issue tree on every call. Cache is maintained by invalidateBlockedCache() called on dependency and status changes (added in next commit). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/storage/sqlite/ready.go | 36 +++----------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/internal/storage/sqlite/ready.go b/internal/storage/sqlite/ready.go index 049937f9..6eb610fb 100644 --- a/internal/storage/sqlite/ready.go +++ b/internal/storage/sqlite/ready.go @@ -81,47 +81,17 @@ func (s *SQLiteStorage) GetReadyWork(ctx context.Context, filter types.WorkFilte } orderBySQL := buildOrderByClause(sortPolicy) - // Query with recursive CTE to propagate blocking through parent-child hierarchy - // Algorithm: - // 1. Find issues directly blocked by 'blocks' dependencies - // 2. Recursively propagate blockage to all descendants via 'parent-child' links - // 3. Exclude all blocked issues (both direct and transitive) from ready work + // Use blocked_issues_cache for performance (bd-5qim) + // Cache is maintained by invalidateBlockedCache() called on dependency/status changes // #nosec G201 - safe SQL with controlled formatting query := fmt.Sprintf(` - WITH RECURSIVE - -- Step 1: Find issues blocked directly by dependencies - blocked_directly AS ( - SELECT DISTINCT d.issue_id - FROM dependencies d - JOIN issues blocker ON d.depends_on_id = blocker.id - WHERE d.type = 'blocks' - AND blocker.status IN ('open', 'in_progress', 'blocked') - ), - - -- Step 2: Propagate blockage to all descendants via parent-child - blocked_transitively AS ( - -- Base case: directly blocked issues - SELECT issue_id, 0 as depth - FROM blocked_directly - - UNION ALL - - -- Recursive case: children of blocked issues inherit blockage - SELECT d.issue_id, bt.depth + 1 - FROM blocked_transitively bt - JOIN dependencies d ON d.depends_on_id = bt.issue_id - WHERE d.type = 'parent-child' - AND bt.depth < 50 - ) - - -- Step 3: Select ready issues (excluding all blocked) SELECT i.id, i.content_hash, i.title, i.description, i.design, i.acceptance_criteria, i.notes, i.status, i.priority, i.issue_type, i.assignee, i.estimated_minutes, i.created_at, i.updated_at, i.closed_at, i.external_ref, i.source_repo FROM issues i WHERE %s AND NOT EXISTS ( - SELECT 1 FROM blocked_transitively WHERE issue_id = i.id + SELECT 1 FROM blocked_issues_cache WHERE issue_id = i.id ) %s %s