mount: refresh and evict hot dir cache (#8174)
* mount: refresh and evict hot dir cache * mount: guard dir update window and extend TTL * mount: reuse timestamp for cache mark * Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * mount: make dir cache tuning configurable * mount: dedupe dir update notices * mount: restore invalidate-all cache helper * mount: keep hot dir tuning constants * mount: centralize cache state reset * mount: mark refresh completion time * mount: allow disabling idle eviction --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
@@ -22,6 +22,19 @@ type InodeEntry struct {
|
||||
isDirectory bool
|
||||
isChildrenCached bool
|
||||
cachedExpiresTime time.Time
|
||||
lastAccess time.Time
|
||||
lastRefresh time.Time
|
||||
updateWindowStart time.Time
|
||||
updateCount int
|
||||
needsRefresh bool
|
||||
}
|
||||
|
||||
func (ie *InodeEntry) resetCacheState() {
|
||||
ie.isChildrenCached = false
|
||||
ie.cachedExpiresTime = time.Time{}
|
||||
ie.needsRefresh = false
|
||||
ie.updateCount = 0
|
||||
ie.updateWindowStart = time.Time{}
|
||||
}
|
||||
|
||||
func (ie *InodeEntry) removeOnePath(p util.FullPath) bool {
|
||||
@@ -51,7 +64,12 @@ func NewInodeToPath(root util.FullPath, ttlSec int) *InodeToPath {
|
||||
path2inode: make(map[util.FullPath]uint64),
|
||||
cacheMetaTtlSec: time.Second * time.Duration(ttlSec),
|
||||
}
|
||||
t.inode2path[1] = &InodeEntry{[]util.FullPath{root}, 1, true, false, time.Time{}}
|
||||
t.inode2path[1] = &InodeEntry{
|
||||
paths: []util.FullPath{root},
|
||||
nlookup: 1,
|
||||
isDirectory: true,
|
||||
lastAccess: time.Now(),
|
||||
}
|
||||
t.path2inode[root] = 1
|
||||
|
||||
return t
|
||||
@@ -94,9 +112,16 @@ func (i *InodeToPath) Lookup(path util.FullPath, unixTime int64, isDirectory boo
|
||||
}
|
||||
} else {
|
||||
if !isLookup {
|
||||
i.inode2path[inode] = &InodeEntry{[]util.FullPath{path}, 0, isDirectory, false, time.Time{}}
|
||||
i.inode2path[inode] = &InodeEntry{
|
||||
paths: []util.FullPath{path},
|
||||
isDirectory: isDirectory,
|
||||
}
|
||||
} else {
|
||||
i.inode2path[inode] = &InodeEntry{[]util.FullPath{path}, 1, isDirectory, false, time.Time{}}
|
||||
i.inode2path[inode] = &InodeEntry{
|
||||
paths: []util.FullPath{path},
|
||||
nlookup: 1,
|
||||
isDirectory: isDirectory,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,8 +188,14 @@ func (i *InodeToPath) MarkChildrenCached(fullpath util.FullPath) {
|
||||
return
|
||||
}
|
||||
path.isChildrenCached = true
|
||||
now := time.Now()
|
||||
path.lastAccess = now
|
||||
path.lastRefresh = now
|
||||
path.updateCount = 0
|
||||
path.needsRefresh = false
|
||||
path.updateWindowStart = time.Time{}
|
||||
if i.cacheMetaTtlSec > 0 {
|
||||
path.cachedExpiresTime = time.Now().Add(i.cacheMetaTtlSec)
|
||||
path.cachedExpiresTime = now.Add(i.cacheMetaTtlSec)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,12 +231,120 @@ func (i *InodeToPath) InvalidateAllChildrenCache() {
|
||||
defer i.Unlock()
|
||||
for _, entry := range i.inode2path {
|
||||
if entry.isDirectory && entry.isChildrenCached {
|
||||
entry.isChildrenCached = false
|
||||
entry.cachedExpiresTime = time.Time{}
|
||||
entry.resetCacheState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *InodeToPath) InvalidateChildrenCache(fullpath util.FullPath) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
inode, found := i.path2inode[fullpath]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
entry, found := i.inode2path[inode]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
entry.resetCacheState()
|
||||
}
|
||||
|
||||
func (i *InodeToPath) TouchDirectory(fullpath util.FullPath) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
inode, found := i.path2inode[fullpath]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
entry, found := i.inode2path[inode]
|
||||
if !found || !entry.isDirectory {
|
||||
return
|
||||
}
|
||||
entry.lastAccess = time.Now()
|
||||
}
|
||||
|
||||
func (i *InodeToPath) RecordDirectoryUpdate(fullpath util.FullPath, now time.Time, window time.Duration, threshold int) bool {
|
||||
if threshold <= 0 || window <= 0 {
|
||||
return false
|
||||
}
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
inode, found := i.path2inode[fullpath]
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
entry, found := i.inode2path[inode]
|
||||
if !found || !entry.isDirectory || !entry.isChildrenCached {
|
||||
return false
|
||||
}
|
||||
if entry.updateWindowStart.IsZero() || now.Sub(entry.updateWindowStart) > window {
|
||||
entry.updateWindowStart = now
|
||||
entry.updateCount = 0
|
||||
}
|
||||
entry.updateCount++
|
||||
if entry.updateCount >= threshold {
|
||||
entry.needsRefresh = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (i *InodeToPath) NeedsRefresh(fullpath util.FullPath) bool {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
inode, found := i.path2inode[fullpath]
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
entry, found := i.inode2path[inode]
|
||||
if !found || !entry.isDirectory {
|
||||
return false
|
||||
}
|
||||
return entry.isChildrenCached && entry.needsRefresh
|
||||
}
|
||||
|
||||
func (i *InodeToPath) MarkDirectoryRefreshed(fullpath util.FullPath, now time.Time) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
inode, found := i.path2inode[fullpath]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
entry, found := i.inode2path[inode]
|
||||
if !found || !entry.isDirectory {
|
||||
return
|
||||
}
|
||||
entry.lastRefresh = now
|
||||
entry.lastAccess = now
|
||||
entry.updateCount = 0
|
||||
entry.needsRefresh = false
|
||||
entry.updateWindowStart = time.Time{}
|
||||
if i.cacheMetaTtlSec > 0 {
|
||||
entry.cachedExpiresTime = now.Add(i.cacheMetaTtlSec)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *InodeToPath) CollectEvictableDirs(now time.Time, idle time.Duration) []util.FullPath {
|
||||
if idle <= 0 {
|
||||
return nil
|
||||
}
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
var dirs []util.FullPath
|
||||
for _, entry := range i.inode2path {
|
||||
if !entry.isDirectory || !entry.isChildrenCached {
|
||||
continue
|
||||
}
|
||||
if entry.lastAccess.IsZero() || now.Sub(entry.lastAccess) < idle {
|
||||
continue
|
||||
}
|
||||
entry.resetCacheState()
|
||||
dirs = append(dirs, entry.paths...)
|
||||
}
|
||||
return dirs
|
||||
}
|
||||
|
||||
func (i *InodeToPath) AddPath(inode uint64, path util.FullPath) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
@@ -217,10 +356,9 @@ func (i *InodeToPath) AddPath(inode uint64, path util.FullPath) {
|
||||
ie.nlookup++
|
||||
} else {
|
||||
i.inode2path[inode] = &InodeEntry{
|
||||
paths: []util.FullPath{path},
|
||||
nlookup: 1,
|
||||
isDirectory: false,
|
||||
isChildrenCached: false,
|
||||
paths: []util.FullPath{path},
|
||||
nlookup: 1,
|
||||
isDirectory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,7 +406,7 @@ func (i *InodeToPath) MovePath(sourcePath, targetPath util.FullPath) (sourceInod
|
||||
entry.paths[i] = targetPath
|
||||
}
|
||||
}
|
||||
entry.isChildrenCached = false
|
||||
entry.resetCacheState()
|
||||
} else {
|
||||
glog.Errorf("MovePath %s to %s: sourceInode %d not found", sourcePath, targetPath, sourceInode)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user