Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 79 additions & 3 deletions local/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ func (s *Store) doSave(img v1.Image, withName string) (image.InspectResponse, er
}

func (s *Store) addImageToTar(tw *tar.Writer, image v1.Image, withName string) error {
// Check if we can optimize by using known layer sizes
canOptimize := s.canOptimizeLayerSizes(image)

rawConfigFile, err := image.RawConfigFile()
if err != nil {
return err
Expand All @@ -217,7 +220,12 @@ func (s *Store) addImageToTar(tw *tar.Writer, image v1.Image, withName string) e
blankIdx int
)
for _, layer := range layers {
layerName, err := s.addLayerToTar(tw, layer, blankIdx)
var layerName string
if canOptimize {
layerName, err = s.addLayerToTarOptimized(tw, layer, blankIdx)
} else {
layerName, err = s.addLayerToTar(tw, layer, blankIdx)
}
if err != nil {
return err
}
Expand All @@ -238,6 +246,72 @@ func (s *Store) addImageToTar(tw *tar.Writer, image v1.Image, withName string) e
return addTextToTar(tw, manifestJSON, "manifest.json")
}

func (s *Store) canOptimizeLayerSizes(image v1.Image) bool {
layers, err := image.Layers()
if err != nil {
return false
}

// Check if all layers have known uncompressed sizes (i.e., they came from docker save)
for _, layer := range layers {
diffID, err := layer.DiffID()
if err != nil {
return false
}
knownLayer, layerFound := s.onDiskLayersByDiffID[diffID]
if !layerFound || knownLayer.uncompressedSize == -1 {
return false
}
}

return true
}

func (s *Store) addLayerToTarOptimized(tw *tar.Writer, layer v1.Layer, blankIdx int) (string, error) {
// This optimized version avoids reading through the layer to calculate uncompressed size
// when we already know it from docker save

// Get size info from cache first
diffID, err := layer.DiffID()
if err != nil {
return "", err
}
knownLayer := s.onDiskLayersByDiffID[diffID]

size, err := layer.Size()
if err != nil {
return "", err
}

var layerName string
if size == -1 { // it's a base (always empty) layer
layerName = fmt.Sprintf("blank_%d", blankIdx)
hdr := &tar.Header{Name: layerName, Mode: 0644, Size: 0}
return layerName, tw.WriteHeader(hdr)
}

// it's a populated layer - use known uncompressed size
layerName = fmt.Sprintf("/%s.tar", diffID.String())
uncompressedSize := knownLayer.uncompressedSize

// Use the uncompressed layer reader
layerReader, err := layer.Uncompressed()
if err != nil {
return "", err
}
defer layerReader.Close()

hdr := &tar.Header{Name: layerName, Mode: 0644, Size: uncompressedSize}
if err = tw.WriteHeader(hdr); err != nil {
return "", err
}
if _, err = io.Copy(tw, layerReader); err != nil {
return "", err
}

return layerName, nil
}

func (s *Store) addLayerToTar(tw *tar.Writer, layer v1.Layer, blankIdx int) (string, error) {
// If the layer is a previous image layer that hasn't been downloaded yet,
// cause ALL the previous image layers to be downloaded by grabbing the ReadCloser.
Expand Down Expand Up @@ -281,8 +355,8 @@ func (s *Store) addLayerToTar(tw *tar.Writer, layer v1.Layer, blankIdx int) (str

// getLayerSize returns the uncompressed layer size.
// This is needed because the daemon expects uncompressed layer size and a v1.Layer reports compressed layer size;
// in a future where we send OCI layout tars to the daemon we should be able to remove this method
// and the need to track layers individually.
// Performance optimization: When all layers have known uncompressed sizes (from docker save),
// use addLayerToTarOptimized to avoid this expensive calculation.
func (s *Store) getLayerSize(layer v1.Layer) (int64, error) {
diffID, err := layer.DiffID()
if err != nil {
Expand All @@ -292,6 +366,8 @@ func (s *Store) getLayerSize(layer v1.Layer) (int64, error) {
if layerFound && knownLayer.uncompressedSize != -1 {
return knownLayer.uncompressedSize, nil
}
// Performance optimization: Avoid expensive size calculation when all layers are known.
// This happens when layers were downloaded via docker save and have cached uncompressed sizes.
// FIXME: this is a time sink and should be avoided if the daemon accepts OCI layout-formatted tars
// If layer was not seen previously, we need to read it to get the uncompressed size
// In practice, we should only get here if layers saved from the daemon via `docker save`
Expand Down