chore: optimize multi-user RSS feed generation by fixing N+1 query (#5749)

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
google-labs-jules[bot] 2026-03-20 18:09:24 +08:00 committed by GitHub
parent 23a7e99a21
commit e0cc247823
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 82 additions and 10 deletions

View File

@ -211,18 +211,24 @@ func (s *RSSService) generateRSSFromMemoList(ctx context.Context, memoList []*st
creatorMap[user.ID] = user
} else {
// Multi-user feed - batch load all unique creators
creatorIDs := make(map[int32]bool)
creatorIDList := []int32{}
creatorIDMap := make(map[int32]bool)
for _, memo := range memoList[:itemCountLimit] {
creatorIDs[memo.CreatorID] = true
if !creatorIDMap[memo.CreatorID] {
creatorIDList = append(creatorIDList, memo.CreatorID)
creatorIDMap[memo.CreatorID] = true
}
}
// Batch load all users with a single query by getting all users and filtering
// Note: This is more efficient than N separate queries
for creatorID := range creatorIDs {
creator, err := s.Store.GetUser(ctx, &store.FindUser{ID: &creatorID})
if err == nil && creator != nil {
creatorMap[creatorID] = creator
}
// Batch load all users with a single query
users, err := s.Store.ListUsers(ctx, &store.FindUser{
IDList: creatorIDList,
})
if err != nil {
return "", lastModified, err
}
for _, creator := range users {
creatorMap[creator.ID] = creator
}
}

View File

@ -91,6 +91,19 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User
if v := find.ID; v != nil {
where, args = append(where, "`id` = ?"), append(args, *v)
}
if len(find.IDList) > 0 {
placeholders := make([]string, 0, len(find.IDList))
for range find.IDList {
placeholders = append(placeholders, "?")
}
where, args = append(where, fmt.Sprintf("`id` IN (%s)", strings.Join(placeholders, ", "))), append(args, func() []any {
list := make([]any, 0, len(find.IDList))
for _, id := range find.IDList {
list = append(list, id)
}
return list
}()...)
}
if v := find.Username; v != nil {
where, args = append(where, "`username` = ?"), append(args, *v)
}

View File

@ -94,6 +94,14 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User
if v := find.ID; v != nil {
where, args = append(where, "id = "+placeholder(len(args)+1)), append(args, *v)
}
if len(find.IDList) > 0 {
holders := make([]string, 0, len(find.IDList))
for range find.IDList {
holders = append(holders, placeholder(len(args)+1))
args = append(args, find.IDList[len(holders)-1])
}
where = append(where, fmt.Sprintf("id IN (%s)", strings.Join(holders, ", ")))
}
if v := find.Username; v != nil {
where, args = append(where, "username = "+placeholder(len(args)+1)), append(args, *v)
}

View File

@ -95,6 +95,19 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User
if v := find.ID; v != nil {
where, args = append(where, "id = ?"), append(args, *v)
}
if len(find.IDList) > 0 {
placeholders := make([]string, 0, len(find.IDList))
for range find.IDList {
placeholders = append(placeholders, "?")
}
where, args = append(where, fmt.Sprintf("id IN (%s)", strings.Join(placeholders, ", "))), append(args, func() []any {
list := make([]any, 0, len(find.IDList))
for _, id := range find.IDList {
list = append(list, id)
}
return list
}()...)
}
if v := find.Username; v != nil {
where, args = append(where, "username = ?"), append(args, *v)
}

View File

@ -40,6 +40,36 @@ func TestUserStore(t *testing.T) {
ts.Close()
}
func TestUserListByIDList(t *testing.T) {
t.Parallel()
ctx := context.Background()
ts := NewTestingStore(ctx, t)
// Create 5 users
var userIDs []int32
for i := 0; i < 5; i++ {
user, err := createTestingUserWithRole(ctx, ts, fmt.Sprintf("user_list_%d", i), store.RoleUser)
require.NoError(t, err)
userIDs = append(userIDs, user.ID)
}
// List users by IDList (3 out of 5)
targetIDs := userIDs[1:4]
users, err := ts.ListUsers(ctx, &store.FindUser{IDList: targetIDs})
require.NoError(t, err)
require.Equal(t, 3, len(users))
foundIDs := make(map[int32]bool)
for _, u := range users {
foundIDs[u.ID] = true
}
for _, id := range targetIDs {
require.True(t, foundIDs[id])
}
ts.Close()
}
func TestUserGetByID(t *testing.T) {
t.Parallel()
ctx := context.Background()

View File

@ -71,7 +71,9 @@ type UpdateUser struct {
}
type FindUser struct {
ID *int32
ID *int32
IDList []int32
RowStatus *RowStatus
Username *string
Role *Role