Skip to content

Commit 74e881a

Browse files
lunnywxiaoguang
authored andcommitted
Filter reviews of one pull request in memory instead of database to reduce slow response because of lacking database index (go-gitea#33106)
This PR fixes a performance problem when reviewing a pull request in a big instance which have many records in the `review` table. Traditionally, we should add more indexes in that table. But since dismissed reviews of 1 pull request will not be too many as expected in a common repository. Filtering reviews in the memory should be more quick . --------- Co-authored-by: wxiaoguang <[email protected]>
1 parent 63b3a33 commit 74e881a

File tree

4 files changed

+53
-39
lines changed

4 files changed

+53
-39
lines changed

models/issues/pull.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error {
301301
return nil
302302
}
303303

304-
reviews, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
304+
reviews, _, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
305305
if err != nil {
306306
return err
307307
}
@@ -320,7 +320,7 @@ func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error {
320320

321321
// LoadRequestedReviewersTeams loads the requested reviewers teams.
322322
func (pr *PullRequest) LoadRequestedReviewersTeams(ctx context.Context) error {
323-
reviews, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
323+
reviews, _, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
324324
if err != nil {
325325
return err
326326
}

models/issues/review_list.go

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package issues
55

66
import (
77
"context"
8+
"slices"
9+
"sort"
810

911
"code.gitea.io/gitea/models/db"
1012
organization_model "code.gitea.io/gitea/models/organization"
@@ -153,43 +155,60 @@ func CountReviews(ctx context.Context, opts FindReviewOptions) (int64, error) {
153155
return db.GetEngine(ctx).Where(opts.toCond()).Count(&Review{})
154156
}
155157

156-
// GetReviewersFromOriginalAuthorsByIssueID gets the latest review of each original authors for a pull request
157-
func GetReviewersFromOriginalAuthorsByIssueID(ctx context.Context, issueID int64) (ReviewList, error) {
158+
// GetReviewsByIssueID gets the latest review of each reviewer for a pull request
159+
// The first returned parameter is the latest review of each individual reviewer or team
160+
// The second returned parameter is the latest review of each original author which is migrated from other systems
161+
// The reviews are sorted by updated time
162+
func GetReviewsByIssueID(ctx context.Context, issueID int64) (latestReviews, migratedOriginalReviews ReviewList, err error) {
158163
reviews := make([]*Review, 0, 10)
159164

160-
// Get latest review of each reviewer, sorted in order they were made
161-
if err := db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND original_author_id <> 0 GROUP BY issue_id, original_author_id) ORDER BY review.updated_unix ASC",
162-
issueID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest).
163-
Find(&reviews); err != nil {
164-
return nil, err
165+
// Get all reviews for the issue id
166+
if err := db.GetEngine(ctx).Where("issue_id=?", issueID).OrderBy("updated_unix ASC").Find(&reviews); err != nil {
167+
return nil, nil, err
165168
}
166169

167-
return reviews, nil
168-
}
169-
170-
// GetReviewsByIssueID gets the latest review of each reviewer for a pull request
171-
func GetReviewsByIssueID(ctx context.Context, issueID int64) (ReviewList, error) {
172-
reviews := make([]*Review, 0, 10)
173-
174-
sess := db.GetEngine(ctx)
170+
// filter them in memory to get the latest review of each reviewer
171+
// Since the reviews should not be too many for one issue, less than 100 commonly, it's acceptable to do this in memory
172+
// And since there are too less indexes in review table, it will be very slow to filter in the database
173+
reviewersMap := make(map[int64][]*Review) // key is reviewer id
174+
originalReviewersMap := make(map[int64][]*Review) // key is original author id
175+
reviewTeamsMap := make(map[int64][]*Review) // key is reviewer team id
176+
countedReivewTypes := []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest}
177+
for _, review := range reviews {
178+
if review.ReviewerTeamID == 0 && slices.Contains(countedReivewTypes, review.Type) && !review.Dismissed {
179+
if review.OriginalAuthorID != 0 {
180+
originalReviewersMap[review.OriginalAuthorID] = append(originalReviewersMap[review.OriginalAuthorID], review)
181+
} else {
182+
reviewersMap[review.ReviewerID] = append(reviewersMap[review.ReviewerID], review)
183+
}
184+
} else if review.ReviewerTeamID != 0 && review.OriginalAuthorID == 0 {
185+
reviewTeamsMap[review.ReviewerTeamID] = append(reviewTeamsMap[review.ReviewerTeamID], review)
186+
}
187+
}
175188

176-
// Get latest review of each reviewer, sorted in order they were made
177-
if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND dismissed = ? AND original_author_id = 0 GROUP BY issue_id, reviewer_id) ORDER BY review.updated_unix ASC",
178-
issueID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, false).
179-
Find(&reviews); err != nil {
180-
return nil, err
189+
individualReviews := make([]*Review, 0, 10)
190+
for _, reviews := range reviewersMap {
191+
individualReviews = append(individualReviews, reviews[len(reviews)-1])
181192
}
193+
sort.Slice(individualReviews, func(i, j int) bool {
194+
return individualReviews[i].UpdatedUnix < individualReviews[j].UpdatedUnix
195+
})
182196

183-
teamReviewRequests := make([]*Review, 0, 5)
184-
if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id <> 0 AND original_author_id = 0 GROUP BY issue_id, reviewer_team_id) ORDER BY review.updated_unix ASC",
185-
issueID).
186-
Find(&teamReviewRequests); err != nil {
187-
return nil, err
197+
originalReviews := make([]*Review, 0, 10)
198+
for _, reviews := range originalReviewersMap {
199+
originalReviews = append(originalReviews, reviews[len(reviews)-1])
188200
}
201+
sort.Slice(originalReviews, func(i, j int) bool {
202+
return originalReviews[i].UpdatedUnix < originalReviews[j].UpdatedUnix
203+
})
189204

190-
if len(teamReviewRequests) > 0 {
191-
reviews = append(reviews, teamReviewRequests...)
205+
teamReviewRequests := make([]*Review, 0, 5)
206+
for _, reviews := range reviewTeamsMap {
207+
teamReviewRequests = append(teamReviewRequests, reviews[len(reviews)-1])
192208
}
209+
sort.Slice(teamReviewRequests, func(i, j int) bool {
210+
return teamReviewRequests[i].UpdatedUnix < teamReviewRequests[j].UpdatedUnix
211+
})
193212

194-
return reviews, nil
213+
return append(individualReviews, teamReviewRequests...), originalReviews, nil
195214
}

models/issues/review_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,9 @@ func TestGetReviewersByIssueID(t *testing.T) {
162162
},
163163
)
164164

165-
allReviews, err := issues_model.GetReviewsByIssueID(db.DefaultContext, issue.ID)
165+
allReviews, migratedReviews, err := issues_model.GetReviewsByIssueID(db.DefaultContext, issue.ID)
166166
assert.NoError(t, err)
167+
assert.Empty(t, migratedReviews)
167168
for _, review := range allReviews {
168169
assert.NoError(t, review.LoadReviewer(db.DefaultContext))
169170
}

routers/web/repo/issue_page_meta.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ func (d *IssuePageMetaData) retrieveReviewersData(ctx *context.Context) {
193193
var posterID int64
194194
var isClosed bool
195195
var reviews issues_model.ReviewList
196+
var err error
196197

197198
if d.Issue == nil {
198199
if ctx.Doer != nil {
@@ -206,14 +207,7 @@ func (d *IssuePageMetaData) retrieveReviewersData(ctx *context.Context) {
206207

207208
isClosed = d.Issue.IsClosed || d.Issue.PullRequest.HasMerged
208209

209-
originalAuthorReviews, err := issues_model.GetReviewersFromOriginalAuthorsByIssueID(ctx, d.Issue.ID)
210-
if err != nil {
211-
ctx.ServerError("GetReviewersFromOriginalAuthorsByIssueID", err)
212-
return
213-
}
214-
data.OriginalReviews = originalAuthorReviews
215-
216-
reviews, err = issues_model.GetReviewsByIssueID(ctx, d.Issue.ID)
210+
reviews, data.OriginalReviews, err = issues_model.GetReviewsByIssueID(ctx, d.Issue.ID)
217211
if err != nil {
218212
ctx.ServerError("GetReviewersByIssueID", err)
219213
return

0 commit comments

Comments
 (0)