Skip to content

Commit fbe6d9d

Browse files
authored
Use batch database operations instead of one by one to optimze api pulls (#32680)
Resolve #31492 The response time for the Pull Requests API has improved significantly, dropping from over `2000ms` to about `350ms` on my local machine. It's about `6` times faster. A key area for further optimization lies in batch-fetching data for `apiPullRequest.ChangedFiles, apiPullRequest.Additions, and apiPullRequest.Deletions`. Tests `TestAPIViewPulls` does exist and new tests added. - This PR also fixes some bugs in `GetDiff` functions. - This PR also fixes data inconsistent in test data. For a pull request, the head branch's reference should be equal to the reference in `pull/xxx/head`.
1 parent 2ac6f2b commit fbe6d9d

File tree

15 files changed

+566
-93
lines changed

15 files changed

+566
-93
lines changed

models/fixtures/review.yml

+18-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
reviewer_id: 1
55
issue_id: 2
66
content: "Demo Review"
7+
original_author_id: 0
78
updated_unix: 946684810
89
created_unix: 946684810
910
-
@@ -12,6 +13,7 @@
1213
reviewer_id: 534543
1314
issue_id: 534543
1415
content: "Invalid Review #1"
16+
original_author_id: 0
1517
updated_unix: 946684810
1618
created_unix: 946684810
1719
-
@@ -20,6 +22,7 @@
2022
reviewer_id: 1
2123
issue_id: 343545
2224
content: "Invalid Review #2"
25+
original_author_id: 0
2326
updated_unix: 946684810
2427
created_unix: 946684810
2528
-
@@ -28,6 +31,7 @@
2831
reviewer_id: 1
2932
issue_id: 2
3033
content: "Pending Review"
34+
original_author_id: 0
3135
updated_unix: 946684810
3236
created_unix: 946684810
3337
-
@@ -36,6 +40,7 @@
3640
reviewer_id: 1
3741
issue_id: 3
3842
content: "New review 1"
43+
original_author_id: 0
3944
updated_unix: 946684810
4045
created_unix: 946684810
4146
-
@@ -61,8 +66,8 @@
6166
type: 1
6267
reviewer_id: 4
6368
issue_id: 3
64-
original_author_id: 0
6569
content: "New review 5"
70+
original_author_id: 0
6671
commit_id: 8091a55037cd59e47293aca02981b5a67076b364
6772
stale: true
6873
updated_unix: 946684813
@@ -73,16 +78,17 @@
7378
reviewer_id: 2
7479
issue_id: 3
7580
content: "New review 3 rejected"
81+
original_author_id: 0
7682
updated_unix: 946684814
7783
created_unix: 946684814
78-
original_author_id: 0
7984

8085
-
8186
id: 10
8287
type: 3
8388
reviewer_id: 100
8489
issue_id: 3
8590
content: "a deleted user's review"
91+
original_author_id: 0
8692
official: true
8793
updated_unix: 946684815
8894
created_unix: 946684815
@@ -112,6 +118,7 @@
112118
reviewer_id: 5
113119
issue_id: 11
114120
content: "old review from user5"
121+
original_author_id: 0
115122
updated_unix: 946684820
116123
created_unix: 946684820
117124

@@ -121,6 +128,7 @@
121128
reviewer_id: 5
122129
issue_id: 11
123130
content: "duplicate review from user5 (latest)"
131+
original_author_id: 0
124132
updated_unix: 946684830
125133
created_unix: 946684830
126134

@@ -130,6 +138,7 @@
130138
reviewer_id: 6
131139
issue_id: 11
132140
content: "singular review from org6 and final review for this pr"
141+
original_author_id: 0
133142
updated_unix: 946684831
134143
created_unix: 946684831
135144

@@ -139,6 +148,7 @@
139148
reviewer_id: 20
140149
issue_id: 20
141150
content: "review request for user20"
151+
original_author_id: 0
142152
updated_unix: 946684832
143153
created_unix: 946684832
144154

@@ -148,6 +158,7 @@
148158
reviewer_id: 20
149159
issue_id: 20
150160
content: "review approved by user20"
161+
original_author_id: 0
151162
updated_unix: 946684833
152163
created_unix: 946684833
153164

@@ -158,6 +169,7 @@
158169
reviewer_team_id: 5
159170
issue_id: 20
160171
content: "review request for team5"
172+
original_author_id: 0
161173
updated_unix: 946684834
162174
created_unix: 946684834
163175

@@ -168,6 +180,7 @@
168180
reviewer_team_id: 0
169181
issue_id: 20
170182
content: "review request for user15"
183+
original_author_id: 0
171184
updated_unix: 946684835
172185
created_unix: 946684835
173186

@@ -177,6 +190,7 @@
177190
reviewer_id: 1
178191
issue_id: 2
179192
content: "Review Comment"
193+
original_author_id: 0
180194
updated_unix: 946684810
181195
created_unix: 946684810
182196

@@ -186,6 +200,7 @@
186200
reviewer_id: 5
187201
issue_id: 3
188202
content: "reviewed by user5"
203+
original_author_id: 0
189204
commit_id: 4a357436d925b5c974181ff12a994538ddc5a269
190205
updated_unix: 946684816
191206
created_unix: 946684816
@@ -196,5 +211,6 @@
196211
reviewer_id: 5
197212
issue_id: 3
198213
content: "review request for user5"
214+
original_author_id: 0
199215
updated_unix: 946684817
200216
created_unix: 946684817

models/issues/pull_list.go

+59
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"code.gitea.io/gitea/modules/log"
1717
"code.gitea.io/gitea/modules/util"
1818

19+
"xorm.io/builder"
1920
"xorm.io/xorm"
2021
)
2122

