Skip to content

Performance improvements for pull request list API #30490

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
12 changes: 8 additions & 4 deletions models/issues/assignees.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,27 @@ func init() {

// LoadAssignees load assignees of this issue.
func (issue *Issue) LoadAssignees(ctx context.Context) (err error) {
if issue.isAssigneeLoaded || len(issue.Assignees) > 0 {
return nil
}

// Reset maybe preexisting assignees
issue.Assignees = []*user_model.User{}
issue.Assignee = nil

err = db.GetEngine(ctx).Table("`user`").
if err = db.GetEngine(ctx).Table("`user`").
Join("INNER", "issue_assignees", "assignee_id = `user`.id").
Where("issue_assignees.issue_id = ?", issue.ID).
Find(&issue.Assignees)
if err != nil {
Find(&issue.Assignees); err != nil {
return err
}

issue.isAssigneeLoaded = true
// Check if we have at least one assignee and if yes put it in as `Assignee`
if len(issue.Assignees) > 0 {
issue.Assignee = issue.Assignees[0]
}
return err
return nil
}

// GetAssigneeIDsByIssue returns the IDs of users assigned to an issue
Expand Down
12 changes: 5 additions & 7 deletions models/issues/comment_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,17 @@ import (
// CommentList defines a list of comments
type CommentList []*Comment

func (comments CommentList) getPosterIDs() []int64 {
return container.FilterSlice(comments, func(c *Comment) (int64, bool) {
return c.PosterID, c.PosterID > 0
})
}

// LoadPosters loads posters
func (comments CommentList) LoadPosters(ctx context.Context) error {
if len(comments) == 0 {
return nil
}

posterMaps, err := getPosters(ctx, comments.getPosterIDs())
posterIDs := container.FilterSlice(comments, func(c *Comment) (int64, bool) {
return c.PosterID, c.Poster == nil && c.PosterID > 0
})

posterMaps, err := getPostersByIDs(ctx, posterIDs)
if err != nil {
return err
}
Expand Down
96 changes: 59 additions & 37 deletions models/issues/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,44 +98,48 @@ var ErrIssueAlreadyChanged = util.NewInvalidArgumentErrorf("the issue is already

// Issue represents an issue or pull request of repository.
type Issue struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"`
Repo *repo_model.Repository `xorm:"-"`
Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
PosterID int64 `xorm:"INDEX"`
Poster *user_model.User `xorm:"-"`
OriginalAuthor string
OriginalAuthorID int64 `xorm:"index"`
Title string `xorm:"name"`
Content string `xorm:"LONGTEXT"`
RenderedContent template.HTML `xorm:"-"`
ContentVersion int `xorm:"NOT NULL DEFAULT 0"`
Labels []*Label `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"`
Project *project_model.Project `xorm:"-"`
Priority int
AssigneeID int64 `xorm:"-"`
Assignee *user_model.User `xorm:"-"`
IsClosed bool `xorm:"INDEX"`
IsRead bool `xorm:"-"`
IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not.
PullRequest *PullRequest `xorm:"-"`
NumComments int
Ref string
PinOrder int `xorm:"DEFAULT 0"`
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"`
Repo *repo_model.Repository `xorm:"-"`
Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
PosterID int64 `xorm:"INDEX"`
Poster *user_model.User `xorm:"-"`
OriginalAuthor string
OriginalAuthorID int64 `xorm:"index"`
Title string `xorm:"name"`
Content string `xorm:"LONGTEXT"`
RenderedContent template.HTML `xorm:"-"`
ContentVersion int `xorm:"NOT NULL DEFAULT 0"`
Labels []*Label `xorm:"-"`
isLabelsLoaded bool `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"`
isMilestoneLoaded bool `xorm:"-"`
Project *project_model.Project `xorm:"-"`
Priority int
AssigneeID int64 `xorm:"-"`
Assignee *user_model.User `xorm:"-"`
isAssigneeLoaded bool `xorm:"-"`
IsClosed bool `xorm:"INDEX"`
IsRead bool `xorm:"-"`
IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not.
PullRequest *PullRequest `xorm:"-"`
NumComments int
Ref string
PinOrder int `xorm:"DEFAULT 0"`

DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"`

CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
ClosedUnix timeutil.TimeStamp `xorm:"INDEX"`

Attachments []*repo_model.Attachment `xorm:"-"`
Comments CommentList `xorm:"-"`
Reactions ReactionList `xorm:"-"`
TotalTrackedTime int64 `xorm:"-"`
Assignees []*user_model.User `xorm:"-"`
Attachments []*repo_model.Attachment `xorm:"-"`
isAttachmentsLoaded bool `xorm:"-"`
Comments CommentList `xorm:"-"`
Reactions ReactionList `xorm:"-"`
TotalTrackedTime int64 `xorm:"-"`
Assignees []*user_model.User `xorm:"-"`

// IsLocked limits commenting abilities to users on an issue
// with write access
Expand Down Expand Up @@ -187,6 +191,19 @@ func (issue *Issue) LoadRepo(ctx context.Context) (err error) {
return nil
}

func (issue *Issue) LoadAttachments(ctx context.Context) (err error) {
if issue.isAttachmentsLoaded || issue.Attachments != nil {
return nil
}

issue.Attachments, err = repo_model.GetAttachmentsByIssueID(ctx, issue.ID)
if err != nil {
return fmt.Errorf("getAttachmentsByIssueID [%d]: %w", issue.ID, err)
}
issue.isAttachmentsLoaded = true
return nil
}

// IsTimetrackerEnabled returns true if the repo enables timetracking
func (issue *Issue) IsTimetrackerEnabled(ctx context.Context) bool {
if err := issue.LoadRepo(ctx); err != nil {
Expand Down Expand Up @@ -287,11 +304,12 @@ func (issue *Issue) loadReactions(ctx context.Context) (err error) {

// LoadMilestone load milestone of this issue.
func (issue *Issue) LoadMilestone(ctx context.Context) (err error) {
if (issue.Milestone == nil || issue.Milestone.ID != issue.MilestoneID) && issue.MilestoneID > 0 {
if !issue.isMilestoneLoaded && (issue.Milestone == nil || issue.Milestone.ID != issue.MilestoneID) && issue.MilestoneID > 0 {
issue.Milestone, err = GetMilestoneByRepoID(ctx, issue.RepoID, issue.MilestoneID)
if err != nil && !IsErrMilestoneNotExist(err) {
return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %w", issue.RepoID, issue.MilestoneID, err)
}
issue.isMilestoneLoaded = true
}
return nil
}
Expand Down Expand Up @@ -327,11 +345,8 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) {
return err
}

if issue.Attachments == nil {
issue.Attachments, err = repo_model.GetAttachmentsByIssueID(ctx, issue.ID)
if err != nil {
return fmt.Errorf("getAttachmentsByIssueID [%d]: %w", issue.ID, err)
}
if err = issue.LoadAttachments(ctx); err != nil {
return err
}

if err = issue.loadComments(ctx); err != nil {
Expand All @@ -350,6 +365,13 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) {
return issue.loadReactions(ctx)
}

func (issue *Issue) ResetAttributesLoaded() {
issue.isLabelsLoaded = false
issue.isMilestoneLoaded = false
issue.isAttachmentsLoaded = false
issue.isAssigneeLoaded = false
}

// GetIsRead load the `IsRead` field of the issue
func (issue *Issue) GetIsRead(ctx context.Context, userID int64) error {
issueUser := &IssueUser{IssueID: issue.ID, UID: userID}
Expand Down
6 changes: 5 additions & 1 deletion models/issues/issue_label.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ func NewIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_m
return err
}

issue.isLabelsLoaded = false
issue.Labels = nil
if err = issue.LoadLabels(ctx); err != nil {
return err
Expand Down Expand Up @@ -160,6 +161,8 @@ func NewIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us
return err
}

// reload all labels
issue.isLabelsLoaded = false
issue.Labels = nil
if err = issue.LoadLabels(ctx); err != nil {
return err
Expand Down Expand Up @@ -325,11 +328,12 @@ func FixIssueLabelWithOutsideLabels(ctx context.Context) (int64, error) {

// LoadLabels loads labels
func (issue *Issue) LoadLabels(ctx context.Context) (err error) {
if issue.Labels == nil && issue.ID != 0 {
if !issue.isLabelsLoaded && issue.Labels == nil && issue.ID != 0 {
issue.Labels, err = GetLabelsByIssueID(ctx, issue.ID)
if err != nil {
return fmt.Errorf("getLabelsByIssueID [%d]: %w", issue.ID, err)
}
issue.isLabelsLoaded = true
}
return nil
}
Expand Down
47 changes: 26 additions & 21 deletions models/issues/issue_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,16 @@ func (issues IssueList) LoadRepositories(ctx context.Context) (repo_model.Reposi
return repo_model.ValuesRepository(repoMaps), nil
}

func (issues IssueList) getPosterIDs() []int64 {
return container.FilterSlice(issues, func(issue *Issue) (int64, bool) {
return issue.PosterID, true
})
}

func (issues IssueList) loadPosters(ctx context.Context) error {
func (issues IssueList) LoadPosters(ctx context.Context) error {
if len(issues) == 0 {
return nil
}

posterMaps, err := getPosters(ctx, issues.getPosterIDs())
posterIDs := container.FilterSlice(issues, func(issue *Issue) (int64, bool) {
return issue.PosterID, issue.Poster == nil && issue.PosterID > 0
})

posterMaps, err := getPostersByIDs(ctx, posterIDs)
if err != nil {
return err
}
Expand All @@ -94,7 +92,7 @@ func (issues IssueList) loadPosters(ctx context.Context) error {
return nil
}

func getPosters(ctx context.Context, posterIDs []int64) (map[int64]*user_model.User, error) {
func getPostersByIDs(ctx context.Context, posterIDs []int64) (map[int64]*user_model.User, error) {
posterMaps := make(map[int64]*user_model.User, len(posterIDs))
left := len(posterIDs)
for left > 0 {
Expand Down Expand Up @@ -136,7 +134,7 @@ func (issues IssueList) getIssueIDs() []int64 {
return ids
}

func (issues IssueList) loadLabels(ctx context.Context) error {
func (issues IssueList) LoadLabels(ctx context.Context) error {
if len(issues) == 0 {
return nil
}
Expand Down Expand Up @@ -168,7 +166,7 @@ func (issues IssueList) loadLabels(ctx context.Context) error {
err = rows.Scan(&labelIssue)
if err != nil {
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadLabels: Close: %w", err1)
return fmt.Errorf("IssueList.LoadLabels: Close: %w", err1)
}
return err
}
Expand All @@ -177,14 +175,15 @@ func (issues IssueList) loadLabels(ctx context.Context) error {
// When there are no rows left and we try to close it.
// Since that is not relevant for us, we can safely ignore it.
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadLabels: Close: %w", err1)
return fmt.Errorf("IssueList.LoadLabels: Close: %w", err1)
}
left -= limit
issueIDs = issueIDs[limit:]
}

for _, issue := range issues {
issue.Labels = issueLabels[issue.ID]
issue.isLabelsLoaded = true
}
return nil
}
Expand All @@ -195,7 +194,7 @@ func (issues IssueList) getMilestoneIDs() []int64 {
})
}

func (issues IssueList) loadMilestones(ctx context.Context) error {
func (issues IssueList) LoadMilestones(ctx context.Context) error {
milestoneIDs := issues.getMilestoneIDs()
if len(milestoneIDs) == 0 {
return nil
Expand All @@ -220,6 +219,7 @@ func (issues IssueList) loadMilestones(ctx context.Context) error {

for _, issue := range issues {
issue.Milestone = milestoneMaps[issue.MilestoneID]
issue.isMilestoneLoaded = true
}
return nil
}
Expand Down Expand Up @@ -263,7 +263,7 @@ func (issues IssueList) LoadProjects(ctx context.Context) error {
return nil
}

func (issues IssueList) loadAssignees(ctx context.Context) error {
func (issues IssueList) LoadAssignees(ctx context.Context) error {
if len(issues) == 0 {
return nil
}
Expand Down Expand Up @@ -310,6 +310,10 @@ func (issues IssueList) loadAssignees(ctx context.Context) error {

for _, issue := range issues {
issue.Assignees = assignees[issue.ID]
if len(issue.Assignees) > 0 {
issue.Assignee = issue.Assignees[0]
}
issue.isAssigneeLoaded = true
}
return nil
}
Expand Down Expand Up @@ -413,6 +417,7 @@ func (issues IssueList) LoadAttachments(ctx context.Context) (err error) {

for _, issue := range issues {
issue.Attachments = attachments[issue.ID]
issue.isAttachmentsLoaded = true
}
return nil
}
Expand Down Expand Up @@ -538,23 +543,23 @@ func (issues IssueList) LoadAttributes(ctx context.Context) error {
return fmt.Errorf("issue.loadAttributes: LoadRepositories: %w", err)
}

if err := issues.loadPosters(ctx); err != nil {
return fmt.Errorf("issue.loadAttributes: loadPosters: %w", err)
if err := issues.LoadPosters(ctx); err != nil {
return fmt.Errorf("issue.loadAttributes: LoadPosters: %w", err)
}

if err := issues.loadLabels(ctx); err != nil {
return fmt.Errorf("issue.loadAttributes: loadLabels: %w", err)
if err := issues.LoadLabels(ctx); err != nil {
return fmt.Errorf("issue.loadAttributes: LoadLabels: %w", err)
}

if err := issues.loadMilestones(ctx); err != nil {
return fmt.Errorf("issue.loadAttributes: loadMilestones: %w", err)
if err := issues.LoadMilestones(ctx); err != nil {
return fmt.Errorf("issue.loadAttributes: LoadMilestones: %w", err)
}

if err := issues.LoadProjects(ctx); err != nil {
return fmt.Errorf("issue.loadAttributes: loadProjects: %w", err)
}

if err := issues.loadAssignees(ctx); err != nil {
if err := issues.LoadAssignees(ctx); err != nil {
return fmt.Errorf("issue.loadAttributes: loadAssignees: %w", err)
}

Expand Down
Loading