-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
cache: Allow sharing compression variants among refs #2603
Conversation
info.Labels[cachedVariantLabel] = desc.Digest.String() | ||
if _, err := cs.Update(ctx, info, "labels."+cachedVariantLabel); err != nil { | ||
vInfo = addBlobDescToInfo(desc, vInfo) | ||
if _, err := cs.Update(ctx, vInfo, fieldsFromLabels(vInfo.Labels)...); err != 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.
Doesn't this create loops that are never cleaned up because they keep references to each other?
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.
Thank you for the comment. Contents are cleaned up even if a loop of GC label is created when no lease reference them. Added a test to make sure that.
c85e633
to
c4a7d1a
Compare
cache/converter.go
Outdated
@@ -129,6 +130,7 @@ var bufioPool = sync.Pool{ | |||
} | |||
|
|||
func (c *conversion) convert(ctx context.Context, cs content.Store, desc ocispecs.Descriptor) (*ocispecs.Descriptor, error) { | |||
logrus.WithField("blob", desc).WithField("target", c.target).Debugf("converting blob to the target compression") |
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: I think there was an update recently to always use the bklog
package instead of logrus directly
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.
Thank you for the suggestion. Fixed to use bklog.
cache/refs.go
Outdated
return nil | ||
} | ||
if _, err := walkBlobVariantsOnly(ctx, cs, desc.Digest, func(desc ocispecs.Descriptor) bool { return f(desc) }, nil); err != nil { | ||
logrus.WithError(err).Warn("failed to get variant blob") |
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.
Why not also return the error? Or if we don't want to return an error from this func, should we just drop the error
return type?
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 to return an error.
cache/remote.go
Outdated
defer func() { | ||
if rerr == nil { | ||
rerr = p.ref.addBlob(ctx, p.desc) |
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.
Should this defer be after the check below for whether the ref is lazy or not?
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.
Thank you for the review. Moved the defer to after the check.
@@ -267,10 +267,15 @@ func computeBlobChain(ctx context.Context, sr *immutableRef, createIfNeeded bool | |||
|
|||
// setBlob associates a blob with the cache record. | |||
// A lease must be held for the blob when calling this function | |||
func (sr *immutableRef) setBlob(ctx context.Context, compressionType compression.Type, desc ocispecs.Descriptor) error { | |||
func (sr *immutableRef) setBlob(ctx context.Context, desc ocispecs.Descriptor) (rerr 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.
nit: while I get how it evolved this way, it's confusing that there are now separate setBlob
and addBlob
methods on *immutableRef
. If you think there's a viable way to combine the two into one that might help clarify the code going forward. But if that's going to require too much refactoring or anything, then maybe just a comment on the methods explaining the difference would help.
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.
Added a comment to the func and renamed it to linkBlob
which might be less confusing.
cache/refs.go
Outdated
} | ||
|
||
func (sr *immutableRef) addCompressionBlob(ctx context.Context, desc ocispecs.Descriptor, compressionType compression.Type) error { | ||
func (sr *immutableRef) addBlob(ctx context.Context, desc ocispecs.Descriptor) 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.
Should this method also set the ref's size
metadata to sizeUnknown
so that future calls to sr.size()
recalculate the new size from the addition of a new blob?
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.
Thank you for the suggestion. Fixed to set size
to sizeUnknown
.
cache/refs.go
Outdated
return err | ||
} | ||
sr.queueSize(sizeUnknown) // let the future call to size() recalcultate the new size |
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.
Thanks for adding this. In order for this metadata update to be safe against race conditions I think it also needs to be wrapped in the same flightcontrol group and key as is used in the cacheRecord.size()
method. The metadata update itself would have to be the same pattern as is used there too like:
cr.mu.Lock()
cr.queueSize(usage.Size)
if err := cr.commitMetadata(); err != nil {
cr.mu.Unlock()
return s, err
}
cr.mu.Unlock()
(We could also just add a shortcut cacheMetadata.setSize
method that skips the queue and gets rid of the need for the lock, whatever works)
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 metadata update itself would have to be the same pattern as is used there too
Thank you for the suggestion. Fixed it.
the same flightcontrol group and key as is used in the cacheRecord.size() method
Sorry, I'm not sure why do we need this. Size metadata seems already protected by cr.mu
?
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.
Sorry, I'm not sure why do we need this.
You're right, I was thinking about this wrong, the mutex is enough. Thanks for adding that.
cache/manager_test.go
Outdated
// Check if contents are cleaned up | ||
for _, d := range gotChain { | ||
_, err := co.cs.Info(ctx, d) | ||
require.Error(t, 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.
should probably also assert its ErrNotFound
specifically
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.
Thank you for the review. Fixed this.
Signed-off-by: Kohei Tokunaga <[email protected]>
Fixes: #2346
Currently, a compression variant of a ref is not visible from another. This leads to duplicated conversion as mentioned in #2346.
This commit allows refs sharing compression variants with each other.
When a ref converts a blob (compression blob A) to another compression (compression blob B), Blob A and B are linked together using GC label to make sure that all linked variants are released together (i.e. they aren't released even when refs lease only some of them). (Maybe in the future we can switch to the lease-based approach as discussed in #2347. But how can we manage the ref counter of the lease itself among refs in a simple way?)
When a ref exports a compression variant, it first walks all linked compression variant blobs and checks if there is a blob that can be reused without conversion. Conversion happens only when no blob can be reused.
latency of exporing layers
This runs two builds with two compression types (gzip and zstd). Both of the builds share the same lower layers.
A:
B:
Before the measurement, zstd-compressed B is stored to the registry.
${REGISTRY}/${IMG}
are stored to the content store.${REGISTRY}/${IMG}
in the lower layers) from the registry then convert it to gzip.${REGISTRY}/${IMG}
has already been cached during building A, so this build should reuse these blobs without duplicated conversions.The following shows the time to export layers during building gzip-version of B at the final step in the above.
This PR exports layers without conversion so it's faster than the master version.
OCI worker
${IMG}
containerd worker
${IMG}
master version of containerd worker's performance is too worse than OCI worker.
This is because of the lack of bufio writers for the content store.
This will be fixed by #2601.