-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bazel remote cache: add eviction #6812
Conversation
@@ -38,6 +38,7 @@ filegroup( | |||
|
|||
go_image( | |||
name = "image", | |||
base = "@alpine-base//image", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
distroless doesn't have any utilities, I've spun up alpine and it has mount
etc.
experiment/nursery/main.go
Outdated
// remounts with 'strictatime,lazyatime' | ||
func remountWithLazyAtime(device, mountPoint string) error { | ||
_, err := commandLines([]string{ | ||
"mount", "-o", "remount,strictatime,lazytime", device, mountPoint, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
couldn't you use syscall.Mount
and pass syscall.MS_REMOUNT | syscall.MS_STRICTATIME | syscall.MS_LAZYTIME
as options?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well, I guess go doesn't know about lazytime yet. lxc/incus@7446f6b
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
though golang.org/x/sys/unix
has it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also have to parse and plumb through the type, mount
is a little more magical
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed this offline, shelling out smells a bit gross but syscall.Mount also appears to be a wrapper around mount(2) anyhow.
2961da7
to
66b4059
Compare
broader question: is this a better approach than just keeping track of access times via GETs in nursery itself? |
I thought about this, implementing this correctly and efficiently is tricky. We're write heavy and we don't want to block when scanning the cache for eviction. Remounting gets us efficient access time tracking for ~free. More specifically I'm planning to do eviction similar to the kubelet, IE periodically get disk usage and when it passes some threshold(s) scan the cache and start kicking things out. |
Also from offline discussion: the filesystem also gets us free persistence and concurrent write / read. Having Downsides: |
c2e46b3
to
93eae1f
Compare
a0e3048
to
7f9c610
Compare
5f9a6b0
to
d9d07a4
Compare
This should be ready for review. I've definitely left room in the API for the cache to track things internally in the future in which case we can remove the remount code. |
d9d07a4
to
8162bee
Compare
I've deployed this and created / cat-ed a test file, |
I've spoken to @verult on storage about this: ~ next quarter local volumes should be beta (alpha currently) and then we can in theory use PV mount options instead and delete the |
/test pull-test-infra-bazel-canary |
// PathToKey converts a path on disk to a key, assuming the path is actually | ||
// under DiskRoot() ... | ||
func (c *Cache) PathToKey(key string) string { | ||
return strings.TrimPrefix(key, c.diskRoot+string(os.PathSeparator)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should document that c.diskRoot
cannot have a trailing slash. Specifying the root dir as the diskRoot
could be a little strange too since it would need to be specified as "" instead of "/".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll probably just make the constructor handle this instead, good point though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the constructor now trims os.PathSeperator
experiment/nursery/main.go
Outdated
flag.Parse() | ||
logger := log.WithFields(log.Fields{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should pass this logger to functions like cacheHandler
and monitorDiskAndEvict
instead of using the default undecorated logger or adding the field component=nusery
in multiple places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been thinking about creating a custom formatter with the default field instead, there are lots of places the subpackages should log things that won't be decorated. We might want to do this in prow as well.
experiment/nursery/main.go
Outdated
if err != nil { | ||
logger.WithError(err).Error("Failed to remount with lazyatime!") | ||
} | ||
logger.Info("Remount complete") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only log this if we successfully remount?
Also, should remount failures be fatal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A) yes, fixing
B) probably not, the cache will still work, the eviction will just be worse
experiment/nursery/main.go
Outdated
@@ -142,3 +176,42 @@ func cacheHandler(cache *diskcache.Cache) http.Handler { | |||
} | |||
}) | |||
} | |||
|
|||
func monitorDiskAndEvict(cache *diskcache.Cache, interval time.Duration, minBlocksFree, minFilesFree float64) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider moving this to the diskcache
package and making it a method of Cache
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM
experiment/nursery/main.go
Outdated
blocksFree, filesFree := diskutil.GetDiskUsage(dir) | ||
// if we are past either threshold, evict until we are not | ||
if blocksFree < minBlocksFree || filesFree < minFilesFree { | ||
logger.WithFields(log.Fields{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be useful to log every tick instead of only when we actually do evictions. Then we could monitor the disk usage more easily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM
experiment/nursery/main.go
Outdated
// so we can pop entries until we have evicted enough | ||
files := cache.GetEntries() | ||
sort.Slice(files, func(i, j int) bool { | ||
return files[i].LastAccess.After(files[j].LastAccess) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you want Before
not After
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Originally I was popping from the end of the slice. Thanks!
cfd39ca
to
e810a9b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mostly lgtm, a few nit-picks
also what's the expected behavior when the eviction blows away? We probably want to get notified somehow? (though it should not happen?)
"files-free": filesFree, | ||
}) | ||
if len(files) < 1 { | ||
logger.Fatalf("Failed to find entries to evict!") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: nothing to format, just use logger.Fatal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
||
// MonitorDiskAndEvict loops monitoring the disk, evicting cache entries | ||
// when the disk passes either minPercentBlocksFree or minPercentFilesFree | ||
func (c *Cache) MonitorDiskAndEvict(interval time.Duration, minPercentBlocksFree, minPercentFilesFree float64) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe also add a test for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, how would that work? I'd need to fill up a disk 😬
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe you can make a fake diskutil?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think tick loops should be unit tested. This was originally in main.go partially due to that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed offline, adding metrics / monitoring is coming later, and the failure modes for this are such that it's not a real concern (jobs will continue to work, but perhaps slower).
I will add regression tests if anything does go wrong here.
entry, files = files[0], files[1:] | ||
err := c.Delete(c.PathToKey(entry.Path)) | ||
if err != nil { | ||
logger.WithError(err).Error("Error deleting entry") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: maybe also log path
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM, done
// GetDiskUsage wraps syscall.Statfs | ||
func GetDiskUsage(path string) (percentBlocksFree, percentFilesFree float64) { | ||
var stat syscall.Statfs_t | ||
syscall.Statfs(path, &stat) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done, passed up and checked at call sites / logged
experiment/nursery/main.go
Outdated
// NOTE: remount is a bit of a hack, unfortunately the kubernetes volumes | ||
// don't really support this and to cleanly track entry access times we | ||
// want to use a volume with lazyatime (and not noatime or relatime) | ||
// so that file access times *are* recorded but are lazily flushed to the disk |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you also link some doc about lazyatime
for future reference?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/done, also so/lazyatime/strictatime,lazytime/
experiment/nursery/main.go
Outdated
var minPercentBlocksFree = flag.Float64("min-percent-blocks-free", 10, | ||
"minimum percent of blocks free on --dir's disk before evicting entries") | ||
var minPercentFilesFree = flag.Float64("min-percent-files-free", 10, | ||
"minimum percent of blocks free on --dir's disk before evicting entries") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: s/blocks/files?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done 😕
forgot to push? |
heh I was just pushing |
var stat syscall.Statfs_t | ||
err = syscall.Statfs(path, &stat) | ||
if err != nil { | ||
return 0, 0, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0, 0, err
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:( fixed
64de7e9
to
c89cee3
Compare
might also worth adding more docs once we start to use it /lgtm |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: BenTheElder, krzyzacy The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
I will write thorough docs if/when this moves out of /hold cancel |
other than needing #6850 to fix the |
--remount
which will attempt to remount the storage device withstrictatime,lazyatime
which will have the filesystem track access times for entries with lazy flushing to diskext4
supportslazyatime
which should have better characteristics than just enablingatime
relatime
which is not very useful for us since it only updates at most once every 24 hours or when the file is modified, but access time tracking viaatime
is simple and durablealso vendors a small library for getting atime cross platform, go's stdlib leaves this as entirely system dependent even though most (all?) platforms support this.
xref: #6808
/area bazel
/area kinda-hacky 😬