Skip to content

Commit ce7062a

Browse files
authored
Cache last commit to accelerate the repository directory page visit (#10069)
* Cache last commit to accelerate the repository directory page visit * Default use default cache configuration * add tests for last commit cache * Simplify last commit cache * Revert Enabled back * Change the last commit cache default ttl to 8760h * Fix test
1 parent 046bb05 commit ce7062a

File tree

10 files changed

+273
-23
lines changed

10 files changed

+273
-23
lines changed

custom/conf/app.ini.sample

+13-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
4343
ENABLE_PUSH_CREATE_USER = false
4444
ENABLE_PUSH_CREATE_ORG = false
4545
; Comma separated list of globally disabled repo units. Allowed values: repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki
46-
DISABLED_REPO_UNITS =
46+
DISABLED_REPO_UNITS =
4747
; Comma separated list of default repo units. Allowed values: repo.code, repo.releases, repo.issues, repo.pulls, repo.wiki.
4848
; Note: Code and Releases can currently not be deactivated. If you specify default repo units you should still list them for future compatibility.
4949
; External wiki and issue tracker can't be enabled by default as it requires additional settings.
@@ -632,6 +632,8 @@ SENDMAIL_PATH = sendmail
632632
SENDMAIL_ARGS =
633633

634634
[cache]
635+
; if the cache enabled
636+
ENABLED = true
635637
; Either "memory", "redis", or "memcache", default is "memory"
636638
ADAPTER = memory
637639
; For "memory" only, GC interval in seconds, default is 60
@@ -644,6 +646,16 @@ HOST =
644646
; Setting it to 0 disables caching
645647
ITEM_TTL = 16h
646648

649+
; Last commit cache
650+
[cache.last_commit]
651+
; if the cache enabled
652+
ENABLED = true
653+
; Time to keep items in cache if not used, default is 8760 hours.
654+
; Setting it to 0 disables caching
655+
ITEM_TTL = 8760h
656+
; Only enable the cache when repository's commits count great than
657+
COMMITS_COUNT = 1000
658+
647659
[session]
648660
; Either "memory", "file", or "redis", default is "memory"
649661
PROVIDER = memory

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

+7
Original file line numberDiff line numberDiff line change
@@ -383,13 +383,20 @@ 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+
- `ITEM_TTL`: **8760h**: Time to keep items in cache if not used, Setting it to 0 disables caching.
398+
- `COMMITS_COUNT`: **1000**: Only enable the cache when repository's commits count great than.
399+
393400
## Session (`session`)
394401

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

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

+7
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,20 @@ 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+
- `ITEM_TTL`: **8760h**: 缓存项目失效时间,设置为 0 则禁用缓存。
163+
- `COMMITS_COUNT`: **1000**: 仅当仓库的提交数大于时才启用缓存。
164+
158165
## Session (`session`)
159166

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

integrations/repo_test.go

+63-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ package integrations
77
import (
88
"fmt"
99
"net/http"
10+
"path"
1011
"strings"
1112
"testing"
13+
"time"
1214

1315
"code.gitea.io/gitea/modules/setting"
1416

@@ -29,12 +31,71 @@ func TestViewRepo(t *testing.T) {
2931
session.MakeRequest(t, req, http.StatusNotFound)
3032
}
3133

32-
func TestViewRepo2(t *testing.T) {
34+
func testViewRepo(t *testing.T) {
3335
defer prepareTestEnv(t)()
3436

3537
req := NewRequest(t, "GET", "/user3/repo3")
3638
session := loginUser(t, "user2")
37-
session.MakeRequest(t, req, http.StatusOK)
39+
resp := session.MakeRequest(t, req, http.StatusOK)
40+
41+
htmlDoc := NewHTMLParser(t, resp.Body)
42+
files := htmlDoc.doc.Find("#repo-files-table > TBODY > TR")
43+
44+
type file struct {
45+
fileName string
46+
commitID string
47+
commitMsg string
48+
commitTime string
49+
}
50+
51+
var items []file
52+
53+
files.Each(func(i int, s *goquery.Selection) {
54+
tds := s.Find("td")
55+
var f file
56+
tds.Each(func(i int, s *goquery.Selection) {
57+
if i == 0 {
58+
f.fileName = strings.TrimSpace(s.Text())
59+
} else if i == 1 {
60+
a := s.Find("a")
61+
f.commitMsg = strings.TrimSpace(a.Text())
62+
l, _ := a.Attr("href")
63+
f.commitID = path.Base(l)
64+
}
65+
})
66+
67+
f.commitTime, _ = s.Find("span.time-since").Attr("title")
68+
items = append(items, f)
69+
})
70+
71+
assert.EqualValues(t, []file{
72+
{
73+
fileName: "doc",
74+
commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6",
75+
commitMsg: "init project",
76+
commitTime: time.Date(2017, time.June, 14, 13, 54, 21, 0, time.UTC).Format(time.RFC1123),
77+
},
78+
{
79+
fileName: "README.md",
80+
commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6",
81+
commitMsg: "init project",
82+
commitTime: time.Date(2017, time.June, 14, 13, 54, 21, 0, time.UTC).Format(time.RFC1123),
83+
},
84+
}, items)
85+
}
86+
87+
func TestViewRepo2(t *testing.T) {
88+
// no last commit cache
89+
testViewRepo(t)
90+
91+
// enable last commit cache for all repositories
92+
oldCommitsCount := setting.CacheService.LastCommit.CommitsCount
93+
setting.CacheService.LastCommit.CommitsCount = 0
94+
// first view will not hit the cache
95+
testViewRepo(t)
96+
// second view will hit the cache
97+
testViewRepo(t)
98+
setting.CacheService.LastCommit.CommitsCount = oldCommitsCount
3899
}
39100

40101
func TestViewRepo3(t *testing.T) {

modules/cache/cache.go

+17-9
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,28 @@ import (
1616
_ "gitea.com/macaron/cache/redis"
1717
)
1818

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

2131
// NewContext start cache service
2232
func NewContext() error {
23-
if setting.CacheService == nil || conn != nil {
24-
return nil
33+
var err error
34+
35+
if conn == nil && setting.CacheService.Enabled {
36+
if conn, err = newCache(setting.CacheService.Cache); err != nil {
37+
return err
38+
}
2539
}
2640

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-
})
3341
return err
3442
}
3543

modules/cache/last_commit.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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: conn,
34+
}
35+
}
36+
37+
// Get get the last commit information by commit id and entry path
38+
func (c LastCommitCache) Get(ref, entryPath string) (*object.Commit, error) {
39+
v := c.Cache.Get(fmt.Sprintf("last_commit:%s:%s:%s", c.repoPath, ref, entryPath))
40+
if vs, ok := v.(string); ok {
41+
log.Trace("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, vs)
42+
if commit, ok := c.commitCache[vs]; ok {
43+
log.Trace("LastCommitCache hit level 2: [%s:%s:%s]", ref, entryPath, vs)
44+
return commit, nil
45+
}
46+
id, err := c.repo.ConvertToSHA1(vs)
47+
if err != nil {
48+
return nil, err
49+
}
50+
commit, err := c.repo.GoGitRepo().CommitObject(id)
51+
if err != nil {
52+
return nil, err
53+
}
54+
c.commitCache[vs] = commit
55+
return commit, nil
56+
}
57+
return nil, nil
58+
}
59+
60+
// Put put the last commit id with commit and entry path
61+
func (c LastCommitCache) Put(ref, entryPath, commitID string) error {
62+
log.Trace("LastCommitCache save: [%s:%s:%s]", ref, entryPath, commitID)
63+
return c.Cache.Put(fmt.Sprintf("last_commit:%s:%s:%s", c.repoPath, ref, entryPath), commitID, c.ttl)
64+
}

modules/git/cache.go

+4-2
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

+44-1
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 {

0 commit comments

Comments
 (0)