Skip to content

Commit aed3b53

Browse files
authored
Some performance optimization on dashboard and issues page (#29010)
This PR do some loading speed optimization for feeds user interface pages. - Load action users batchly but not one by one. - Load action repositories batchly but not one by one. - Load action's Repo Owners batchly but not one by one. - Load action's possible issues batchly but not one by one. - Load action's possible comments batchly but not one by one.
1 parent 75a9f61 commit aed3b53

File tree

7 files changed

+228
-75
lines changed

7 files changed

+228
-75
lines changed

models/activities/action.go

+53-53
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ type Action struct {
148148
Repo *repo_model.Repository `xorm:"-"`
149149
CommentID int64 `xorm:"INDEX"`
150150
Comment *issues_model.Comment `xorm:"-"`
151+
Issue *issues_model.Issue `xorm:"-"` // get the issue id from content
151152
IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
152153
RefName string
153154
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
@@ -290,11 +291,6 @@ func (a *Action) GetRepoAbsoluteLink(ctx context.Context) string {
290291
return setting.AppURL + url.PathEscape(a.GetRepoUserName(ctx)) + "/" + url.PathEscape(a.GetRepoName(ctx))
291292
}
292293

293-
// GetCommentHTMLURL returns link to action comment.
294-
func (a *Action) GetCommentHTMLURL(ctx context.Context) string {
295-
return a.getCommentHTMLURL(ctx)
296-
}
297-
298294
func (a *Action) loadComment(ctx context.Context) (err error) {
299295
if a.CommentID == 0 || a.Comment != nil {
300296
return nil
@@ -303,69 +299,44 @@ func (a *Action) loadComment(ctx context.Context) (err error) {
303299
return err
304300
}
305301

306-
func (a *Action) getCommentHTMLURL(ctx context.Context) string {
302+
// GetCommentHTMLURL returns link to action comment.
303+
func (a *Action) GetCommentHTMLURL(ctx context.Context) string {
307304
if a == nil {
308305
return "#"
309306
}
310307
_ = a.loadComment(ctx)
311308
if a.Comment != nil {
312309
return a.Comment.HTMLURL(ctx)
313310
}
314-
if len(a.GetIssueInfos()) == 0 {
315-
return "#"
316-
}
317-
// Return link to issue
318-
issueIDString := a.GetIssueInfos()[0]
319-
issueID, err := strconv.ParseInt(issueIDString, 10, 64)
320-
if err != nil {
321-
return "#"
322-
}
323311

324-
issue, err := issues_model.GetIssueByID(ctx, issueID)
325-
if err != nil {
312+
if err := a.LoadIssue(ctx); err != nil || a.Issue == nil {
326313
return "#"
327314
}
328-
329-
if err = issue.LoadRepo(ctx); err != nil {
315+
if err := a.Issue.LoadRepo(ctx); err != nil {
330316
return "#"
331317
}
332318

333-
return issue.HTMLURL()
319+
return a.Issue.HTMLURL()
334320
}
335321

336322
// GetCommentLink returns link to action comment.
337323
func (a *Action) GetCommentLink(ctx context.Context) string {
338-
return a.getCommentLink(ctx)
339-
}
340-
341-
func (a *Action) getCommentLink(ctx context.Context) string {
342324
if a == nil {
343325
return "#"
344326
}
345327
_ = a.loadComment(ctx)
346328
if a.Comment != nil {
347329
return a.Comment.Link(ctx)
348330
}
349-
if len(a.GetIssueInfos()) == 0 {
350-
return "#"
351-
}
352-
// Return link to issue
353-
issueIDString := a.GetIssueInfos()[0]
354-
issueID, err := strconv.ParseInt(issueIDString, 10, 64)
355-
if err != nil {
356-
return "#"
357-
}
358331

359-
issue, err := issues_model.GetIssueByID(ctx, issueID)
360-
if err != nil {
332+
if err := a.LoadIssue(ctx); err != nil || a.Issue == nil {
361333
return "#"
362334
}
363-
364-
if err = issue.LoadRepo(ctx); err != nil {
335+
if err := a.Issue.LoadRepo(ctx); err != nil {
365336
return "#"
366337
}
367338

368-
return issue.Link()
339+
return a.Issue.Link()
369340
}
370341

371342
// GetBranch returns the action's repository branch.
@@ -393,6 +364,10 @@ func (a *Action) GetCreate() time.Time {
393364
return a.CreatedUnix.AsTime()
394365
}
395366

367+
func (a *Action) IsIssueEvent() bool {
368+
return a.OpType.InActions("comment_issue", "approve_pull_request", "reject_pull_request", "comment_pull", "merge_pull_request")
369+
}
370+
396371
// GetIssueInfos returns a list of associated information with the action.
397372
func (a *Action) GetIssueInfos() []string {
398373
// make sure it always returns 3 elements, because there are some access to the a[1] and a[2] without checking the length
@@ -403,27 +378,52 @@ func (a *Action) GetIssueInfos() []string {
403378
return ret
404379
}
405380

381+
func (a *Action) getIssueIndex() int64 {
382+
infos := a.GetIssueInfos()
383+
if len(infos) == 0 {
384+
return 0
385+
}
386+
index, _ := strconv.ParseInt(infos[0], 10, 64)
387+
return index
388+
}
389+
390+
func (a *Action) LoadIssue(ctx context.Context) error {
391+
if a.Issue != nil {
392+
return nil
393+
}
394+
if index := a.getIssueIndex(); index > 0 {
395+
issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index)
396+
if err != nil {
397+
return err
398+
}
399+
a.Issue = issue
400+
a.Issue.Repo = a.Repo
401+
}
402+
return nil
403+
}
404+
406405
// GetIssueTitle returns the title of first issue associated with the action.
407406
func (a *Action) GetIssueTitle(ctx context.Context) string {
408-
index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
409-
issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index)
410-
if err != nil {
411-
log.Error("GetIssueByIndex: %v", err)
412-
return "500 when get issue"
407+
if err := a.LoadIssue(ctx); err != nil {
408+
log.Error("LoadIssue: %v", err)
409+
return "<500 when get issue>"
410+
}
411+
if a.Issue == nil {
412+
return "<Issue not found>"
413413
}
414-
return issue.Title
414+
return a.Issue.Title
415415
}
416416

417-
// GetIssueContent returns the content of first issue associated with
418-
// this action.
417+
// GetIssueContent returns the content of first issue associated with this action.
419418
func (a *Action) GetIssueContent(ctx context.Context) string {
420-
index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
421-
issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index)
422-
if err != nil {
423-
log.Error("GetIssueByIndex: %v", err)
424-
return "500 when get issue"
419+
if err := a.LoadIssue(ctx); err != nil {
420+
log.Error("LoadIssue: %v", err)
421+
return "<500 when get issue>"
422+
}
423+
if a.Issue == nil {
424+
return "<Content not found>"
425425
}
426-
return issue.Content
426+
return a.Issue.Content
427427
}
428428

429429
// GetFeedsOptions options for retrieving feeds
@@ -463,7 +463,7 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
463463
return nil, 0, fmt.Errorf("FindAndCount: %w", err)
464464
}
465465

466-
if err := ActionList(actions).loadAttributes(ctx); err != nil {
466+
if err := ActionList(actions).LoadAttributes(ctx); err != nil {
467467
return nil, 0, fmt.Errorf("LoadAttributes: %w", err)
468468
}
469469

models/activities/action_list.go

+113-21
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@ package activities
66
import (
77
"context"
88
"fmt"
9+
"strconv"
910

1011
"code.gitea.io/gitea/models/db"
12+
issues_model "code.gitea.io/gitea/models/issues"
1113
repo_model "code.gitea.io/gitea/models/repo"
1214
user_model "code.gitea.io/gitea/models/user"
1315
"code.gitea.io/gitea/modules/container"
16+
"code.gitea.io/gitea/modules/util"
17+
18+
"xorm.io/builder"
1419
)
1520

1621
// ActionList defines a list of actions
@@ -24,7 +29,7 @@ func (actions ActionList) getUserIDs() []int64 {
2429
return userIDs.Values()
2530
}
2631

27-
func (actions ActionList) loadUsers(ctx context.Context) (map[int64]*user_model.User, error) {
32+
func (actions ActionList) LoadActUsers(ctx context.Context) (map[int64]*user_model.User, error) {
2833
if len(actions) == 0 {
2934
return nil, nil
3035
}
@@ -52,7 +57,7 @@ func (actions ActionList) getRepoIDs() []int64 {
5257
return repoIDs.Values()
5358
}
5459

55-
func (actions ActionList) loadRepositories(ctx context.Context) error {
60+
func (actions ActionList) LoadRepositories(ctx context.Context) error {
5661
if len(actions) == 0 {
5762
return nil
5863
}
@@ -63,49 +68,136 @@ func (actions ActionList) loadRepositories(ctx context.Context) error {
6368
if err != nil {
6469
return fmt.Errorf("find repository: %w", err)
6570
}
66-
6771
for _, action := range actions {
6872
action.Repo = repoMaps[action.RepoID]
6973
}
70-
return nil
74+
repos := repo_model.RepositoryList(util.ValuesOfMap(repoMaps))
75+
return repos.LoadUnits(ctx)
7176
}
7277

7378
func (actions ActionList) loadRepoOwner(ctx context.Context, userMap map[int64]*user_model.User) (err error) {
7479
if userMap == nil {
7580
userMap = make(map[int64]*user_model.User)
7681
}
7782

83+
userSet := make(container.Set[int64], len(actions))
7884
for _, action := range actions {
7985
if action.Repo == nil {
8086
continue
8187
}
82-
repoOwner, ok := userMap[action.Repo.OwnerID]
83-
if !ok {
84-
repoOwner, err = user_model.GetUserByID(ctx, action.Repo.OwnerID)
85-
if err != nil {
86-
if user_model.IsErrUserNotExist(err) {
87-
continue
88-
}
89-
return err
90-
}
91-
userMap[repoOwner.ID] = repoOwner
88+
if _, ok := userMap[action.Repo.OwnerID]; !ok {
89+
userSet.Add(action.Repo.OwnerID)
90+
}
91+
}
92+
93+
if err := db.GetEngine(ctx).
94+
In("id", userSet.Values()).
95+
Find(&userMap); err != nil {
96+
return fmt.Errorf("find user: %w", err)
97+
}
98+
99+
for _, action := range actions {
100+
if action.Repo != nil {
101+
action.Repo.Owner = userMap[action.Repo.OwnerID]
92102
}
93-
action.Repo.Owner = repoOwner
94103
}
95104

96105
return nil
97106
}
98107

99-
// loadAttributes loads all attributes
100-
func (actions ActionList) loadAttributes(ctx context.Context) error {
101-
userMap, err := actions.loadUsers(ctx)
108+
// LoadAttributes loads all attributes
109+
func (actions ActionList) LoadAttributes(ctx context.Context) error {
110+
// the load sequence cannot be changed because of the dependencies
111+
userMap, err := actions.LoadActUsers(ctx)
102112
if err != nil {
103113
return err
104114
}
105-
106-
if err := actions.loadRepositories(ctx); err != nil {
115+
if err := actions.LoadRepositories(ctx); err != nil {
116+
return err
117+
}
118+
if err := actions.loadRepoOwner(ctx, userMap); err != nil {
119+
return err
120+
}
121+
if err := actions.LoadIssues(ctx); err != nil {
107122
return err
108123
}
124+
return actions.LoadComments(ctx)
125+
}
126+
127+
func (actions ActionList) LoadComments(ctx context.Context) error {
128+
if len(actions) == 0 {
129+
return nil
130+
}
131+
132+
commentIDs := make([]int64, 0, len(actions))
133+
for _, action := range actions {
134+
if action.CommentID > 0 {
135+
commentIDs = append(commentIDs, action.CommentID)
136+
}
137+
}
109138

110-
return actions.loadRepoOwner(ctx, userMap)
139+
commentsMap := make(map[int64]*issues_model.Comment, len(commentIDs))
140+
if err := db.GetEngine(ctx).In("id", commentIDs).Find(&commentsMap); err != nil {
141+
return fmt.Errorf("find comment: %w", err)
142+
}
143+
144+
for _, action := range actions {
145+
if action.CommentID > 0 {
146+
action.Comment = commentsMap[action.CommentID]
147+
if action.Comment != nil {
148+
action.Comment.Issue = action.Issue
149+
}
150+
}
151+
}
152+
return nil
153+
}
154+
155+
func (actions ActionList) LoadIssues(ctx context.Context) error {
156+
if len(actions) == 0 {
157+
return nil
158+
}
159+
160+
conditions := builder.NewCond()
161+
issueNum := 0
162+
for _, action := range actions {
163+
if action.IsIssueEvent() {
164+
infos := action.GetIssueInfos()
165+
if len(infos) == 0 {
166+
continue
167+
}
168+
index, _ := strconv.ParseInt(infos[0], 10, 64)
169+
if index > 0 {
170+
conditions = conditions.Or(builder.Eq{
171+
"repo_id": action.RepoID,
172+
"`index`": index,
173+
})
174+
issueNum++
175+
}
176+
}
177+
}
178+
if !conditions.IsValid() {
179+
return nil
180+
}
181+
182+
issuesMap := make(map[string]*issues_model.Issue, issueNum)
183+
issues := make([]*issues_model.Issue, 0, issueNum)
184+
if err := db.GetEngine(ctx).Where(conditions).Find(&issues); err != nil {
185+
return fmt.Errorf("find issue: %w", err)
186+
}
187+
for _, issue := range issues {
188+
issuesMap[fmt.Sprintf("%d-%d", issue.RepoID, issue.Index)] = issue
189+
}
190+
191+
for _, action := range actions {
192+
if !action.IsIssueEvent() {
193+
continue
194+
}
195+
if index := action.getIssueIndex(); index > 0 {
196+
if issue, ok := issuesMap[fmt.Sprintf("%d-%d", action.RepoID, index)]; ok {
197+
action.Issue = issue
198+
action.Issue.Repo = action.Repo
199+
}
200+
}
201+
}
202+
return nil
111203
}

models/issues/issue_list.go

+10
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,16 @@ func (issues IssueList) loadTotalTrackedTimes(ctx context.Context) (err error) {
476476
}
477477
trackedTimes := make(map[int64]int64, len(issues))
478478

479+
reposMap := make(map[int64]*repo_model.Repository, len(issues))
480+
for _, issue := range issues {
481+
reposMap[issue.RepoID] = issue.Repo
482+
}
483+
repos := repo_model.RepositoryListOfMap(reposMap)
484+
485+
if err := repos.LoadUnits(ctx); err != nil {
486+
return err
487+
}
488+
479489
ids := make([]int64, 0, len(issues))
480490
for _, issue := range issues {
481491
if issue.Repo.IsTimetrackerEnabled(ctx) {

0 commit comments

Comments
 (0)