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
This commit is contained in:
Lunny Xiao 2020-02-02 03:11:32 +08:00 committed by GitHub
parent 046bb05979
commit ce7062a422
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 273 additions and 23 deletions

View file

@ -16,20 +16,28 @@ import (
_ "gitea.com/macaron/cache/redis"
)
var conn mc.Cache
var (
conn mc.Cache
)
func newCache(cacheConfig setting.Cache) (mc.Cache, error) {
return mc.NewCacher(cacheConfig.Adapter, mc.Options{
Adapter: cacheConfig.Adapter,
AdapterConfig: cacheConfig.Conn,
Interval: cacheConfig.Interval,
})
}
// NewContext start cache service
func NewContext() error {
if setting.CacheService == nil || conn != nil {
return nil
var err error
if conn == nil && setting.CacheService.Enabled {
if conn, err = newCache(setting.CacheService.Cache); err != nil {
return err
}
}
var err error
conn, err = mc.NewCacher(setting.CacheService.Adapter, mc.Options{
Adapter: setting.CacheService.Adapter,
AdapterConfig: setting.CacheService.Conn,
Interval: setting.CacheService.Interval,
})
return err
}

64
modules/cache/last_commit.go vendored Normal file
View file

@ -0,0 +1,64 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cache
import (
"fmt"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
mc "gitea.com/macaron/cache"
"gopkg.in/src-d/go-git.v4/plumbing/object"
)
// LastCommitCache represents a cache to store last commit
type LastCommitCache struct {
repoPath string
ttl int64
repo *git.Repository
commitCache map[string]*object.Commit
mc.Cache
}
// NewLastCommitCache creates a new last commit cache for repo
func NewLastCommitCache(repoPath string, gitRepo *git.Repository, ttl int64) *LastCommitCache {
return &LastCommitCache{
repoPath: repoPath,
repo: gitRepo,
commitCache: make(map[string]*object.Commit),
ttl: ttl,
Cache: conn,
}
}
// Get get the last commit information by commit id and entry path
func (c LastCommitCache) Get(ref, entryPath string) (*object.Commit, error) {
v := c.Cache.Get(fmt.Sprintf("last_commit:%s:%s:%s", c.repoPath, ref, entryPath))
if vs, ok := v.(string); ok {
log.Trace("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, vs)
if commit, ok := c.commitCache[vs]; ok {
log.Trace("LastCommitCache hit level 2: [%s:%s:%s]", ref, entryPath, vs)
return commit, nil
}
id, err := c.repo.ConvertToSHA1(vs)
if err != nil {
return nil, err
}
commit, err := c.repo.GoGitRepo().CommitObject(id)
if err != nil {
return nil, err
}
c.commitCache[vs] = commit
return commit, nil
}
return nil, nil
}
// Put put the last commit id with commit and entry path
func (c LastCommitCache) Put(ref, entryPath, commitID string) error {
log.Trace("LastCommitCache save: [%s:%s:%s]", ref, entryPath, commitID)
return c.Cache.Put(fmt.Sprintf("last_commit:%s:%s:%s", c.repoPath, ref, entryPath), commitID, c.ttl)
}

View file

@ -4,8 +4,10 @@
package git
import "gopkg.in/src-d/go-git.v4/plumbing/object"
// LastCommitCache cache
type LastCommitCache interface {
Get(repoPath, ref, entryPath string) (*Commit, error)
Put(repoPath, ref, entryPath string, commit *Commit) error
Get(ref, entryPath string) (*object.Commit, error)
Put(ref, entryPath, commitID string) error
}

View file

@ -5,6 +5,8 @@
package git
import (
"path"
"github.com/emirpasic/gods/trees/binaryheap"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/object"
@ -30,7 +32,29 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom
return nil, nil, err
}
revs, err := getLastCommitForPaths(c, treePath, entryPaths)
var revs map[string]*object.Commit
if cache != nil {
var unHitPaths []string
revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, cache)
if err != nil {
return nil, nil, err
}
if len(unHitPaths) > 0 {
revs2, err := getLastCommitForPaths(c, treePath, unHitPaths)
if err != nil {
return nil, nil, err
}
for k, v := range revs2 {
if err := cache.Put(commit.ID.String(), path.Join(treePath, k), v.ID().String()); err != nil {
return nil, nil, err
}
revs[k] = v
}
}
} else {
revs, err = getLastCommitForPaths(c, treePath, entryPaths)
}
if err != nil {
return nil, nil, err
}
@ -127,6 +151,25 @@ func getFileHashes(c cgobject.CommitNode, treePath string, paths []string) (map[
return hashes, nil
}
func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cache LastCommitCache) (map[string]*object.Commit, []string, error) {
var unHitEntryPaths []string
var results = make(map[string]*object.Commit)
for _, p := range paths {
lastCommit, err := cache.Get(commitID, path.Join(treePath, p))
if err != nil {
return nil, nil, err
}
if lastCommit != nil {
results[p] = lastCommit
continue
}
unHitEntryPaths = append(unHitEntryPaths, p)
}
return results, unHitEntryPaths, nil
}
func getLastCommitForPaths(c cgobject.CommitNode, treePath string, paths []string) (map[string]*object.Commit, error) {
// We do a tree traversal with nodes sorted by commit time
heap := binaryheap.NewWith(func(a, b interface{}) int {

View file

@ -13,31 +13,71 @@ import (
// Cache represents cache settings
type Cache struct {
Enabled bool
Adapter string
Interval int
Conn string
TTL time.Duration
TTL time.Duration `ini:"ITEM_TTL"`
}
var (
// CacheService the global cache
CacheService *Cache
CacheService = struct {
Cache
LastCommit struct {
Enabled bool
TTL time.Duration `ini:"ITEM_TTL"`
CommitsCount int64
} `ini:"cache.last_commit"`
}{
Cache: Cache{
Enabled: true,
Adapter: "memory",
Interval: 60,
TTL: 16 * time.Hour,
},
LastCommit: struct {
Enabled bool
TTL time.Duration `ini:"ITEM_TTL"`
CommitsCount int64
}{
Enabled: true,
TTL: 8760 * time.Hour,
CommitsCount: 1000,
},
}
)
func newCacheService() {
sec := Cfg.Section("cache")
CacheService = &Cache{
Adapter: sec.Key("ADAPTER").In("memory", []string{"memory", "redis", "memcache"}),
if err := sec.MapTo(&CacheService); err != nil {
log.Fatal("Failed to map Cache settings: %v", err)
}
CacheService.Adapter = sec.Key("ADAPTER").In("memory", []string{"memory", "redis", "memcache"})
switch CacheService.Adapter {
case "memory":
CacheService.Interval = sec.Key("INTERVAL").MustInt(60)
case "redis", "memcache":
CacheService.Conn = strings.Trim(sec.Key("HOST").String(), "\" ")
case "": // disable cache
CacheService.Enabled = false
default:
log.Fatal("Unknown cache adapter: %s", CacheService.Adapter)
}
CacheService.TTL = sec.Key("ITEM_TTL").MustDuration(16 * time.Hour)
log.Info("Cache Service Enabled")
if CacheService.Enabled {
log.Info("Cache Service Enabled")
}
sec = Cfg.Section("cache.last_commit")
if !CacheService.Enabled {
CacheService.LastCommit.Enabled = false
}
CacheService.LastCommit.CommitsCount = sec.Key("COMMITS_COUNT").MustInt64(1000)
if CacheService.LastCommit.Enabled {
log.Info("Last Commit Cache Service Enabled")
}
}