From 308dd93484e3e4275a4447fb4fdd3efddcf34e08 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Sat, 31 Oct 2026 00:38:45 -0700 Subject: [PATCH] perf: speed up the issue and pull request dashboards and fix table indices (#40001) (#40096) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport #40001 by @silverwind The dashboard counts ran queries whose cost grew with the whole instance instead of the viewing user. On https://gitea.com/pulls the two review counts alone take about 1.4s per page load. - Check the owner of the already joined repository with `EXISTS` instead of listing every public repository and public user - Hide superseded review requests with `NOT EXISTS` instead of grouping every review on the instance - Skip the review counts on `/issues`, which never shows them - Index `team_user.uid` for permission and team lookups by user - Fix indices for table `watch`, `package_property` and `action_run`, fix #40090 Median page times in ms, before → after, on a synthetic dataset sized like gitea.com (175k users, 40k repos, 120k issues, 1M comments): | Page | SQLite | PostgreSQL 17 | MySQL 8.4 | MariaDB 13 | |---|---|---|---|---| | `/pulls` | 901 → 56 | 330 → 156 | 189 → 124 | 192 → 218 | | `/pulls?type=review_requested` | 1797 → 66 | 557 → 254 | 307 → 245 | 604 → 499 | | `/pulls?type=reviewed_by` | 1646 → 118 | 537 → 316 | 515 → 265 | 528 → 682 | | `/pulls?type=mentioned` | 1403 → 46 | 426 → 119 | 165 → 116 | 179 → 179 | | `/issues` | 923 → 33 | 293 → 39 | 178 → 65 | 140 → 41 | | `/api/v1/repos/issues/search` (anonymous) | 870 → 110 | 144 → 104 | 123 → 87 | 170 → 193 | MariaDB checks the owner per row on long scans across all public repositories, which makes a few pages slower there. Co-authored-by: silverwind Co-authored-by: wxiaoguang --- models/actions/run.go | 4 ++-- models/issues/issue_search.go | 28 ++++++++++------------------ models/organization/team_user.go | 4 ++-- models/packages/package_property.go | 6 +++--- models/repo/repo_list.go | 3 ++- models/repo/watch.go | 2 +- routers/web/user/home.go | 3 +++ 7 files changed, 23 insertions(+), 27 deletions(-) diff --git a/models/actions/run.go b/models/actions/run.go index e76ccccdb72..d558ddf70c2 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -34,8 +34,8 @@ type ActionRun struct { RepoID int64 `xorm:"unique(repo_index) index(repo_status)"` Repo *repo_model.Repository `xorm:"-"` OwnerID int64 `xorm:"index"` - WorkflowID string `xorm:"index"` // the name of workflow file - Index int64 `xorm:"index unique(repo_index)"` // a unique number for each run of a repository + WorkflowID string `xorm:"index"` // the name of workflow file + Index int64 `xorm:"unique(repo_index)"` // a unique number for each run of a repository TriggerUserID int64 `xorm:"index"` TriggerUser *user_model.User `xorm:"-"` ScheduleID int64 diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go index 2653ef297a2..d284c798005 100644 --- a/models/issues/issue_search.go +++ b/models/issues/issue_search.go @@ -218,10 +218,7 @@ func applyRepoConditions(sess db.Session, opts *IssuesOptions) { opts.RepoCond = builder.In("issue.repo_id", opts.RepoIDs) } if opts.AllPublic { - if opts.RepoCond == nil { - opts.RepoCond = builder.NewCond() - } - opts.RepoCond = opts.RepoCond.Or(builder.In("issue.repo_id", builder.Select("id").From("repository").Where(repo_model.PublicRepoUnderPublicOwnerCond()))) + opts.RepoCond = builder.Or(opts.RepoCond, repo_model.PublicRepoUnderPublicOwnerCond()) } if opts.RepoCond != nil { sess.And(opts.RepoCond) @@ -402,22 +399,17 @@ func applyReviewRequestedCondition(sess db.Session, reviewRequestedID int64) { From("team_user"). Where(builder.Eq{"team_user.uid": reviewRequestedID}) - // if the review is approved or rejected, it should not be shown in the review requested list - maxReview := builder.Select("MAX(r.id)"). - From("review as r"). - Where(builder.In("r.type", []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest})). - GroupBy("r.issue_id, r.reviewer_id, r.reviewer_team_id") - subQuery := builder.Select("review.issue_id"). From("review"). - Where(builder.And( - builder.Eq{"review.type": ReviewTypeRequest}, - builder.Or( - builder.Eq{"review.reviewer_id": reviewRequestedID}, - builder.In("review.reviewer_team_id", existInTeamQuery), - ), - builder.In("review.id", maxReview), - )) + Where(builder.Eq{"review.type": ReviewTypeRequest}). + And(builder.Or( + builder.Eq{"review.reviewer_id": reviewRequestedID}, + builder.In("review.reviewer_team_id", existInTeamQuery), + )). + // if the review is approved or rejected, it should not be shown in the review requested list + And(builder.NotExists(builder.Select("1").From("review as r"). + Where(builder.Expr("r.issue_id = review.issue_id AND r.reviewer_id = review.reviewer_id AND r.reviewer_team_id = review.reviewer_team_id AND r.id > review.id"). + And(builder.In("r.type", []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest}))))) sess.Where("issue.poster_id <> ?", reviewRequestedID). And(builder.In("issue.id", subQuery)) } diff --git a/models/organization/team_user.go b/models/organization/team_user.go index 069f85c0dc3..45fa1a92296 100644 --- a/models/organization/team_user.go +++ b/models/organization/team_user.go @@ -12,12 +12,12 @@ import ( "xorm.io/builder" ) -// TeamUser represents an team-user relation. +// TeamUser represents a team-user relation. type TeamUser struct { ID int64 `xorm:"pk autoincr"` OrgID int64 `xorm:"INDEX"` TeamID int64 `xorm:"UNIQUE(s)"` - UID int64 `xorm:"UNIQUE(s)"` + UID int64 `xorm:"UNIQUE(s) INDEX"` } // IsTeamMember returns true if given user is a member of team. diff --git a/models/packages/package_property.go b/models/packages/package_property.go index 6aa5c435a95..cc65e1042be 100644 --- a/models/packages/package_property.go +++ b/models/packages/package_property.go @@ -30,9 +30,9 @@ const ( // PackageProperty represents a property of a package, version or file type PackageProperty struct { ID int64 `xorm:"pk autoincr"` - RefType PropertyType `xorm:"INDEX NOT NULL"` - RefID int64 `xorm:"INDEX NOT NULL"` - Name string `xorm:"INDEX NOT NULL"` + RefType PropertyType `xorm:"NOT NULL"` + RefID int64 `xorm:"INDEX(ref_id_name) NOT NULL"` + Name string `xorm:"INDEX(ref_id_name) NOT NULL"` Value string `xorm:"LONGTEXT NOT NULL"` } diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index 066f75df2c4..5d4284534d0 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -762,7 +762,8 @@ func FindUserCodeAccessibleOwnerRepoIDs(ctx context.Context, ownerID int64, user func PublicRepoUnderPublicOwnerCond() builder.Cond { return builder.And( builder.Eq{"`repository`.is_private": false}, - builder.In("`repository`.owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"visibility": structs.VisibleTypePublic})), + builder.Exists(builder.Select("1").From("`user` repo_owner"). + Where(builder.Expr("repo_owner.id = `repository`.owner_id").And(builder.Eq{"repo_owner.visibility": structs.VisibleTypePublic}))), ) } diff --git a/models/repo/watch.go b/models/repo/watch.go index 3736ed3ca6e..a497643aec0 100644 --- a/models/repo/watch.go +++ b/models/repo/watch.go @@ -39,7 +39,7 @@ const ( type Watch struct { ID int64 `xorm:"pk autoincr"` UserID int64 `xorm:"UNIQUE(watch)"` - RepoID int64 `xorm:"UNIQUE(watch)"` + RepoID int64 `xorm:"UNIQUE(watch) INDEX"` Mode WatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` diff --git a/routers/web/user/home.go b/routers/web/user/home.go index c401ed8cb1e..6e422b408bd 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -821,6 +821,9 @@ func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMod if err != nil { return nil, err } + if !opts.IsPull.Value() { + return ret, nil + } ret.ReviewRequestedCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.ReviewRequestedID = optional.Some(doerID) })) if err != nil { return nil, err