Skip to content

Commit 30e7727

Browse files
committed
Cache last commit to accelerate the repository directory page visit
1 parent 159732d commit 30e7727

File tree

9 files changed

+242
-20
lines changed

9 files changed

+242
-20
lines changed

custom/conf/app.ini.sample

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,24 @@ SENDMAIL_PATH = sendmail
632632
SENDMAIL_ARGS =
633633

634634
[cache]
635+
; if the cache enabled
636+
ENABLED = true
637+
; Either "memory", "redis", or "memcache", default is "memory"
638+
ADAPTER = memory
639+
; For "memory" only, GC interval in seconds, default is 60
640+
INTERVAL = 60
641+
; For "redis" and "memcache", connection host address
642+
; redis: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180
643+
; memcache: `127.0.0.1:11211`
644+
HOST =
645+
; Time to keep items in cache if not used, default is 16 hours.
646+
; Setting it to 0 disables caching
647+
ITEM_TTL = 16h
648+
649+
; Last commit cache
650+
[cache.last_commit]
651+
; if the cache enabled
652+
ENABLED = true
635653
; Either "memory", "redis", or "memcache", default is "memory"
636654
ADAPTER = memory
637655
; For "memory" only, GC interval in seconds, default is 60
@@ -643,6 +661,8 @@ HOST =
643661
; Time to keep items in cache if not used, default is 16 hours.
644662
; Setting it to 0 disables caching
645663
ITEM_TTL = 16h
664+
; Only enable the cache when repository's commits count great than
665+
COMMITS_COUNT = 1000
646666

647667
[session]
648668
; Either "memory", "file", or "redis", default is "memory"

docs/content/doc/advanced/config-cheat-sheet.en-us.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,13 +383,25 @@ relation to port exhaustion.
383383

384384
## Cache (`cache`)
385385

386+
- `ENABLED`: **true**: Enable the cache.
386387
- `ADAPTER`: **memory**: Cache engine adapter, either `memory`, `redis`, or `memcache`.
387388
- `INTERVAL`: **60**: Garbage Collection interval (sec), for memory cache only.
388389
- `HOST`: **\<empty\>**: Connection string for `redis` and `memcache`.
389390
- Redis: `network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180`
390391
- Memcache: `127.0.0.1:9090;127.0.0.1:9091`
391392
- `ITEM_TTL`: **16h**: Time to keep items in cache if not used, Setting it to 0 disables caching.
392393

394+
## Cache - LastCommitCache settings (`cache.last_commit`)
395+
396+
- `ENABLED`: **true**: Enable the cache.
397+
- `ADAPTER`: **memory**: Cache engine adapter, either `memory`, `redis`, or `memcache`.
398+
- `INTERVAL`: **60**: Garbage Collection interval (sec), for memory cache only.
399+
- `HOST`: **\<empty\>**: Connection string for `redis` and `memcache`.
400+
- Redis: `network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180`
401+
- Memcache: `127.0.0.1:9090;127.0.0.1:9091`
402+
- `ITEM_TTL`: **16h**: Time to keep items in cache if not used, Setting it to 0 disables caching.
403+
- `COMMITS_COUNT`: **1000**: Only enable the cache when repository's commits count great than.
404+
393405
## Session (`session`)
394406

395407
- `PROVIDER`: **memory**: Session engine provider \[memory, file, redis, mysql, couchbase, memcache, nodb, postgres\].

docs/content/doc/advanced/config-cheat-sheet.zh-cn.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,25 @@ menu:
148148

149149
## Cache (`cache`)
150150

151+
- `ENABLED`: **true**: 是否启用。
151152
- `ADAPTER`: **memory**: 缓存引擎,可以为 `memory`, `redis``memcache`
152153
- `INTERVAL`: **60**: 只对内存缓存有效,GC间隔,单位秒。
153154
- `HOST`: **\<empty\>**: 针对redis和memcache有效,主机地址和端口。
154155
- Redis: `network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180`
155156
- Memache: `127.0.0.1:9090;127.0.0.1:9091`
156157
- `ITEM_TTL`: **16h**: 缓存项目失效时间,设置为 0 则禁用缓存。
157158

