Fix N+1 query in convoy status fetching (gt-wah8i)
getTrackedIssues was spawning a separate bd show subprocess for each tracked issue. With 10 convoys x 5 issues = 50+ subprocesses per poll. Solution: Use bd show batch capability (bd show id1 id2 id3 --json) to fetch all issue details in a single call. Falls back to individual lookups if the batch fails (e.g., invalid IDs). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
f3f46de20d
commit
0dc2c4ab7b
@@ -551,6 +551,7 @@ type trackedIssueInfo struct {
|
|||||||
|
|
||||||
// getTrackedIssues queries SQLite directly to get issues tracked by a convoy.
|
// getTrackedIssues queries SQLite directly to get issues tracked by a convoy.
|
||||||
// This is needed because bd dep list doesn't properly show cross-rig external dependencies.
|
// This is needed because bd dep list doesn't properly show cross-rig external dependencies.
|
||||||
|
// Uses batched lookup to avoid N+1 subprocess calls.
|
||||||
func getTrackedIssues(townBeads, convoyID string) []trackedIssueInfo {
|
func getTrackedIssues(townBeads, convoyID string) []trackedIssueInfo {
|
||||||
dbPath := filepath.Join(townBeads, "beads.db")
|
dbPath := filepath.Join(townBeads, "beads.db")
|
||||||
|
|
||||||
@@ -572,7 +573,9 @@ func getTrackedIssues(townBeads, convoyID string) []trackedIssueInfo {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var tracked []trackedIssueInfo
|
// First pass: collect all issue IDs (normalized from external refs)
|
||||||
|
issueIDs := make([]string, 0, len(deps))
|
||||||
|
idToDepType := make(map[string]string)
|
||||||
for _, dep := range deps {
|
for _, dep := range deps {
|
||||||
issueID := dep.DependsOnID
|
issueID := dep.DependsOnID
|
||||||
|
|
||||||
@@ -584,14 +587,22 @@ func getTrackedIssues(townBeads, convoyID string) []trackedIssueInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to get issue details from the appropriate rig
|
issueIDs = append(issueIDs, issueID)
|
||||||
|
idToDepType[issueID] = dep.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single batch call to get all issue details
|
||||||
|
detailsMap := getIssueDetailsBatch(issueIDs)
|
||||||
|
|
||||||
|
// Second pass: build result using the batch lookup
|
||||||
|
var tracked []trackedIssueInfo
|
||||||
|
for _, issueID := range issueIDs {
|
||||||
info := trackedIssueInfo{
|
info := trackedIssueInfo{
|
||||||
ID: issueID,
|
ID: issueID,
|
||||||
Type: dep.Type,
|
Type: idToDepType[issueID],
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query issue status (try to find it in any known beads location)
|
if details, ok := detailsMap[issueID]; ok {
|
||||||
if details := getIssueDetails(issueID); details != nil {
|
|
||||||
info.Title = details.Title
|
info.Title = details.Title
|
||||||
info.Status = details.Status
|
info.Status = details.Status
|
||||||
info.IssueType = details.IssueType
|
info.IssueType = details.IssueType
|
||||||
@@ -614,7 +625,57 @@ type issueDetails struct {
|
|||||||
IssueType string
|
IssueType string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getIssueDetailsBatch fetches details for multiple issues in a single bd show call.
|
||||||
|
// Returns a map from issue ID to details. Missing/invalid issues are omitted from the map.
|
||||||
|
func getIssueDetailsBatch(issueIDs []string) map[string]*issueDetails {
|
||||||
|
result := make(map[string]*issueDetails)
|
||||||
|
if len(issueIDs) == 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build args: bd show id1 id2 id3 ... --json
|
||||||
|
args := append([]string{"show"}, issueIDs...)
|
||||||
|
args = append(args, "--json")
|
||||||
|
|
||||||
|
showCmd := exec.Command("bd", args...)
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
showCmd.Stdout = &stdout
|
||||||
|
|
||||||
|
if err := showCmd.Run(); err != nil {
|
||||||
|
// Batch failed - fall back to individual lookups for robustness
|
||||||
|
// This handles cases where some IDs are invalid/missing
|
||||||
|
for _, id := range issueIDs {
|
||||||
|
if details := getIssueDetails(id); details != nil {
|
||||||
|
result[id] = details
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
var issues []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
IssueType string `json:"issue_type"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(stdout.Bytes(), &issues); err != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, issue := range issues {
|
||||||
|
result[issue.ID] = &issueDetails{
|
||||||
|
ID: issue.ID,
|
||||||
|
Title: issue.Title,
|
||||||
|
Status: issue.Status,
|
||||||
|
IssueType: issue.IssueType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// getIssueDetails fetches issue details by trying to show it via bd.
|
// getIssueDetails fetches issue details by trying to show it via bd.
|
||||||
|
// Prefer getIssueDetailsBatch for multiple issues to avoid N+1 subprocess calls.
|
||||||
func getIssueDetails(issueID string) *issueDetails {
|
func getIssueDetails(issueID string) *issueDetails {
|
||||||
// Use bd show with routing - it should find the issue in the right rig
|
// Use bd show with routing - it should find the issue in the right rig
|
||||||
showCmd := exec.Command("bd", "show", issueID, "--json")
|
showCmd := exec.Command("bd", "show", issueID, "--json")
|
||||||
|
|||||||
Reference in New Issue
Block a user