Skip to content

Commit a2b0da3

Browse files
committed
Optimize refreshAccesses with cross-comparison to minimize DB operations
Implemented the FIXME to perform cross-comparison between existing and desired accesses, reducing deletions, updates, and insertions to the minimum necessary. This improves performance for repositories with many users by avoiding bulk delete-all and re-insert-all.
1 parent 2d36a0c commit a2b0da3

File tree

1 file changed

+60
-15
lines changed

1 file changed

+60
-15
lines changed

models/perm/access/access.go

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ func updateUserAccess(accessMap map[int64]*userAccess, user *user_model.User, mo
8484
}
8585
}
8686

87-
// FIXME: do cross-comparison so reduce deletions and additions to the minimum?
8887
func refreshAccesses(ctx context.Context, repo *repo_model.Repository, accessMap map[int64]*userAccess) (err error) {
8988
minMode := perm.AccessModeRead
9089
if err := repo.LoadOwner(ctx); err != nil {
@@ -97,30 +96,76 @@ func refreshAccesses(ctx context.Context, repo *repo_model.Repository, accessMap
9796
minMode = perm.AccessModeWrite
9897
}
9998

100-
newAccesses := make([]Access, 0, len(accessMap))
99+
// Query existing accesses for cross-comparison
100+
existingAccesses, err := db.Find[Access](ctx, &Access{RepoID: repo.ID})
101+
if err != nil {
102+
return fmt.Errorf("find existing accesses: %w", err)
103+
}
104+
existingMap := make(map[int64]perm.AccessMode, len(existingAccesses))
105+
for _, a := range existingAccesses {
106+
existingMap[a.UserID] = a.Mode
107+
}
108+
109+
var toDelete []int64
110+
var toInsert []Access
111+
var toUpdate []struct {
112+
UserID int64
113+
Mode perm.AccessMode
114+
}
115+
116+
// Determine changes
101117
for userID, ua := range accessMap {
102118
if ua.Mode < minMode && !ua.User.IsRestricted {
103-
continue
119+
// Should not have access
120+
if _, exists := existingMap[userID]; exists {
121+
toDelete = append(toDelete, userID)
122+
}
123+
} else {
124+
desiredMode := ua.Mode
125+
if existingMode, exists := existingMap[userID]; exists {
126+
if existingMode != desiredMode {
127+
toUpdate = append(toUpdate, struct {
128+
UserID int64
129+
Mode perm.AccessMode
130+
}{UserID: userID, Mode: desiredMode})
131+
}
132+
} else {
133+
toInsert = append(toInsert, Access{
134+
UserID: userID,
135+
RepoID: repo.ID,
136+
Mode: desiredMode,
137+
})
138+
}
104139
}
140+
delete(existingMap, userID)
141+
}
105142

106-
newAccesses = append(newAccesses, Access{
107-
UserID: userID,
108-
RepoID: repo.ID,
109-
Mode: ua.Mode,
110-
})
143+
// Remaining in existingMap should be deleted
144+
for userID := range existingMap {
145+
toDelete = append(toDelete, userID)
111146
}
112147

113-
// Delete old accesses and insert new ones for repository.
114-
if _, err = db.DeleteByBean(ctx, &Access{RepoID: repo.ID}); err != nil {
115-
return fmt.Errorf("delete old accesses: %w", err)
148+
// Execute deletions
149+
if len(toDelete) > 0 {
150+
if _, err = db.GetEngine(ctx).In("user_id", toDelete).And("repo_id = ?", repo.ID).Delete(&Access{}); err != nil {
151+
return fmt.Errorf("delete accesses: %w", err)
152+
}
116153
}
117-
if len(newAccesses) == 0 {
118-
return nil
154+
155+
// Execute updates
156+
for _, u := range toUpdate {
157+
if _, err = db.GetEngine(ctx).Where("user_id = ? AND repo_id = ?", u.UserID, repo.ID).Cols("mode").Update(&Access{Mode: u.Mode}); err != nil {
158+
return fmt.Errorf("update access for user %d: %w", u.UserID, err)
159+
}
119160
}
120161

121-
if err = db.Insert(ctx, newAccesses); err != nil {
122-
return fmt.Errorf("insert new accesses: %w", err)
162+
// Execute insertions
163+
if len(toInsert) > 0 {
164+
if err = db.Insert(ctx, toInsert); err != nil {
165+
return fmt.Errorf("insert new accesses: %w", err)
166+
}
123167
}
168+
124169
return nil
125170
}
126171

0 commit comments

Comments
 (0)