159+
## Cache - LastCommitCache settings (`cache.last_commit`)
160+
161+
- `ENABLED`: **true**: 是否启用。
162+
- `ADAPTER`: **memory**: 缓存引擎,可以为 `memory`, `redis``memcache`
163+
- `INTERVAL`: **60**: 只对内存缓存有效,GC间隔,单位秒。
164+
- `HOST`: **\<empty\>**: 针对redis和memcache有效,主机地址和端口。
165+
- Redis: `network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180`
166+
- Memache: `127.0.0.1:9090;127.0.0.1:9091`
167+
- `ITEM_TTL`: **86400h**: 缓存项目失效时间,设置为 0 则禁用缓存。
168+
- `COMMITS_COUNT`: **1000**: 仅当仓库的提交数大于时才启用缓存。
169+
158170
## Session (`session`)
159171

160172
- `PROVIDER`: Session 内容存储方式,可选 `memory`, `file`, `redis``mysql`

modules/cache/cache.go

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,35 @@ import (
1616
_ "gitea.com/macaron/cache/redis"
1717
)
1818

19-
var conn mc.Cache
19+
var (
20+
conn mc.Cache
21+
lastCommitCache mc.Cache
22+
)
23+
24+
func newCache(cacheConfig setting.Cache) (mc.Cache, error) {
25+
return mc.NewCacher(cacheConfig.Adapter, mc.Options{
26+
Adapter: cacheConfig.Adapter,
27+
AdapterConfig: cacheConfig.Conn,
28+
Interval: cacheConfig.Interval,
29+
})
30+
}
2031

