Fix N+1 query pattern in export operations (bd-rcmg)
**Problem**: Export operations called GetLabels() and GetIssueComments() in a loop for each issue, creating N+1 query pattern. For 100 issues this created 201 queries instead of 3-5. **Solution**: - Added GetCommentsForIssues() batch method to storage interface - Implemented batch method in SQLite and memory storage backends - Updated handleExport() and triggerExport() to use batch queries - Added comprehensive tests for batch operations **Impact**: Query count reduced from ~201 to ~3-5 for 100 issues. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -59,28 +59,32 @@ func (s *Server) handleExport(req *Request) Response {
|
||||
issue.Dependencies = allDeps[issue.ID]
|
||||
}
|
||||
|
||||
// Populate labels for all issues
|
||||
for _, issue := range issues {
|
||||
labels, err := store.GetLabels(ctx, issue.ID)
|
||||
if err != nil {
|
||||
return Response{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("failed to get labels for %s: %v", issue.ID, err),
|
||||
}
|
||||
// Populate labels for all issues (avoid N+1)
|
||||
issueIDs := make([]string, len(issues))
|
||||
for i, issue := range issues {
|
||||
issueIDs[i] = issue.ID
|
||||
}
|
||||
allLabels, err := store.GetLabelsForIssues(ctx, issueIDs)
|
||||
if err != nil {
|
||||
return Response{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("failed to get labels: %v", err),
|
||||
}
|
||||
issue.Labels = labels
|
||||
}
|
||||
for _, issue := range issues {
|
||||
issue.Labels = allLabels[issue.ID]
|
||||
}
|
||||
|
||||
// Populate comments for all issues
|
||||
for _, issue := range issues {
|
||||
comments, err := store.GetIssueComments(ctx, issue.ID)
|
||||
if err != nil {
|
||||
return Response{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("failed to get comments for %s: %v", issue.ID, err),
|
||||
}
|
||||
// Populate comments for all issues (avoid N+1)
|
||||
allComments, err := store.GetCommentsForIssues(ctx, issueIDs)
|
||||
if err != nil {
|
||||
return Response{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("failed to get comments: %v", err),
|
||||
}
|
||||
issue.Comments = comments
|
||||
}
|
||||
for _, issue := range issues {
|
||||
issue.Comments = allComments[issue.ID]
|
||||
}
|
||||
|
||||
// Create temp file for atomic write
|
||||
@@ -387,7 +391,7 @@ func (s *Server) triggerExport(ctx context.Context, store storage.Storage, dbPat
|
||||
})
|
||||
|
||||
// CRITICAL: Populate all related data to prevent data loss
|
||||
// This mirrors the logic in handleExport (lines 50-83)
|
||||
// This mirrors the logic in handleExport
|
||||
|
||||
// Populate dependencies for all issues (avoid N+1 queries)
|
||||
allDeps, err := store.GetAllDependencyRecords(ctx)
|
||||
@@ -398,22 +402,26 @@ func (s *Server) triggerExport(ctx context.Context, store storage.Storage, dbPat
|
||||
issue.Dependencies = allDeps[issue.ID]
|
||||
}
|
||||
|
||||
// Populate labels for all issues
|
||||
// Populate labels for all issues (avoid N+1 queries)
|
||||
issueIDs := make([]string, len(allIssues))
|
||||
for i, issue := range allIssues {
|
||||
issueIDs[i] = issue.ID
|
||||
}
|
||||
allLabels, err := store.GetLabelsForIssues(ctx, issueIDs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get labels: %w", err)
|
||||
}
|
||||
for _, issue := range allIssues {
|
||||
labels, err := store.GetLabels(ctx, issue.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get labels for %s: %w", issue.ID, err)
|
||||
}
|
||||
issue.Labels = labels
|
||||
issue.Labels = allLabels[issue.ID]
|
||||
}
|
||||
|
||||
// Populate comments for all issues
|
||||
// Populate comments for all issues (avoid N+1 queries)
|
||||
allComments, err := store.GetCommentsForIssues(ctx, issueIDs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get comments: %w", err)
|
||||
}
|
||||
for _, issue := range allIssues {
|
||||
comments, err := store.GetIssueComments(ctx, issue.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get comments for %s: %w", issue.ID, err)
|
||||
}
|
||||
issue.Comments = comments
|
||||
issue.Comments = allComments[issue.ID]
|
||||
}
|
||||
|
||||
// Write to JSONL file with atomic replace (temp file + rename)
|
||||
|
||||
Reference in New Issue
Block a user