aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLunny Xiao <xiaolunwen@gmail.com>2025-03-20 10:46:18 -0700
committerGitHub <noreply@github.com>2025-03-20 10:46:18 -0700
commita4df01b5802c62863cdf26dc747e20617d847d89 (patch)
tree3d9d17ffc6040036589e37c879c6afcc74fbc0fa
parentef0970506f360977fbd74afe1952e246783da2b4 (diff)
downloadgitea-a4df01b5802c62863cdf26dc747e20617d847d89.tar.gz
gitea-a4df01b5802c62863cdf26dc747e20617d847d89.zip
Optimize total count of feed when loading activities in user dashboard. (#33841)
Two SQLs are very slow when `action` table have over 5M records. ``` database duration=1.8881s db.sql="SELECT created_unix DIV 900 * 900 AS timestamp, count(user_id) as contributions FROM `action` WHERE user_id=? AND act_user_id=? AND (created_unix > ?) GROUP BY timestamp ORDER BY timestamp" database duration=1.5408s db.sql="SELECT count(*) FROM `action` WHERE (user_id = ?) AND (is_deleted = ?)" ``` This will cache the count for the first loading or when the activities changed.
-rw-r--r--models/activities/action.go1
-rw-r--r--models/activities/action_list.go24
-rw-r--r--routers/web/user/home.go2
-rw-r--r--services/feed/feed.go26
4 files changed, 46 insertions, 7 deletions
diff --git a/models/activities/action.go b/models/activities/action.go
index 823af8fdcf..c89ba3e14e 100644
--- a/models/activities/action.go
+++ b/models/activities/action.go
@@ -445,6 +445,7 @@ type GetFeedsOptions struct {
OnlyPerformedBy bool // only actions performed by requested user
IncludeDeleted bool // include deleted actions
Date string // the day we want activity for: YYYY-MM-DD
+ DontCount bool // do counting in GetFeeds
}
// ActivityReadable return whether doer can read activities of user
diff --git a/models/activities/action_list.go b/models/activities/action_list.go
index f7ea48f03e..6789ebcb99 100644
--- a/models/activities/action_list.go
+++ b/models/activities/action_list.go
@@ -243,7 +243,11 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
sess := db.GetEngine(ctx).Where(cond)
sess = db.SetSessionPagination(sess, &opts)
- count, err = sess.Desc("`action`.created_unix").FindAndCount(&actions)
+ if opts.DontCount {
+ err = sess.Desc("`action`.created_unix").Find(&actions)
+ } else {
+ count, err = sess.Desc("`action`.created_unix").FindAndCount(&actions)
+ }
if err != nil {
return nil, 0, fmt.Errorf("FindAndCount: %w", err)
}
@@ -257,11 +261,13 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
return nil, 0, fmt.Errorf("Find(actionsIDs): %w", err)
}
- count, err = db.GetEngine(ctx).Where(cond).
- Table("action").
- Cols("`action`.id").Count()
- if err != nil {
- return nil, 0, fmt.Errorf("Count: %w", err)
+ if !opts.DontCount {
+ count, err = db.GetEngine(ctx).Where(cond).
+ Table("action").
+ Cols("`action`.id").Count()
+ if err != nil {
+ return nil, 0, fmt.Errorf("Count: %w", err)
+ }
}
if err := db.GetEngine(ctx).In("`action`.id", actionIDs).Desc("`action`.created_unix").Find(&actions); err != nil {
@@ -275,3 +281,9 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
return actions, count, nil
}
+
+func CountUserFeeds(ctx context.Context, userID int64) (int64, error) {
+ return db.GetEngine(ctx).Where("user_id = ?", userID).
+ And("is_deleted = ?", false).
+ Count(&Action{})
+}
diff --git a/routers/web/user/home.go b/routers/web/user/home.go
index 8e030a62a2..3973aea742 100644
--- a/routers/web/user/home.go
+++ b/routers/web/user/home.go
@@ -119,7 +119,7 @@ func Dashboard(ctx *context.Context) {
ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data)
}
- feeds, count, err := feed_service.GetFeeds(ctx, activities_model.GetFeedsOptions{
+ feeds, count, err := feed_service.GetFeedsForDashboard(ctx, activities_model.GetFeedsOptions{
RequestedUser: ctxUser,
RequestedTeam: ctx.Org.Team,
Actor: ctx.Doer,
diff --git a/services/feed/feed.go b/services/feed/feed.go
index a1c327fb51..41a918f00e 100644
--- a/services/feed/feed.go
+++ b/services/feed/feed.go
@@ -13,9 +13,28 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/setting"
)
+func userFeedCacheKey(userID int64) string {
+ return fmt.Sprintf("user_feed_%d", userID)
+}
+
+func GetFeedsForDashboard(ctx context.Context, opts activities_model.GetFeedsOptions) (activities_model.ActionList, int64, error) {
+ opts.DontCount = opts.RequestedTeam == nil && opts.Date == ""
+ results, cnt, err := activities_model.GetFeeds(ctx, opts)
+ if err != nil {
+ return nil, 0, err
+ }
+ if opts.DontCount {
+ cnt, err = cache.GetInt64(userFeedCacheKey(opts.Actor.ID), func() (int64, error) {
+ return activities_model.CountUserFeeds(ctx, opts.Actor.ID)
+ })
+ }
+ return results, cnt, err
+}
+
// GetFeeds returns actions according to the provided options
func GetFeeds(ctx context.Context, opts activities_model.GetFeedsOptions) (activities_model.ActionList, int64, error) {
return activities_model.GetFeeds(ctx, opts)
@@ -68,6 +87,13 @@ func notifyWatchers(ctx context.Context, act *activities_model.Action, watchers
if err := db.Insert(ctx, act); err != nil {
return fmt.Errorf("insert new action: %w", err)
}
+
+ total, err := activities_model.CountUserFeeds(ctx, act.UserID)
+ if err != nil {
+ return fmt.Errorf("count user feeds: %w", err)
+ }
+
+ _ = cache.GetCache().Put(userFeedCacheKey(act.UserID), fmt.Sprintf("%d", total), setting.CacheService.TTLSeconds())
}
return nil