nandi/gleanpublic Fork 0
4fa381d
Commits
Clone
git clone https://git.rickub.com/nandi/glean.git
git clone ssh://git@rickub.com/nandi/glean.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Refactor precomputation to use helperUnverified

Julien Robert committed 2026-06-05T23:23:36+02:00 Browse files
4fa381d parent: 9cacbcf
modified internal/cluster/precompute.go +23 -32
@@ -6,11 +6,6 @@ import (
66 "time"
77 )
88
9-type PrecomputedRec struct {
10- RecType string
11- Data string
12-}
13-
149 func (e *Engine) PrecomputeAllRecommendations(ctx context.Context) error {
1510 e.logger.Info("starting recommendation precomputation")
1611 start := time.Now()
@@ -20,6 +15,8 @@ func (e *Engine) PrecomputeAllRecommendations(ctx context.Context) error {
2015 return err
2116 }
2217
18+ e.logger.Info("precomputing recommendations for users", "count", len(users))
19+
2320 computed := 0
2421 for _, did := range users {
2522 select {
@@ -43,6 +40,8 @@ func (e *Engine) PrecomputeAllRecommendations(ctx context.Context) error {
4340 }
4441
4542 func (e *Engine) precomputeForUser(ctx context.Context, userDID string) error {
43+ start := time.Now()
44+
4645 feedRecs, err := e.ComputeFeedRecommendationsOnDemand(ctx, userDID, 10)
4746 if err != nil {
4847 return err
@@ -80,22 +79,17 @@ func (e *Engine) precomputeForUser(ctx context.Context, userDID string) error {
8079 }
8180 }
8281
83- for _, rec := range []PrecomputedRec{
84- {RecType: "feed", Data: mustJSON(feedRecs)},
85- {RecType: "article", Data: mustJSON(articleRecs)},
86- {RecType: "person", Data: mustJSON(peopleRecs)},
87- } {
88- if rec.Data == "null" {
89- continue
90- }
91- if _, err := e.db.ExecContext(ctx, `
92- INSERT INTO recs.precomputed_recommendations (user_did, rec_type, data, computed_at)
93- VALUES (?, ?, ?, CURRENT_TIMESTAMP)
94- ON CONFLICT(user_did, rec_type) DO UPDATE SET data = excluded.data, computed_at = excluded.computed_at
95- `, userDID, rec.RecType, rec.Data); err != nil {
96- e.logger.Warn("failed to store precomputed rec", "did", userDID, "type", rec.RecType, "error", err)
97- }
98- }
82+ e.storePrecomputed(ctx, userDID, "feed", mustJSON(feedRecs))
83+ e.storePrecomputed(ctx, userDID, "article", mustJSON(articleRecs))
84+ e.storePrecomputed(ctx, userDID, "person", mustJSON(peopleRecs))
85+
86+ e.logger.Debug("precomputed recommendations for user",
87+ "did", userDID,
88+ "feeds", len(feedRecs),
89+ "articles", len(articleRecs),
90+ "people", len(peopleRecs),
91+ "duration", time.Since(start),
92+ )
9993
10094 return nil
10195 }
@@ -134,18 +128,15 @@ func (e *Engine) getPrecomputed(ctx context.Context, userDID, recType string) (s
134128 return data, true
135129 }
136130
137-func (e *Engine) invalidatePrecomputed(ctx context.Context, userDID, recType string) {
138- _, _ = e.db.ExecContext(ctx, `
139- DELETE FROM recs.precomputed_recommendations
140- WHERE user_did = ? AND rec_type = ?
141- `, userDID, recType)
142-}
143-
144-func (e *Engine) recomputeForUser(ctx context.Context, userDID, recType string) {
145- e.invalidatePrecomputed(ctx, userDID, recType)
146- if err := e.precomputeForUser(ctx, userDID); err != nil {
147- e.logger.Warn("recompute failed for user", "did", userDID, "type", recType, "error", err)
131+func (e *Engine) storePrecomputed(ctx context.Context, userDID, recType string, data string) {
132+ if data == "null" {
133+ return
148134 }
135+ _, _ = e.db.ExecContext(ctx, `
136+ INSERT INTO recs.precomputed_recommendations (user_did, rec_type, data, computed_at)
137+ VALUES (?, ?, ?, CURRENT_TIMESTAMP)
138+ ON CONFLICT(user_did, rec_type) DO UPDATE SET data = excluded.data, computed_at = excluded.computed_at
139+ `, userDID, recType, data)
149140 }
150141
151142 func mustJSON(v any) string {
@@ -6,11 +6,6 @@ import (
6 "time"6 "time"
7 )7 )
8 8
9-type PrecomputedRec struct {
10- RecType string
11- Data string
12-}
13-
14 func (e *Engine) PrecomputeAllRecommendations(ctx context.Context) error {9 func (e *Engine) PrecomputeAllRecommendations(ctx context.Context) error {
15 e.logger.Info("starting recommendation precomputation")10 e.logger.Info("starting recommendation precomputation")
16 start := time.Now()11 start := time.Now()
@@ -20,6 +15,8 @@ func (e *Engine) PrecomputeAllRecommendations(ctx context.Context) error {
20 return err15 return err
21 }16 }
22 17
18+ e.logger.Info("precomputing recommendations for users", "count", len(users))
19+
23 computed := 020 computed := 0
24 for _, did := range users {21 for _, did := range users {
25 select {22 select {
@@ -43,6 +40,8 @@ func (e *Engine) PrecomputeAllRecommendations(ctx context.Context) error {
43 }40 }
44 41
45 func (e *Engine) precomputeForUser(ctx context.Context, userDID string) error {42 func (e *Engine) precomputeForUser(ctx context.Context, userDID string) error {
43+ start := time.Now()
44+
46 feedRecs, err := e.ComputeFeedRecommendationsOnDemand(ctx, userDID, 10)45 feedRecs, err := e.ComputeFeedRecommendationsOnDemand(ctx, userDID, 10)
47 if err != nil {46 if err != nil {
48 return err47 return err
@@ -80,22 +79,17 @@ func (e *Engine) precomputeForUser(ctx context.Context, userDID string) error {
80 }79 }
81 }80 }
82 81
83- for _, rec := range []PrecomputedRec{82+ e.storePrecomputed(ctx, userDID, "feed", mustJSON(feedRecs))
84- {RecType: "feed", Data: mustJSON(feedRecs)},83+ e.storePrecomputed(ctx, userDID, "article", mustJSON(articleRecs))
85- {RecType: "article", Data: mustJSON(articleRecs)},84+ e.storePrecomputed(ctx, userDID, "person", mustJSON(peopleRecs))
86- {RecType: "person", Data: mustJSON(peopleRecs)},85+
87- } {86+ e.logger.Debug("precomputed recommendations for user",
88- if rec.Data == "null" {87+ "did", userDID,
89- continue88+ "feeds", len(feedRecs),
90- }89+ "articles", len(articleRecs),
91- if _, err := e.db.ExecContext(ctx, `90+ "people", len(peopleRecs),
92- INSERT INTO recs.precomputed_recommendations (user_did, rec_type, data, computed_at)91+ "duration", time.Since(start),
93- VALUES (?, ?, ?, CURRENT_TIMESTAMP)92+ )
94- ON CONFLICT(user_did, rec_type) DO UPDATE SET data = excluded.data, computed_at = excluded.computed_at
95- `, userDID, rec.RecType, rec.Data); err != nil {
96- e.logger.Warn("failed to store precomputed rec", "did", userDID, "type", rec.RecType, "error", err)
97- }
98- }
99 93
100 return nil94 return nil
101 }95 }
@@ -134,18 +128,15 @@ func (e *Engine) getPrecomputed(ctx context.Context, userDID, recType string) (s
134 return data, true128 return data, true
135 }129 }
136 130
137-func (e *Engine) invalidatePrecomputed(ctx context.Context, userDID, recType string) {131+func (e *Engine) storePrecomputed(ctx context.Context, userDID, recType string, data string) {
138- _, _ = e.db.ExecContext(ctx, `132+ if data == "null" {
139- DELETE FROM recs.precomputed_recommendations133+ return
140- WHERE user_did = ? AND rec_type = ?
141- `, userDID, recType)
142-}
143-
144-func (e *Engine) recomputeForUser(ctx context.Context, userDID, recType string) {
145- e.invalidatePrecomputed(ctx, userDID, recType)
146- if err := e.precomputeForUser(ctx, userDID); err != nil {
147- e.logger.Warn("recompute failed for user", "did", userDID, "type", recType, "error", err)
148 }134 }
135+ _, _ = e.db.ExecContext(ctx, `
136+ INSERT INTO recs.precomputed_recommendations (user_did, rec_type, data, computed_at)
137+ VALUES (?, ?, ?, CURRENT_TIMESTAMP)
138+ ON CONFLICT(user_did, rec_type) DO UPDATE SET data = excluded.data, computed_at = excluded.computed_at
139+ `, userDID, recType, data)
149 }140 }
150 141
151 func mustJSON(v any) string {142 func mustJSON(v any) string {
modified internal/cluster/scoring.go +24 -5
@@ -47,15 +47,15 @@ type ArticleRecommendation struct {
4747 }
4848
4949 func (e *Engine) InvalidateFeedCache(userDID string) {
50- go e.recomputeForUser(context.Background(), userDID, "feed")
50+ go e.precomputeForUser(context.Background(), userDID)
5151 }
5252
5353 func (e *Engine) InvalidateArticleCache(userDID string) {
54- go e.recomputeForUser(context.Background(), userDID, "article")
54+ go e.precomputeForUser(context.Background(), userDID)
5555 }
5656
5757 func (e *Engine) InvalidatePeopleCache(userDID string) {
58- go e.recomputeForUser(context.Background(), userDID, "person")
58+ go e.precomputeForUser(context.Background(), userDID)
5959 }
6060
6161 // GetFeedRecommendations returns feed recommendations for a user. Users with
@@ -70,6 +70,9 @@ func (e *Engine) GetFeedRecommendations(ctx context.Context, userDID string, lim
7070 }
7171 }
7272
73+ start := time.Now()
74+ e.logger.Info("feed recommendations cache miss, computing on-demand", "did", userDID)
75+
7376 subCount := 0
7477 _ = e.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM articles.subscriptions WHERE user_did = ?`, userDID).Scan(&subCount)
7578
@@ -77,7 +80,10 @@ func (e *Engine) GetFeedRecommendations(ctx context.Context, userDID string, lim
7780 recs, err := e.ColdStartRecommendations(ctx, userDID, limit*2)
7881 if err == nil && len(recs) > 0 {
7982 normalizeFeedScores(recs)
80- return ApplyDiversity(recs, limit), nil
83+ result := ApplyDiversity(recs, limit)
84+ e.storePrecomputed(ctx, userDID, "feed", mustJSON(result))
85+ e.logger.Info("feed recommendations computed (cold-start)", "did", userDID, "count", len(result), "duration", time.Since(start))
86+ return result, nil
8187 }
8288 }
8389
@@ -87,7 +93,10 @@ func (e *Engine) GetFeedRecommendations(ctx context.Context, userDID string, lim
8793 }
8894
8995 normalizeFeedScores(recs)
90- return ApplyDiversity(recs, limit), nil
96+ result := ApplyDiversity(recs, limit)
97+ e.storePrecomputed(ctx, userDID, "feed", mustJSON(result))
98+ e.logger.Info("feed recommendations computed (on-demand)", "did", userDID, "count", len(result), "duration", time.Since(start))
99+ return result, nil
91100 }
92101
93102 // GetPeopleRecommendations returns similar users based on subscription overlap,
@@ -102,6 +111,9 @@ func (e *Engine) GetPeopleRecommendations(ctx context.Context, userDID string, l
102111 }
103112 }
104113
114+ start := time.Now()
115+ e.logger.Info("people recommendations cache miss, computing on-demand", "did", userDID)
116+
105117 half := max(limit/2, 1)
106118
107119 inNet, err := e.computePeopleByFollowStatus(ctx, userDID, true, half)
@@ -119,6 +131,8 @@ func (e *Engine) GetPeopleRecommendations(ctx context.Context, userDID string, l
119131 recs = append(recs, outNet...)
120132
121133 normalizePersonScores(recs)
134+ e.storePrecomputed(ctx, userDID, "person", mustJSON(recs))
135+ e.logger.Info("people recommendations computed (on-demand)", "did", userDID, "count", len(recs), "duration", time.Since(start))
122136 return recs, nil
123137 }
124138
@@ -134,11 +148,16 @@ func (e *Engine) GetArticleRecommendations(ctx context.Context, userDID string,
134148 }
135149 }
136150
151+ start := time.Now()
152+ e.logger.Info("article recommendations cache miss, computing on-demand", "did", userDID)
153+
137154 recs, err := e.ComputeArticleRecommendationsOnDemand(ctx, userDID, languages, limit)
138155 if err != nil {
139156 return nil, err
140157 }
141158 normalizeArticleScores(recs)
159+ e.storePrecomputed(ctx, userDID, "article", mustJSON(recs))
160+ e.logger.Info("article recommendations computed (on-demand)", "did", userDID, "count", len(recs), "duration", time.Since(start))
142161 return recs, nil
143162 }
144163
@@ -47,15 +47,15 @@ type ArticleRecommendation struct {
47 }47 }
48 48
49 func (e *Engine) InvalidateFeedCache(userDID string) {49 func (e *Engine) InvalidateFeedCache(userDID string) {
50- go e.recomputeForUser(context.Background(), userDID, "feed")50+ go e.precomputeForUser(context.Background(), userDID)
51 }51 }
52 52
53 func (e *Engine) InvalidateArticleCache(userDID string) {53 func (e *Engine) InvalidateArticleCache(userDID string) {
54- go e.recomputeForUser(context.Background(), userDID, "article")54+ go e.precomputeForUser(context.Background(), userDID)
55 }55 }
56 56
57 func (e *Engine) InvalidatePeopleCache(userDID string) {57 func (e *Engine) InvalidatePeopleCache(userDID string) {
58- go e.recomputeForUser(context.Background(), userDID, "person")58+ go e.precomputeForUser(context.Background(), userDID)
59 }59 }
60 60
61 // GetFeedRecommendations returns feed recommendations for a user. Users with61 // GetFeedRecommendations returns feed recommendations for a user. Users with
@@ -70,6 +70,9 @@ func (e *Engine) GetFeedRecommendations(ctx context.Context, userDID string, lim
70 }70 }
71 }71 }
72 72
73+ start := time.Now()
74+ e.logger.Info("feed recommendations cache miss, computing on-demand", "did", userDID)
75+
73 subCount := 076 subCount := 0
74 _ = e.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM articles.subscriptions WHERE user_did = ?`, userDID).Scan(&subCount)77 _ = e.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM articles.subscriptions WHERE user_did = ?`, userDID).Scan(&subCount)
75 78
@@ -77,7 +80,10 @@ func (e *Engine) GetFeedRecommendations(ctx context.Context, userDID string, lim
77 recs, err := e.ColdStartRecommendations(ctx, userDID, limit*2)80 recs, err := e.ColdStartRecommendations(ctx, userDID, limit*2)
78 if err == nil && len(recs) > 0 {81 if err == nil && len(recs) > 0 {
79 normalizeFeedScores(recs)82 normalizeFeedScores(recs)
80- return ApplyDiversity(recs, limit), nil83+ result := ApplyDiversity(recs, limit)
84+ e.storePrecomputed(ctx, userDID, "feed", mustJSON(result))
85+ e.logger.Info("feed recommendations computed (cold-start)", "did", userDID, "count", len(result), "duration", time.Since(start))
86+ return result, nil
81 }87 }
82 }88 }
83 89
@@ -87,7 +93,10 @@ func (e *Engine) GetFeedRecommendations(ctx context.Context, userDID string, lim
87 }93 }
88 94
89 normalizeFeedScores(recs)95 normalizeFeedScores(recs)
90- return ApplyDiversity(recs, limit), nil96+ result := ApplyDiversity(recs, limit)
97+ e.storePrecomputed(ctx, userDID, "feed", mustJSON(result))
98+ e.logger.Info("feed recommendations computed (on-demand)", "did", userDID, "count", len(result), "duration", time.Since(start))
99+ return result, nil
91 }100 }
92 101
93 // GetPeopleRecommendations returns similar users based on subscription overlap,102 // GetPeopleRecommendations returns similar users based on subscription overlap,
@@ -102,6 +111,9 @@ func (e *Engine) GetPeopleRecommendations(ctx context.Context, userDID string, l
102 }111 }
103 }112 }
104 113
114+ start := time.Now()
115+ e.logger.Info("people recommendations cache miss, computing on-demand", "did", userDID)
116+
105 half := max(limit/2, 1)117 half := max(limit/2, 1)
106 118
107 inNet, err := e.computePeopleByFollowStatus(ctx, userDID, true, half)119 inNet, err := e.computePeopleByFollowStatus(ctx, userDID, true, half)
@@ -119,6 +131,8 @@ func (e *Engine) GetPeopleRecommendations(ctx context.Context, userDID string, l
119 recs = append(recs, outNet...)131 recs = append(recs, outNet...)
120 132
121 normalizePersonScores(recs)133 normalizePersonScores(recs)
134+ e.storePrecomputed(ctx, userDID, "person", mustJSON(recs))
135+ e.logger.Info("people recommendations computed (on-demand)", "did", userDID, "count", len(recs), "duration", time.Since(start))
122 return recs, nil136 return recs, nil
123 }137 }
124 138
@@ -134,11 +148,16 @@ func (e *Engine) GetArticleRecommendations(ctx context.Context, userDID string,
134 }148 }
135 }149 }
136 150
151+ start := time.Now()
152+ e.logger.Info("article recommendations cache miss, computing on-demand", "did", userDID)
153+
137 recs, err := e.ComputeArticleRecommendationsOnDemand(ctx, userDID, languages, limit)154 recs, err := e.ComputeArticleRecommendationsOnDemand(ctx, userDID, languages, limit)
138 if err != nil {155 if err != nil {
139 return nil, err156 return nil, err
140 }157 }
141 normalizeArticleScores(recs)158 normalizeArticleScores(recs)
159+ e.storePrecomputed(ctx, userDID, "article", mustJSON(recs))
160+ e.logger.Info("article recommendations computed (on-demand)", "did", userDID, "count", len(recs), "duration", time.Since(start))
142 return recs, nil161 return recs, nil
143 }162 }
144 163