Skip to content
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

Merged
merged 1 commit into from
Feb 11, 2022

Conversation

ktock
Copy link
Collaborator

@ktock ktock commented Feb 7, 2022

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:

FROM ${REGISTRY}/${IMG}
RUN echo hello > /hello

B:

FROM ${REGISTRY}/${IMG}
RUN echo hi > /hi

Before the measurement, zstd-compressed B is stored to the registry.

  • Steps: https://github.com/ktock/buildkit/blob/5b9997d2b45ce57ed8ad07969840069e1292a388/hack/blob/measure.sh#L51-L71
    • First, we build A as gzip then convert it to zstd.
      • Here, gzip and zstd variants of ${REGISTRY}/${IMG} are stored to the content store.
    • Next, we pull zstd-compressed B (contains zstd-version of ${REGISTRY}/${IMG} in the lower layers) from the registry then convert it to gzip.
      • Here, gzip-version of ${REGISTRY}/${IMG} has already been cached during building A, so this build should reuse these blobs without duplicated conversions.
      • This PR-version of BuildKit successfully reuses it but master version of BuildKit doesn't.

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} master PR
ubuntu:20.04 4.1s 0s
tomcat:10.0.0-jdk15-openjdk-buster 23.3s 0s

containerd worker

${IMG} master PR
ubuntu:20.04 25.5s 0s
tomcat:10.0.0-jdk15-openjdk-buster 190.2s 0.1s

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.

@ktock ktock marked this pull request as draft February 7, 2022 12:19
@ktock ktock marked this pull request as ready for review February 7, 2022 12:20
@ktock ktock marked this pull request as draft February 7, 2022 12:25
@ktock ktock marked this pull request as ready for review February 7, 2022 13:27
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 {
Copy link
Member

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?

Copy link
Collaborator Author

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.

@tonistiigi tonistiigi requested a review from sipsma February 9, 2022 00:43
@ktock ktock force-pushed the blobgc branch 2 times, most recently from c85e633 to c4a7d1a Compare February 9, 2022 14:31
@@ -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")
Copy link
Collaborator

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

Copy link
Collaborator Author

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")
Copy link
Collaborator

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?

Copy link
Collaborator Author

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
Comment on lines 306 to 308
defer func() {
if rerr == nil {
rerr = p.ref.addBlob(ctx, p.desc)
Copy link
Collaborator

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?

Copy link
Collaborator Author

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) {
Copy link
Collaborator

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.

Copy link
Collaborator Author

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 {
Copy link
Collaborator

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?

Copy link
Collaborator Author

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
Copy link
Collaborator

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)

Copy link
Collaborator Author

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?

Copy link
Collaborator

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.

// Check if contents are cleaned up
for _, d := range gotChain {
_, err := co.cs.Info(ctx, d)
require.Error(t, err)
Copy link
Collaborator

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

Copy link
Collaborator Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

possible race conditions in blobs conversion
3 participants