perf: batch external dep checks by project (bd-687v)

Optimize CheckExternalDeps to group refs by project and open each
external DB only once, checking all capabilities in a single query.

Before: If 10 issues depend on external:gastown:cap1, we opened
gastown's DB 10 times.

After: We open each external project's DB once, query for all
capabilities needed from that project, then close.

Also added deduplication in filterByExternalDeps to collect all
unique refs before checking, avoiding redundant checks for the
same ref across multiple issues.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beads/crew/grip
2026-01-02 17:17:26 -08:00
committed by Steve Yegge
parent 939f222c35
commit 32a4a9e060
3 changed files with 313 additions and 5 deletions

View File

@@ -191,6 +191,10 @@ func (s *SQLiteStorage) GetReadyWork(ctx context.Context, filter types.WorkFilte
// filterByExternalDeps removes issues that have unsatisfied external dependencies.
// External deps have format: external:<project>:<capability>
// They are satisfied when the target project has a closed issue with provides:<capability> label.
//
// Optimization: Collects all unique external refs across all issues, then checks
// them in batch (one DB open per external project) rather than checking each
// ref individually. This avoids O(N) DB opens when issues share external deps.
func (s *SQLiteStorage) filterByExternalDeps(ctx context.Context, issues []*types.Issue) ([]*types.Issue, error) {
if len(issues) == 0 {
return issues, nil
@@ -213,12 +217,26 @@ func (s *SQLiteStorage) filterByExternalDeps(ctx context.Context, issues []*type
return issues, nil
}
// Check each external dep and build set of blocked issue IDs
// Collect all unique external refs across all issues
uniqueRefs := make(map[string]bool)
for _, deps := range externalDeps {
for _, dep := range deps {
uniqueRefs[dep] = true
}
}
// Check all refs in batch (grouped by project internally)
refList := make([]string, 0, len(uniqueRefs))
for ref := range uniqueRefs {
refList = append(refList, ref)
}
statuses := CheckExternalDeps(ctx, refList)
// Build set of blocked issue IDs using batch results
blockedIssues := make(map[string]bool)
for issueID, deps := range externalDeps {
for _, dep := range deps {
status := CheckExternalDep(ctx, dep)
if !status.Satisfied {
if status, ok := statuses[dep]; ok && !status.Satisfied {
blockedIssues[issueID] = true
break // One unsatisfied dep is enough to block
}