Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

appview/db: avoid N+1 queries in follow/star status fetching

Use a bulk fetcher for both.

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.sh>

authored by

Anirudh Oppiliappan and committed by
Tangled
890e7863 04314f77

+162 -17
+67 -7
appview/db/follow.go
··· 229 229 } 230 230 } 231 231 232 - func GetFollowStatus(e Execer, userDid, subjectDid string) FollowStatus { 233 - if userDid == subjectDid { 234 - return IsSelf 235 - } else if _, err := GetFollow(e, userDid, subjectDid); err != nil { 236 - return IsNotFollowing 237 - } else { 238 - return IsFollowing 232 + func getFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]FollowStatus, error) { 233 + if len(subjectDids) == 0 || userDid == "" { 234 + return make(map[string]FollowStatus), nil 239 235 } 236 + 237 + result := make(map[string]FollowStatus) 238 + 239 + for _, subjectDid := range subjectDids { 240 + if userDid == subjectDid { 241 + result[subjectDid] = IsSelf 242 + } else { 243 + result[subjectDid] = IsNotFollowing 244 + } 245 + } 246 + 247 + var querySubjects []string 248 + for _, subjectDid := range subjectDids { 249 + if userDid != subjectDid { 250 + querySubjects = append(querySubjects, subjectDid) 251 + } 252 + } 253 + 254 + if len(querySubjects) == 0 { 255 + return result, nil 256 + } 257 + 258 + placeholders := make([]string, len(querySubjects)) 259 + args := make([]any, len(querySubjects)+1) 260 + args[0] = userDid 261 + 262 + for i, subjectDid := range querySubjects { 263 + placeholders[i] = "?" 264 + args[i+1] = subjectDid 265 + } 266 + 267 + query := fmt.Sprintf(` 268 + SELECT subject_did 269 + FROM follows 270 + WHERE user_did = ? AND subject_did IN (%s) 271 + `, strings.Join(placeholders, ",")) 272 + 273 + rows, err := e.Query(query, args...) 274 + if err != nil { 275 + return nil, err 276 + } 277 + defer rows.Close() 278 + 279 + for rows.Next() { 280 + var subjectDid string 281 + if err := rows.Scan(&subjectDid); err != nil { 282 + return nil, err 283 + } 284 + result[subjectDid] = IsFollowing 285 + } 286 + 287 + return result, nil 288 + } 289 + 290 + func GetFollowStatus(e Execer, userDid, subjectDid string) FollowStatus { 291 + statuses, err := getFollowStatuses(e, userDid, []string{subjectDid}) 292 + if err != nil { 293 + return IsNotFollowing 294 + } 295 + return statuses[subjectDid] 296 + } 297 + 298 + func GetFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]FollowStatus, error) { 299 + return getFollowStatuses(e, userDid, subjectDids) 240 300 }
+55 -5
appview/db/star.go
··· 94 94 return stars, nil 95 95 } 96 96 97 - func GetStarStatus(e Execer, userDid string, repoAt syntax.ATURI) bool { 98 - if _, err := GetStar(e, userDid, repoAt); err != nil { 99 - return false 100 - } else { 101 - return true 97 + // getStarStatuses returns a map of repo URIs to star status for a given user 98 + // This is an internal helper function to avoid N+1 queries 99 + func getStarStatuses(e Execer, userDid string, repoAts []syntax.ATURI) (map[string]bool, error) { 100 + if len(repoAts) == 0 || userDid == "" { 101 + return make(map[string]bool), nil 102 102 } 103 + 104 + placeholders := make([]string, len(repoAts)) 105 + args := make([]any, len(repoAts)+1) 106 + args[0] = userDid 107 + 108 + for i, repoAt := range repoAts { 109 + placeholders[i] = "?" 110 + args[i+1] = repoAt.String() 111 + } 112 + 113 + query := fmt.Sprintf(` 114 + SELECT repo_at 115 + FROM stars 116 + WHERE starred_by_did = ? AND repo_at IN (%s) 117 + `, strings.Join(placeholders, ",")) 118 + 119 + rows, err := e.Query(query, args...) 120 + if err != nil { 121 + return nil, err 122 + } 123 + defer rows.Close() 124 + 125 + result := make(map[string]bool) 126 + // Initialize all repos as not starred 127 + for _, repoAt := range repoAts { 128 + result[repoAt.String()] = false 129 + } 130 + 131 + // Mark starred repos as true 132 + for rows.Next() { 133 + var repoAt string 134 + if err := rows.Scan(&repoAt); err != nil { 135 + return nil, err 136 + } 137 + result[repoAt] = true 138 + } 139 + 140 + return result, nil 103 141 } 104 142 143 + func GetStarStatus(e Execer, userDid string, repoAt syntax.ATURI) bool { 144 + statuses, err := getStarStatuses(e, userDid, []syntax.ATURI{repoAt}) 145 + if err != nil { 146 + return false 147 + } 148 + return statuses[repoAt.String()] 149 + } 150 + 151 + // GetStarStatuses returns a map of repo URIs to star status for a given user 152 + func GetStarStatuses(e Execer, userDid string, repoAts []syntax.ATURI) (map[string]bool, error) { 153 + return getStarStatuses(e, userDid, repoAts) 154 + } 105 155 func GetStars(e Execer, limit int, filters ...filter) ([]Star, error) { 106 156 var conditions []string 107 157 var args []any
+40 -5
appview/db/timeline.go
··· 3 3 import ( 4 4 "sort" 5 5 "time" 6 + 7 + "github.com/bluesky-social/indigo/atproto/syntax" 6 8 ) 7 9 8 10 type TimelineEvent struct { ··· 32 30 func MakeTimeline(e Execer, limit int, loggedInUserDid string) ([]TimelineEvent, error) { 33 31 var events []TimelineEvent 34 32 35 - repos, err := getTimelineRepos(e, limit) 33 + repos, err := getTimelineRepos(e, limit, loggedInUserDid) 36 34 if err != nil { 37 35 return nil, err 38 36 } ··· 63 61 return events, nil 64 62 } 65 63 66 - func getTimelineRepos(e Execer, limit int) ([]TimelineEvent, error) { 64 + func getTimelineRepos(e Execer, limit int, loggedInUserDid string) ([]TimelineEvent, error) { 67 65 repos, err := GetRepos(e, limit) 68 66 if err != nil { 69 67 return nil, err ··· 90 88 uriToRepo[r.RepoAt().String()] = r 91 89 } 92 90 91 + var starStatuses map[string]bool 92 + if loggedInUserDid != "" { 93 + var repoAts []syntax.ATURI 94 + for _, r := range repos { 95 + repoAts = append(repoAts, r.RepoAt()) 96 + } 97 + var err error 98 + starStatuses, err = GetStarStatuses(e, loggedInUserDid, repoAts) 99 + if err != nil { 100 + return nil, err 101 + } 102 + } 103 + 93 104 var events []TimelineEvent 94 105 for _, r := range repos { 95 106 var source *Repo ··· 112 97 } 113 98 } 114 99 100 + var isStarred bool 101 + if starStatuses != nil { 102 + isStarred = starStatuses[r.RepoAt().String()] 103 + } 104 + 105 + var starCount int64 106 + if r.RepoStats != nil { 107 + starCount = int64(r.RepoStats.StarCount) 108 + } 109 + 115 110 events = append(events, TimelineEvent{ 116 - Repo: &r, 117 - EventAt: r.Created, 118 - Source: source, 111 + Repo: &r, 112 + EventAt: r.Created, 113 + Source: source, 114 + IsStarred: isStarred, 115 + StarCount: starCount, 119 116 }) 120 117 } 121 118 ··· 184 157 followStatMap, err := GetFollowerFollowingCounts(e, subjects) 185 158 if err != nil { 186 159 return nil, err 160 + } 161 + 162 + var followStatuses map[string]FollowStatus 163 + if loggedInUserDid != "" { 164 + followStatuses, err = GetFollowStatuses(e, loggedInUserDid, subjects) 165 + if err != nil { 166 + return nil, err 167 + } 187 168 } 188 169 189 170 var events []TimelineEvent