@@ -240,6 +241,64 @@ func (prs PullRequestList) GetIssueIDs() []int64 {
240241
})
241242
}
242243

244+
func (prs PullRequestList) LoadReviewCommentsCounts(ctx context.Context) (map[int64]int, error) {
245+
issueIDs := prs.GetIssueIDs()
246+
countsMap := make(map[int64]int, len(issueIDs))
247+
counts := make([]struct {
248+
IssueID int64
249+
Count int
250+
}, 0, len(issueIDs))
251+
if err := db.GetEngine(ctx).Select("issue_id, count(*) as count").
252+
Table("comment").In("issue_id", issueIDs).And("type = ?", CommentTypeReview).
253+
GroupBy("issue_id").Find(&counts); err != nil {
254+
return nil, err
255+
}
256+
for _, c := range counts {
257+
countsMap[c.IssueID] = c.Count
258+
}
259+
return countsMap, nil
260+
}
261+
262+
func (prs PullRequestList) LoadReviews(ctx context.Context) (ReviewList, error) {
263+
issueIDs := prs.GetIssueIDs()
264+
reviews := make([]*Review, 0, len(issueIDs))
265+
266+
subQuery := builder.Select("max(id) as id").
267+
From("review").
268+
Where(builder.In("issue_id", issueIDs)).
269+
And(builder.In("`type`", ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest)).
270+
And(builder.Eq{
271+
"dismissed": false,
272+
"original_author_id": 0,
273+
"reviewer_team_id": 0,
274+
}).
275+
GroupBy("issue_id, reviewer_id")
276+
// Get latest review of each reviewer, sorted in order they were made
277+
if err := db.GetEngine(ctx).In("id", subQuery).OrderBy("review.updated_unix ASC").Find(&reviews); err != nil {
278+
return nil, err
279+
}
280+
281+
teamReviewRequests := make([]*Review, 0, 5)
282+
subQueryTeam := builder.Select("max(id) as id").
283+
From("review").
284+
Where(builder.In("issue_id", issueIDs)).
285+
And(builder.Eq{
286+
"original_author_id": 0,
287+
}).And(builder.Neq{
288+
"reviewer_team_id": 0,
289+
}).
290+
GroupBy("issue_id, reviewer_team_id")
291+
if err := db.GetEngine(ctx).In("id", subQueryTeam).OrderBy("review.updated_unix ASC").Find(&teamReviewRequests); err != nil {
292+
return nil, err
293+
}
294+
295+
if len(teamReviewRequests) > 0 {
296+
reviews = append(reviews, teamReviewRequests...)
297+
}
298+
299+
return reviews, nil
300+
}
301+
243302
// HasMergedPullRequestInRepo returns whether the user(poster) has merged pull-request in the repo
244303
func HasMergedPullRequestInRepo(ctx context.Context, repoID, posterID int64) (bool, error) {
245304
return db.GetEngine(ctx).

models/issues/pull_list_test.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package issues_test
5+
6+
import (
7+
"testing"
8+
9+
"code.gitea.io/gitea/models/db"
10+
issues_model "code.gitea.io/gitea/models/issues"
11+
"code.gitea.io/gitea/models/unittest"
12+
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
func TestPullRequestList_LoadAttributes(t *testing.T) {
17+
assert.NoError(t, unittest.PrepareTestDatabase())
18+
19+
prs := []*issues_model.PullRequest{
20+
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}),
21+
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}),
22+
}
23+
assert.NoError(t, issues_model.PullRequestList(prs).LoadAttributes(db.DefaultContext))
24+
for _, pr := range prs {
25+
assert.NotNil(t, pr.Issue)
26+
assert.Equal(t, pr.IssueID, pr.Issue.ID)
27+
}
28+
29+
assert.NoError(t, issues_model.PullRequestList([]*issues_model.PullRequest{}).LoadAttributes(db.DefaultContext))
30+
}
31+
32+
func TestPullRequestList_LoadReviewCommentsCounts(t *testing.T) {
33+
assert.NoError(t, unittest.PrepareTestDatabase())
34+
35+
prs := []*issues_model.PullRequest{
36+
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}),
37+
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}),
38+
}
39+
reviewComments, err := issues_model.PullRequestList(prs).LoadReviewCommentsCounts(db.DefaultContext)
40+
assert.NoError(t, err)
41+
assert.Len(t, reviewComments, 2)
42+
for _, pr := range prs {
43+
assert.EqualValues(t, reviewComments[pr.IssueID], 1)
44+
}
45+
}
46+
47+
func TestPullRequestList_LoadReviews(t *testing.T) {
48+
assert.NoError(t, unittest.PrepareTestDatabase())
49+
50+
prs := []*issues_model.PullRequest{
51+
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}),
52+
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}),
53+
}
54+
reviewList, err := issues_model.PullRequestList(prs).LoadReviews(db.DefaultContext)
55+
assert.NoError(t, err)
56+
// 1, 7, 8, 9, 10, 22
57+
assert.Len(t, reviewList, 6)
58+
assert.EqualValues(t, 1, reviewList[0].ID)
59+
assert.EqualValues(t, 7, reviewList[1].ID)
60+
assert.EqualValues(t, 8, reviewList[2].ID)
61+
assert.EqualValues(t, 9, reviewList[3].ID)
62+
assert.EqualValues(t, 10, reviewList[4].ID)
63+
assert.EqualValues(t, 22, reviewList[5].ID)
64+
}

