-
Notifications
You must be signed in to change notification settings - Fork 112
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
Export cache image and app image in parallel #1167
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ import ( | |
"os" | ||
"path/filepath" | ||
"strconv" | ||
"sync" | ||
"time" | ||
|
||
"github.com/BurntSushi/toml" | ||
|
@@ -69,6 +70,7 @@ func (e *exportCmd) DefineFlags() { | |
cli.FlagLaunchCacheDir(&e.LaunchCacheDir) | ||
cli.FlagLauncherPath(&e.LauncherPath) | ||
cli.FlagLayersDir(&e.LayersDir) | ||
cli.FlagParallelExport(&e.ParallelExport) | ||
cli.FlagProcessType(&e.DefaultProcessType) | ||
cli.FlagProjectMetadataPath(&e.ProjectMetadataPath) | ||
cli.FlagReportPath(&e.ReportPath) | ||
|
@@ -205,31 +207,52 @@ func (e *exportCmd) export(group buildpack.Group, cacheStore lifecycle.Cache, an | |
return err | ||
} | ||
|
||
report, err := exporter.Export(lifecycle.ExportOptions{ | ||
AdditionalNames: e.AdditionalTags, | ||
AppDir: e.AppDir, | ||
DefaultProcessType: e.DefaultProcessType, | ||
ExtendedDir: e.ExtendedDir, | ||
LauncherConfig: launcherConfig(e.LauncherPath, e.LauncherSBOMDir), | ||
LayersDir: e.LayersDir, | ||
OrigMetadata: analyzedMD.LayersMetadata, | ||
Project: projectMD, | ||
RunImageRef: runImageID, | ||
RunImageForExport: runImageForExport, | ||
WorkingImage: appImage, | ||
}) | ||
if err != nil { | ||
return cmd.FailErrCode(err, e.CodeFor(platform.ExportError), "export") | ||
var exportWaitGroup sync.WaitGroup | ||
var report files.Report | ||
var appErr error | ||
appErr = nil | ||
|
||
exportWaitGroup.Add(1) | ||
go func() { | ||
defer exportWaitGroup.Done() | ||
report, appErr = exporter.Export(lifecycle.ExportOptions{ | ||
AdditionalNames: e.AdditionalTags, | ||
AppDir: e.AppDir, | ||
DefaultProcessType: e.DefaultProcessType, | ||
ExtendedDir: e.ExtendedDir, | ||
LauncherConfig: launcherConfig(e.LauncherPath, e.LauncherSBOMDir), | ||
LayersDir: e.LayersDir, | ||
OrigMetadata: analyzedMD.LayersMetadata, | ||
Project: projectMD, | ||
RunImageRef: runImageID, | ||
RunImageForExport: runImageForExport, | ||
WorkingImage: appImage, | ||
}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we move There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense @natalieparellano - working on this change. 1 qq -> when parallel export is enabled and when go routine to export app image fails, should we cancel go routine to export cache image or wait for it to complete, wdyt? |
||
}() | ||
|
||
// waiting here if parallel export is not enabled | ||
if !e.ParallelExport { | ||
exportWaitGroup.Wait() | ||
} | ||
|
||
exportWaitGroup.Add(1) | ||
go func() { | ||
defer exportWaitGroup.Done() | ||
if cacheStore != nil { | ||
natalieparellano marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if cacheErr := exporter.Cache(e.LayersDir, cacheStore); cacheErr != nil { | ||
cmd.DefaultLogger.Warnf("Failed to export cache: %v\n", cacheErr) | ||
} | ||
} | ||
}() | ||
|
||
exportWaitGroup.Wait() | ||
if appErr != nil { | ||
return cmd.FailErrCode(appErr, e.CodeFor(platform.ExportError), "export") | ||
} | ||
if err = encoding.WriteTOML(e.ReportPath, &report); err != nil { | ||
return cmd.FailErrCode(err, e.CodeFor(platform.ExportError), "write export report") | ||
} | ||
|
||
if cacheStore != nil { | ||
if cacheErr := exporter.Cache(e.LayersDir, cacheStore); cacheErr != nil { | ||
cmd.DefaultLogger.Warnf("Failed to export cache: %v\n", cacheErr) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import ( | |
"os" | ||
"path/filepath" | ||
"strings" | ||
"sync" | ||
|
||
v1 "github.com/google/go-containerregistry/pkg/v1" | ||
|
||
|
@@ -27,7 +28,8 @@ type Factory struct { | |
UID, GID int // UID and GID are used to normalize layer entries | ||
Logger log.Logger | ||
|
||
tarHashes map[string]string // tarHases Stores hashes of layer tarballs for reuse between the export and cache steps. | ||
// tarHashes stores hashes of layer tarballs for reuse between the export and cache steps. | ||
tarHashes sync.Map | ||
} | ||
|
||
type Layer struct { | ||
|
@@ -39,15 +41,13 @@ type Layer struct { | |
|
||
func (f *Factory) writeLayer(id, createdBy string, addEntries func(tw *archive.NormalizingTarWriter) error) (layer Layer, err error) { | ||
tarPath := filepath.Join(f.ArtifactsDir, escape(id)+".tar") | ||
if f.tarHashes == nil { | ||
f.tarHashes = make(map[string]string) | ||
} | ||
if sha, ok := f.tarHashes[tarPath]; ok { | ||
f.Logger.Debugf("Reusing tarball for layer %q with SHA: %s\n", id, sha) | ||
if sha, ok := f.tarHashes.Load(tarPath); ok { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been thinking about it, and I think we could still have a race condition if this load returns
We could debate about the manner of the backoff but this seems preferable to potentially reading all the bits twice, especially for large layers which tend to take a lot of time anyway. @jabrown85 do you have any thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jabrown85 wdyt? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On second thought, do you think "exponential" backoff can cause it to be slower in some case? Ideally we need parallelism to make it faster and at least "exponential" backoff might be counter-productive in worst case and I think fixed time delay would be better here - how about 500ms or even 1 sec. wdyt? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think starting with what @natalieparellano seems reasonable - we can always circle back and adjust any delay timings as we get feedback and real world cases. |
||
shaString := sha.(string) | ||
f.Logger.Debugf("Reusing tarball for layer %q with SHA: %s\n", id, shaString) | ||
return Layer{ | ||
ID: id, | ||
TarPath: tarPath, | ||
Digest: sha, | ||
Digest: shaString, | ||
History: v1.History{CreatedBy: createdBy}, | ||
}, nil | ||
} | ||
|
@@ -69,7 +69,7 @@ func (f *Factory) writeLayer(id, createdBy string, addEntries func(tw *archive.N | |
return Layer{}, err | ||
} | ||
digest := lw.Digest() | ||
f.tarHashes[tarPath] = digest | ||
f.tarHashes.Store(tarPath, digest) | ||
return Layer{ | ||
ID: id, | ||
Digest: digest, | ||
|
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.
Could we put this validation in the
platform
package? Somewhere inResolveInputs
? Our eventual aim is to move all such validations there. That would also have the nice side effect of printing the warning whencmd/lifecycle/exporter
is invoked with this configuration.