Skip to content

cache: Allow sharing compression variants among refs#2603

Merged
tonistiigi merged 1 commit into
moby:masterfrom
ktock:blobgc
Feb 11, 2022
Merged

cache: Allow sharing compression variants among refs#2603
tonistiigi merged 1 commit into
moby:masterfrom
ktock:blobgc

Conversation

@ktock
Copy link
Copy Markdown
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
Comment thread cache/refs.go
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
Copy Markdown
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
Copy Markdown
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
Comment thread cache/converter.go Outdated
}

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
Copy Markdown
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
Copy Markdown
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.

Comment thread 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
Copy Markdown
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
Copy Markdown
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.

Comment thread cache/remote.go Outdated
Comment on lines +306 to +308
defer func() {
if rerr == nil {
rerr = p.ref.addBlob(ctx, p.desc)
Copy link
Copy Markdown
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
Copy Markdown
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.

Comment thread cache/blobs.go
// 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
Copy Markdown
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
Copy Markdown
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.

Comment thread 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
Copy Markdown
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
Copy Markdown
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.

Comment thread cache/refs.go Outdated
if _, err := cs.Update(ctx, vInfo, fieldsFromLabels(vInfo.Labels)...); err != nil {
return err
}
sr.queueSize(sizeUnknown) // let the future call to size() recalcultate the new size
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

Comment thread 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)
Copy link
Copy Markdown
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
Copy Markdown
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.

Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
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