models/issues/pull_test.go

+2-18
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func TestPullRequestsNewest(t *testing.T) {
7979
func TestLoadRequestedReviewers(t *testing.T) {
8080
assert.NoError(t, unittest.PrepareTestDatabase())
8181

82-
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
82+
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2})
8383
assert.NoError(t, pull.LoadIssue(db.DefaultContext))
8484
issue := pull.Issue
8585
assert.NoError(t, issue.LoadRepo(db.DefaultContext))
@@ -93,7 +93,7 @@ func TestLoadRequestedReviewers(t *testing.T) {
9393
assert.NotNil(t, comment)
9494

9595
assert.NoError(t, pull.LoadRequestedReviewers(db.DefaultContext))
96-
assert.Len(t, pull.RequestedReviewers, 1)
96+
assert.Len(t, pull.RequestedReviewers, 6)
9797

9898
comment, err = issues_model.RemoveReviewRequest(db.DefaultContext, issue, user1, &user_model.User{})
9999
assert.NoError(t, err)
@@ -234,22 +234,6 @@ func TestPullRequest_UpdateCols(t *testing.T) {
234234
unittest.CheckConsistencyFor(t, pr)
235235
}
236236

237-
func TestPullRequestList_LoadAttributes(t *testing.T) {
238-
assert.NoError(t, unittest.PrepareTestDatabase())
239-
240-
prs := []*issues_model.PullRequest{
241-
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}),
242-
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}),
243-
}
244-
assert.NoError(t, issues_model.PullRequestList(prs).LoadAttributes(db.DefaultContext))
245-
for _, pr := range prs {
246-
assert.NotNil(t, pr.Issue)
247-
assert.Equal(t, pr.IssueID, pr.Issue.ID)
248-
}
249-
250-
assert.NoError(t, issues_model.PullRequestList([]*issues_model.PullRequest{}).LoadAttributes(db.DefaultContext))
251-
}
252-
253237
// TODO TestAddTestPullRequestTask
254238

255239
func TestPullRequest_IsWorkInProgress(t *testing.T) {

models/issues/review_list.go

+3-8
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,9 @@ func (reviews ReviewList) LoadReviewersTeams(ctx context.Context) error {
4747
}
4848
}
4949

50-
teamsMap := make(map[int64]*organization_model.Team, 0)
51-
for _, teamID := range reviewersTeamsIDs {
52-
team, err := organization_model.GetTeamByID(ctx, teamID)
53-
if err != nil {
54-
return err
55-
}
56-
57-
teamsMap[teamID] = team
50+
teamsMap, err := organization_model.GetTeamsByIDs(ctx, reviewersTeamsIDs)
51+
if err != nil {
52+
return err
5853
}
5954

6055
for _, review := range reviews {

0 commit comments

Comments
 (0)