Skip to content

Commit 74d0e97

Browse files
committed
storage: support staged addition of layers
The extracting of the tar under the store lock is a bottleneck as many concurrent processes might hold the locks for a long time on big layers. To address this move the layer extraction before we take the locks if possible. Currently this only work when using the overlay driver as the implementation requires driver specifc details in order for a rename() to work. Signed-off-by: Paul Holzinger <[email protected]>
1 parent 0b1f951 commit 74d0e97

File tree

3 files changed

+141
-18
lines changed

3 files changed

+141
-18
lines changed

storage/layers.go

Lines changed: 114 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,26 @@ type DiffOptions struct {
199199
// stagedLayerOptions are the options passed to .create to populate a staged
200200
// layer
201201
type stagedLayerOptions struct {
202+
// These are used via the zstd:chunked pull paths
202203
DiffOutput *drivers.DriverWithDifferOutput
203204
DiffOptions *drivers.ApplyDiffWithDifferOpts
205+
206+
// stagedLayerExtraction is used by the normal tar layer extraction.
207+
stagedLayerExtraction *maybeStagedLayerExtraction
208+
}
209+
210+
// maybeStagedLayerExtraction is a helper to encapsulate details around extracting
211+
// a layer potentially before we even take a look if the driver implements the
212+
// ApplyDiffStaging interface.
213+
type maybeStagedLayerExtraction struct {
214+
// diff contains the tar archive, can be compressed
215+
diff io.Reader
216+
staging drivers.ApplyDiffStaging
217+
result *applyDiffResult
218+
219+
cleanupFuncs []tempdir.CleanupTempDirFunc
220+
stagedTarSplit *tempdir.StageAddition
221+
stagedLayer *tempdir.StageAddition
204222
}
205223

206224
type applyDiffResult struct {
@@ -300,7 +318,7 @@ type rwLayerStore interface {
300318
// underlying drivers do not themselves distinguish between writeable
301319
// and read-only layers. Returns the new layer structure and the size of the
302320
// diff which was applied to its parent to initialize its contents.
303-
create(id string, parent *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, diff io.Reader, slo *stagedLayerOptions) (*Layer, int64, error)
321+
create(id string, parent *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, slo *stagedLayerOptions) (*Layer, int64, error)
304322

305323
// updateNames modifies names associated with a layer based on (op, names).
306324
updateNames(id string, names []string, op updateNameOperation) error
@@ -1391,7 +1409,7 @@ func (r *layerStore) pickStoreLocation(volatile, writeable bool) layerLocations
13911409
}
13921410

13931411
// Requires startWriting.
1394-
func (r *layerStore) create(id string, parentLayer *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, diff io.Reader, slo *stagedLayerOptions) (layer *Layer, size int64, err error) {
1412+
func (r *layerStore) create(id string, parentLayer *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, slo *stagedLayerOptions) (layer *Layer, size int64, err error) {
13951413
if moreOptions == nil {
13961414
moreOptions = &LayerOptions{}
13971415
}
@@ -1580,15 +1598,28 @@ func (r *layerStore) create(id string, parentLayer *Layer, names []string, mount
15801598
}
15811599

15821600
size = -1
1583-
if diff != nil {
1584-
if size, err = r.applyDiffWithOptions(layer.ID, moreOptions, diff); err != nil {
1585-
cleanupFailureContext = "applying layer diff"
1586-
return nil, -1, err
1587-
}
1588-
} else if slo != nil {
1589-
if err := r.applyDiffFromStagingDirectory(layer.ID, slo.DiffOutput, slo.DiffOptions); err != nil {
1590-
cleanupFailureContext = "applying staged directory diff"
1591-
return nil, -1, err
1601+
if slo != nil {
1602+
if slo.stagedLayerExtraction != nil {
1603+
if slo.stagedLayerExtraction.result != nil {
1604+
// The layer is staged, just commit it and update the metadata.
1605+
if err := slo.stagedLayerExtraction.commitLayer(r, layer.ID); err != nil {
1606+
cleanupFailureContext = "commiting staged layer diff"
1607+
return nil, -1, err
1608+
}
1609+
applyDiffResultToLayer(r, layer, moreOptions, slo.stagedLayerExtraction.result)
1610+
} else {
1611+
// The diff was not staged, apply it now here instead.
1612+
if size, err = r.applyDiffWithOptions(layer.ID, moreOptions, slo.stagedLayerExtraction.diff); err != nil {
1613+
cleanupFailureContext = "applying layer diff"
1614+
return nil, -1, err
1615+
}
1616+
}
1617+
} else {
1618+
// staging logic for the chunked pull path
1619+
if err := r.applyDiffFromStagingDirectory(layer.ID, slo.DiffOutput, slo.DiffOptions); err != nil {
1620+
cleanupFailureContext = "applying staged directory diff"
1621+
return nil, -1, err
1622+
}
15921623
}
15931624
} else {
15941625
// applyDiffWithOptions() would have updated r.bycompressedsum
@@ -2417,6 +2448,78 @@ func createTarSplitFile(r *layerStore, layerID string) (*os.File, error) {
24172448
return os.OpenFile(r.tspath(layerID), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600)
24182449
}
24192450

2451+
// newMaybeStagedLayerExtraction initlaizes a new maybeStagedLayerExtraction. The caller
2452+
// must call .cleanup() to remove any temporary files.
2453+
func newMaybeStagedLayerExtraction(diff io.Reader, driver drivers.Driver) *maybeStagedLayerExtraction {
2454+
m := &maybeStagedLayerExtraction{
2455+
diff: diff,
2456+
}
2457+
if d, ok := driver.(drivers.ApplyDiffStaging); ok {
2458+
m.staging = d
2459+
}
2460+
return m
2461+
}
2462+
2463+
func (sl *maybeStagedLayerExtraction) cleanup() error {
2464+
return tempdir.CleanupTemporaryDirectories(sl.cleanupFuncs...)
2465+
}
2466+
2467+
// stageWithUnlockedStore stages the layer content without needing the store locked.
2468+
// If the driver does not support stage addition then this is a NOP and does nothing.
2469+
func (sl *maybeStagedLayerExtraction) stageWithUnlockedStore(r *layerStore, layerOptions *LayerOptions) error {
2470+
if sl.staging == nil {
2471+
// driver does not implement stage addition
2472+
return nil
2473+
}
2474+
td, err := tempdir.NewTempDir(filepath.Join(r.layerdir, tempDirPath))
2475+
if err != nil {
2476+
return err
2477+
}
2478+
sl.cleanupFuncs = append(sl.cleanupFuncs, td.Cleanup)
2479+
2480+
stageTarSplit, err := td.StageAddition()
2481+
if err != nil {
2482+
return err
2483+
}
2484+
sl.stagedTarSplit = stageTarSplit
2485+
2486+
f, err := os.OpenFile(stageTarSplit.Path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600)
2487+
if err != nil {
2488+
return err
2489+
}
2490+
defer f.Close()
2491+
2492+
result, err := applyDiff(layerOptions, sl.diff, f, func(payload io.Reader) (int64, error) {
2493+
cleanup, stageLayer, size, err := sl.staging.StartStagingDiffToApply(drivers.ApplyDiffOpts{
2494+
Diff: payload,
2495+
Mappings: idtools.NewIDMappingsFromMaps(layerOptions.UIDMap, layerOptions.GIDMap),
2496+
// FIXME: What to do here? We have no lock and assigned label yet.
2497+
// Overlayfs should not need it anyway so this seems fine for now.
2498+
MountLabel: "",
2499+
})
2500+
sl.cleanupFuncs = append(sl.cleanupFuncs, cleanup)
2501+
sl.stagedLayer = stageLayer
2502+
return size, err
2503+
})
2504+
if err != nil {
2505+
return err
2506+
}
2507+
2508+
sl.result = result
2509+
return nil
2510+
}
2511+
2512+
// commitLayer() commits the content that was staged in stageWithUnlockedStore()
2513+
//
2514+
// Requires startWriting.
2515+
func (sl *maybeStagedLayerExtraction) commitLayer(r *layerStore, layerID string) error {
2516+
err := sl.stagedTarSplit.Commit(r.tspath(layerID))
2517+
if err != nil {
2518+
return err
2519+
}
2520+
return sl.staging.CommitStagedLayer(layerID, sl.stagedLayer)
2521+
}
2522+
24202523
func applyDiff(layerOptions *LayerOptions, diff io.Reader, tarSplitFile *os.File, applyDriverFunc func(io.Reader) (int64, error)) (*applyDiffResult, error) {
24212524
header := make([]byte, 10240)
24222525
n, err := diff.Read(header)

storage/store.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,7 +1452,7 @@ func (s *store) canUseShifting(uidmap, gidmap []idtools.IDMap) bool {
14521452
// On entry:
14531453
// - rlstore must be locked for writing
14541454
// - rlstores MUST NOT be locked
1455-
func (s *store) putLayer(rlstore rwLayerStore, rlstores []roLayerStore, id, parent string, names []string, mountLabel string, writeable bool, lOptions *LayerOptions, diff io.Reader, slo *stagedLayerOptions) (*Layer, int64, error) {
1455+
func (s *store) putLayer(rlstore rwLayerStore, rlstores []roLayerStore, id, parent string, names []string, mountLabel string, writeable bool, lOptions *LayerOptions, slo *stagedLayerOptions) (*Layer, int64, error) {
14561456
var parentLayer *Layer
14571457
var options LayerOptions
14581458
if lOptions != nil {
@@ -1533,19 +1533,39 @@ func (s *store) putLayer(rlstore rwLayerStore, rlstores []roLayerStore, id, pare
15331533
GIDMap: copySlicePreferringNil(gidMap),
15341534
}
15351535
}
1536-
return rlstore.create(id, parentLayer, names, mountLabel, nil, &options, writeable, diff, slo)
1536+
return rlstore.create(id, parentLayer, names, mountLabel, nil, &options, writeable, slo)
15371537
}
15381538

15391539
func (s *store) PutLayer(id, parent string, names []string, mountLabel string, writeable bool, lOptions *LayerOptions, diff io.Reader) (*Layer, int64, error) {
15401540
rlstore, rlstores, err := s.bothLayerStoreKinds()
15411541
if err != nil {
15421542
return nil, -1, err
15431543
}
1544+
1545+
var slo *stagedLayerOptions
1546+
1547+
if diff != nil {
1548+
m := newMaybeStagedLayerExtraction(diff, s.graphDriver)
1549+
defer func() {
1550+
if err := m.cleanup(); err != nil {
1551+
logrus.Errorf("Error cleaning up temporary directories: %v", err)
1552+
}
1553+
}()
1554+
// FIXME: type case should be safe for now but really there should be a better way to do this
1555+
err = m.stageWithUnlockedStore(rlstore.(*layerStore), lOptions)
1556+
if err != nil {
1557+
return nil, -1, err
1558+
}
1559+
slo = &stagedLayerOptions{
1560+
stagedLayerExtraction: m,
1561+
}
1562+
}
1563+
15441564
if err := rlstore.startWriting(); err != nil {
15451565
return nil, -1, err
15461566
}
15471567
defer rlstore.stopWriting()
1548-
return s.putLayer(rlstore, rlstores, id, parent, names, mountLabel, writeable, lOptions, diff, nil)
1568+
return s.putLayer(rlstore, rlstores, id, parent, names, mountLabel, writeable, lOptions, slo)
15491569
}
15501570

15511571
func (s *store) CreateLayer(id, parent string, names []string, mountLabel string, writeable bool, options *LayerOptions) (*Layer, error) {
@@ -1753,7 +1773,7 @@ func (s *store) imageTopLayerForMapping(image *Image, ristore roImageStore, rlst
17531773
}
17541774
}
17551775
layerOptions.TemplateLayer = layer.ID
1756-
mappedLayer, _, err := rlstore.create("", parentLayer, nil, layer.MountLabel, nil, &layerOptions, false, nil, nil)
1776+
mappedLayer, _, err := rlstore.create("", parentLayer, nil, layer.MountLabel, nil, &layerOptions, false, nil)
17571777
if err != nil {
17581778
return nil, fmt.Errorf("creating an ID-mapped copy of layer %q: %w", layer.ID, err)
17591779
}
@@ -1924,7 +1944,7 @@ func (s *store) CreateContainer(id string, names []string, image, layer, metadat
19241944
options.Flags[mountLabelFlag] = mountLabel
19251945
}
19261946

1927-
clayer, _, err := rlstore.create(layer, imageTopLayer, nil, mlabel, options.StorageOpt, layerOptions, true, nil, nil)
1947+
clayer, _, err := rlstore.create(layer, imageTopLayer, nil, mlabel, options.StorageOpt, layerOptions, true, nil)
19281948
if err != nil {
19291949
return nil, err
19301950
}
@@ -3186,7 +3206,7 @@ func (s *store) ApplyStagedLayer(args ApplyStagedLayerOptions) (*Layer, error) {
31863206
DiffOutput: args.DiffOutput,
31873207
DiffOptions: args.DiffOptions,
31883208
}
3189-
layer, _, err = s.putLayer(rlstore, rlstores, args.ID, args.ParentLayer, args.Names, args.MountLabel, args.Writeable, args.LayerOptions, nil, &slo)
3209+
layer, _, err = s.putLayer(rlstore, rlstores, args.ID, args.ParentLayer, args.Names, args.MountLabel, args.Writeable, args.LayerOptions, &slo)
31903210
return layer, err
31913211
}
31923212

storage/userns.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ outer:
197197

198198
// We need to create a temporary layer so we can mount it and lookup the
199199
// maximum IDs used.
200-
clayer, _, err := rlstore.create("", topLayer, nil, "", nil, layerOptions, false, nil, nil)
200+
clayer, _, err := rlstore.create("", topLayer, nil, "", nil, layerOptions, false, nil)
201201
if err != nil {
202202
return 0, err
203203
}

0 commit comments

Comments
 (0)