Skip to content

Commit b4f0eed

Browse files
GiteaBotlunnywxiaoguang
authored
Filter reviews of one pull request in memory instead of database to reduce slow response because of lacking database index (#33106) (#33128)
Backport #33106 by @lunny 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: Lunny Xiao <[email protected]> Co-authored-by: wxiaoguang <[email protected]>
1 parent 63b3a33 commit b4f0eed

File tree

4 files changed

+53
-39
lines changed

4 files changed

+53
-39
lines changed

models/issues/pull.go

+2-2
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

+47-28
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

+2-1
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

+2-8
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)