From 94502362dd78d2b855fb752dae5f8f04599437a2 Mon Sep 17 00:00:00 2001 From: Seongmin Lee Date: Sat, 18 Jul 2026 08:48:51 +0000 Subject: [PATCH] appview: deduplicate stars Similar to follow records from previous commit, deduplicate stars. Define a `deduped_stars` view to query deduplicated stars. Oldest star wins so refreshing stars won't put repository to trending list. Signed-off-by: Seongmin Lee --- appview/ingester.go | 3 +-- appview/db/db.go | 11 +++++++++++ appview/db/profile.go | 2 +- appview/db/repos.go | 2 +- appview/db/star.go | 45 ++++++++++++++++----------------------------- appview/models/star.go | 1 - appview/state/star.go | 6 +++--- 7 file(s) changed, 33 insertion(s)(+), 37 deletion(s)(-) diff --git a/appview/ingester.go b/appview/ingester.go --- a/appview/ingester.go +++ b/appview/ingester.go @@ -210,7 +210,6 @@ star := models.Star{ Did: did, - Rkey: e.Commit.RKey, Created: createdAt, } @@ -245,7 +244,7 @@ return fmt.Errorf("star record has empty subject union") } - err = db.UpsertStar(i.Db, star) + err = db.UpsertStar(i.Db, e.Commit.RKey, star) case jmodels.CommitOperationDelete: err = db.DeleteStarByRkey(i.Db, did, e.Commit.RKey) } diff --git a/appview/db/db.go b/appview/db/db.go --- a/appview/db/db.go +++ b/appview/db/db.go @@ -2465,6 +2465,17 @@ `) return err }) + + orm.RunMigration(conn, logger, "deduped-stars-view", func(tx *sql.Tx) error { + _, err := tx.Exec(` + create view deduped_stars as + select did, subject_type, subject, min(created) as created + from stars + group by did, subject; + `) + return err + }) + return &DB{ db, logger, diff --git a/appview/db/profile.go b/appview/db/profile.go --- a/appview/db/profile.go +++ b/appview/db/profile.go @@ -550,7 +550,7 @@ query = `select count(id) from repos where did = ?` args = append(args, did) case models.VanityStatStarCount: - query = `select count(s.at_uri) from stars s join repos r on s.subject = r.repo_did where s.subject_type = 'repo' and r.did = ?` + query = `select count(*) from deduped_stars s join repos r on s.subject = r.repo_did where s.subject_type = 'repo' and r.did = ?` args = append(args, did) case models.VanityStatNone: return 0, nil diff --git a/appview/db/repos.go b/appview/db/repos.go --- a/appview/db/repos.go +++ b/appview/db/repos.go @@ -277,7 +277,7 @@ // get star counts { starCountQuery := fmt.Sprintf( - `select subject, count(1) from stars where subject_type = 'repo' and subject in (%s) group by subject`, + `select subject, count(*) from deduped_stars where subject_type = 'repo' and subject in (%s) group by subject`, inClause, ) diff --git a/appview/db/star.go b/appview/db/star.go --- a/appview/db/star.go +++ b/appview/db/star.go @@ -13,7 +13,7 @@ "tangled.org/core/orm" ) -func UpsertStar(e Execer, star models.Star) error { +func UpsertStar(e Execer, rkey string, star models.Star) error { _, err := e.Exec( `insert into stars (did, rkey, subject_type, subject, created) values (?, ?, ?, ?, ?) @@ -22,7 +22,7 @@ subject = excluded.subject, created = excluded.created`, star.Did, - star.Rkey, + rkey, string(star.SubjectType), star.Subject, star.Created.Format(time.RFC3339), @@ -32,8 +32,8 @@ func GetStars(e Execer, subject string, page pagination.Page) ([]models.Star, error) { query := ` - select did, subject_type, subject, created, rkey - from stars + select did, subject_type, subject, created + from deduped_stars where subject = ? order by created desc limit ? offset ? @@ -48,7 +48,7 @@ for rows.Next() { var star models.Star var created string - if err := rows.Scan(&star.Did, &star.SubjectType, &star.Subject, &created, &star.Rkey); err != nil { + if err := rows.Scan(&star.Did, &star.SubjectType, &star.Subject, &created); err != nil { return nil, err } @@ -97,7 +97,7 @@ func GetStarCount(e Execer, subjectType models.StarSubjectType, subject string) (int, error) { stars := 0 err := e.QueryRow( - `select count(did) from stars where subject_type = ? and subject = ?`, + `select count(*) from deduped_stars where subject_type = ? and subject = ?`, string(subjectType), subject, ).Scan(&stars) if err != nil { @@ -185,8 +185,8 @@ } repoQuery := fmt.Sprintf( - `select did, subject_type, subject, created, rkey - from stars + `select did, subject_type, subject, created + from deduped_stars %s order by created desc %s`, @@ -203,7 +203,7 @@ for rows.Next() { var star models.Star var created string - err := rows.Scan(&star.Did, &star.SubjectType, &star.Subject, &created, &star.Rkey) + err := rows.Scan(&star.Did, &star.SubjectType, &star.Subject, &created) if err != nil { return nil, err } @@ -271,7 +271,7 @@ whereClause = " where " + strings.Join(conditions, " and ") } - repoQuery := fmt.Sprintf(`select count(1) from stars %s`, whereClause) + repoQuery := fmt.Sprintf(`select count(*) from deduped_stars %s`, whereClause) var count int64 if err := e.QueryRow(repoQuery, args...).Scan(&count); err != nil { return 0, err @@ -284,25 +284,12 @@ func GetTopStarredReposLastWeek(e Execer) ([]models.Repo, error) { // first, get the top repo DIDs by star count from the last week query := ` - with recent_starred_repos as ( - select distinct subject - from stars - where created >= datetime('now', '-7 days') - and subject_type = 'repo' - ), - repo_star_counts as ( - select - s.subject, - count(*) as stars_gained_last_week - from stars s - join recent_starred_repos rsr on s.subject = rsr.subject - where s.created >= datetime('now', '-7 days') - and s.subject_type = 'repo' - group by s.subject - ) - select rsc.subject - from repo_star_counts rsc - order by rsc.stars_gained_last_week desc + select subject + from deduped_stars + where subject_type = 'repo' + and created >= datetime('now', '-7 days') + group by subject + order by count(*) desc limit 5 ` diff --git a/appview/models/star.go b/appview/models/star.go --- a/appview/models/star.go +++ b/appview/models/star.go @@ -16,7 +16,6 @@ SubjectType StarSubjectType Subject string Created time.Time - Rkey string } // RepoStar is used for reverse mapping to repos diff --git a/appview/state/star.go b/appview/state/star.go --- a/appview/state/star.go +++ b/appview/state/star.go @@ -79,11 +79,11 @@ case http.MethodPost: star := models.Star{ Did: currentUser.Did, - Rkey: tid.TID(), SubjectType: subjectType, Subject: subjectKey, Created: time.Now(), } + rkey := tid.TID() tx, err := s.db.BeginTx(r.Context(), nil) if err != nil { @@ -92,7 +92,7 @@ } defer tx.Rollback() - if err := db.UpsertStar(tx, star); err != nil { + if err := db.UpsertStar(tx, rkey, star); err != nil { l.Error("failed to star", "err", err) return } @@ -100,7 +100,7 @@ resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ Collection: tangled.FeedStarNSID, Repo: currentUser.Did, - Rkey: star.Rkey, + Rkey: rkey, Record: &lexutil.LexiconTypeDecoder{ Val: &tangled.FeedStar{ CreatedAt: star.Created.Format(time.RFC3339), -- tangled.sh