2132
// NewContext start cache service
2233
func NewContext() error {
23-
if setting.CacheService == nil || conn != nil {
24-
return nil
34+
var err error
35+
36+
if conn == nil && setting.CacheService.Enabled {
37+
if conn, err = newCache(setting.CacheService.Cache); err != nil {
38+
return err
39+
}
40+
}
41+
42+
if lastCommitCache == nil && setting.CacheService.LastCommit.Enabled {
43+
if lastCommitCache, err = newCache(setting.CacheService.LastCommit.Cache); err != nil {
44+
return err
45+
}
2546
}
2647

27-
var err error
28-
conn, err = mc.NewCacher(setting.CacheService.Adapter, mc.Options{
29-
Adapter: setting.CacheService.Adapter,
30-
AdapterConfig: setting.CacheService.Conn,
31-
Interval: setting.CacheService.Interval,
32-
})
3348
return err
3449
}
3550

modules/cache/last_commit.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cache
6+
7+
import (
8+
"fmt"
9+
10+
"code.gitea.io/gitea/modules/git"
11+
"code.gitea.io/gitea/modules/log"
12+
13+
mc "gitea.com/macaron/cache"
14+
"gopkg.in/src-d/go-git.v4/plumbing/object"
15+
)
16+
17+
// LastCommitCache represents a cache to store last commit
18+
type LastCommitCache struct {
19+
repoPath string
20+
ttl int64
21+
repo *git.Repository
22+
commitCache map[string]*object.Commit
23+
mc.Cache
24+
}
25+
26+
// NewLastCommitCache creates a new last commit cache for repo
27+
func NewLastCommitCache(repoPath string, gitRepo *git.Repository, ttl int64) *LastCommitCache {
28+
return &LastCommitCache{
29+
repoPath: repoPath,
30+
repo: gitRepo,
31+
commitCache: make(map[string]*object.Commit),
32+
ttl: ttl,
33+
Cache: lastCommitCache,
34+
}
35+
}
36+
37+
func (c LastCommitCache) Get(ref, entryPath string) (*object.Commit, error) {
38+
v := c.Cache.Get(fmt.Sprintf("%s:%s:%s", c.repoPath, ref, entryPath))
39+
if vs, ok := v.(string); ok {
40+
log.Trace("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, vs)
41+
if commit, ok := c.commitCache[vs]; ok {
42+
log.Trace("LastCommitCache hit level 2: [%s:%s:%s]", ref, entryPath, vs)
43+
return commit, nil
44+
}
45+
id, err := c.repo.ConvertToSHA1(vs)
46+
if err != nil {
47+
return nil, err
48+
}
49+
commit, err := c.repo.GoGitRepo().CommitObject(id)
50+
if err != nil {
51+
return nil, err
52+
}
53+
c.commitCache[vs] = commit
54+
return commit, nil
55+
}
56+
return nil, nil
57+
}
58+
59+
func (c LastCommitCache) Put(ref, entryPath, commitID string) error {
60+
log.Trace("LastCommitCache save: [%s:%s:%s]", ref, entryPath, commitID)
61+
return c.Cache.Put(fmt.Sprintf("%s:%s:%s", c.repoPath, ref, entryPath), commitID, c.ttl)
62+
}

modules/git/cache.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
package git
66

7+
import "gopkg.in/src-d/go-git.v4/plumbing/object"
8+
79
// LastCommitCache cache
810
type LastCommitCache interface {
9-
Get(repoPath, ref, entryPath string) (*Commit, error)
10-
Put(repoPath, ref, entryPath string, commit *Commit) error
11+
Get(ref, entryPath string) (*object.Commit, error)
12+
Put(ref, entryPath, commitID string) error
1113
}

modules/git/commit_info.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
package git
66

77
import (
8+
"path"
9+
810
"github.com/emirpasic/gods/trees/binaryheap"
911
"gopkg.in/src-d/go-git.v4/plumbing"
1012
"gopkg.in/src-d/go-git.v4/plumbing/object"
@@ -30,7 +32,29 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom
3032
return nil, nil, err
3133
}
3234

33-
revs, err := getLastCommitForPaths(c, treePath, entryPaths)
35+
var revs map[string]*object.Commit
36+
if cache != nil {
37+
var unHitPaths []string
38+
revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, cache)
39+
if err != nil {
40+
return nil, nil, err
41+
}
42+
if len(unHitPaths) > 0 {
43+
revs2, err := getLastCommitForPaths(c, treePath, unHitPaths)
44+
if err != nil {
45+
return nil, nil, err
46+
}
47+
48+
for k, v := range revs2 {
49+
if err := cache.Put(commit.ID.String(), path.Join(treePath, k), v.ID().String()); err != nil {
50+
return nil, nil, err
51+
}
52+
revs[k] = v
53+
}
54+
}
55+
} else {
56+
revs, err = getLastCommitForPaths(c, treePath, entryPaths)
57+
}
3458
if err != nil {
3559
return nil, nil, err
3660
}
@@ -127,6 +151,25 @@ func getFileHashes(c cgobject.CommitNode, treePath string, paths []string) (map[
127151
return hashes, nil
128152
}
129153

154+
func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cache LastCommitCache) (map[string]*object.Commit, []string, error) {
155+
var unHitEntryPaths []string
156+
var results = make(map[string]*object.Commit)
157+
for _, p := range paths {
158+
lastCommit, err := cache.Get(commitID, path.Join(treePath, p))
159+
if err != nil {
160+
return nil, nil, err
161+
}
162+
if lastCommit != nil {
163+
results[p] = lastCommit
164+
continue
165+
}
166+
167+
unHitEntryPaths = append(unHitEntryPaths, p)
168+
}
169+
170+
return results, unHitEntryPaths, nil
171+
}
172+
130173
func getLastCommitForPaths(c cgobject.CommitNode, treePath string, paths []string) (map[string]*object.Commit, error) {
131174
// We do a tree traversal with nodes sorted by commit time
132175
heap := binaryheap.NewWith(func(a, b interface{}) int {

modules/setting/cache.go

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,81 @@ import (
1313

1414
// Cache represents cache settings
1515
type Cache struct {
16+
Enabled bool
1617
Adapter string
1718
Interval int
1819
Conn string
19-
TTL time.Duration
20+
TTL time.Duration `ini:"ITEM_TTL"`
2021
}
2122

2223
var (
2324
// CacheService the global cache
24-
CacheService *Cache
25+
CacheService = struct {
26+
Cache
27+
28+
LastCommit struct {
29+
Cache
30+
CommitsCount int64
31+
} `ini:"cache.last_commit"`
32+
}{
33+
Cache: Cache{
34+
Enabled: true,
35+
Adapter: "memory",
36+
Interval: 60,
37+
TTL: 16 * time.Hour,
38+
},
39+
LastCommit: struct {
40+
Cache
41+
CommitsCount int64
42+
}{
43+
Cache: Cache{
44+
Enabled: true,
45+
Adapter: "memory",
46+
Interval: 60,
47+
TTL: 86400 * time.Hour,
48+
},
49+
CommitsCount: 1000,
50+
},
51+
}
2552
)
2653

2754
func newCacheService() {
2855
sec := Cfg.Section("cache")
29-
CacheService = &Cache{
30-
Adapter: sec.Key("ADAPTER").In("memory", []string{"memory", "redis", "memcache"}),
56+
if err := sec.MapTo(&CacheService); err != nil {
57+
log.Fatal("Failed to map Cache settings: %v", err)
3158
}
59+
60+
CacheService.Adapter = sec.Key("ADAPTER").In("memory", []string{"memory", "redis", "memcache"})
3261
switch CacheService.Adapter {
3362
case "memory":
34-
CacheService.Interval = sec.Key("INTERVAL").MustInt(60)
3563
case "redis", "memcache":
3664
CacheService.Conn = strings.Trim(sec.Key("HOST").String(), "\" ")
65+
case "": // disable cache
66+
CacheService.Enabled = false
3767
default:
3868
log.Fatal("Unknown cache adapter: %s", CacheService.Adapter)
3969
}
40-
CacheService.TTL = sec.Key("ITEM_TTL").MustDuration(16 * time.Hour)
4170

42-
log.Info("Cache Service Enabled")
71+
if CacheService.Enabled {
72+
log.Info("Cache Service Enabled")
73+
}
74+
75+
sec = Cfg.Section("cache.last_commit")
76+
77+
CacheService.LastCommit.Adapter = sec.Key("ADAPTER").In("memory", []string{"memory", "redis", "memcache"})
78+
switch CacheService.LastCommit.Adapter {
79+
case "memory":
80+
case "redis", "memcache":
81+
CacheService.LastCommit.Conn = strings.Trim(sec.Key("HOST").String(), "\" ")
82+
case "": // disable cache
83+
CacheService.LastCommit.Enabled = false
84+
default:
85+
log.Fatal("Unknown cache.last_commit adapter: %s", CacheService.LastCommit.Adapter)
86+
}
87+
88+
CacheService.LastCommit.CommitsCount = sec.Key("COMMITS_COUNT").MustInt64(1000)
89+
90+
if CacheService.LastCommit.Enabled {
91+
log.Info("Last Commit Cache Service Enabled")
92+
}
4393
}

routers/repo/view.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
"code.gitea.io/gitea/models"
1919
"code.gitea.io/gitea/modules/base"
20+
"code.gitea.io/gitea/modules/cache"
2021
"code.gitea.io/gitea/modules/charset"
2122
"code.gitea.io/gitea/modules/context"
2223
"code.gitea.io/gitea/modules/git"
@@ -49,8 +50,13 @@ func renderDirectory(ctx *context.Context, treeLink string) {
4950
}
5051
entries.CustomSort(base.NaturalSortLess)
5152

53+
var c git.LastCommitCache
54+
if setting.CacheService.LastCommit.Enabled && ctx.Repo.CommitsCount >= setting.CacheService.LastCommit.CommitsCount {
55+
c = cache.NewLastCommitCache(ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, int64(setting.CacheService.LastCommit.TTL.Seconds()))
56+
}
57+
5258
var latestCommit *git.Commit
53-
ctx.Data["Files"], latestCommit, err = entries.GetCommitsInfo(ctx.Repo.Commit, ctx.Repo.TreePath, nil)
59+
ctx.Data["Files"], latestCommit, err = entries.GetCommitsInfo(ctx.Repo.Commit, ctx.Repo.TreePath, c)
5460
if err != nil {
5561
ctx.ServerError("GetCommitsInfo", err)
5662
return

0 commit comments

Comments
 (0)