From c53211eacd63718d1eb1219990dfd6f5bb39f118 Mon Sep 17 00:00:00 2001 From: Nalin Dahyabhai Date: Wed, 14 Feb 2018 13:57:18 -0500 Subject: [PATCH] imageService: cache information about images Cache information about images that isn't trivially read from them, so that ImageStatus and particularly ListImages don't have to do potentially-expensive things for every image that they report. The cache is an in-memory map, and we prune it after ListImages has assembled its result set. Signed-off-by: Nalin Dahyabhai --- pkg/storage/image.go | 145 ++++++++++++++++++++++++++++++------------- 1 file changed, 102 insertions(+), 43 deletions(-) diff --git a/pkg/storage/image.go b/pkg/storage/image.go index a94f95ad..2c2c3f62 100644 --- a/pkg/storage/image.go +++ b/pkg/storage/image.go @@ -47,12 +47,22 @@ type indexInfo struct { secure bool } +// A set of information that we prefer to cache about images, so that we can +// avoid having to reread them every time we need to return information about +// images. +type imageCacheItem struct { + user string + size *uint64 + configDigest digest.Digest +} + type imageService struct { store storage.Store defaultTransport string insecureRegistryCIDRs []*net.IPNet indexConfigs map[string]*indexInfo registries []string + imageCache map[string]imageCacheItem } // sizer knows its size. @@ -172,25 +182,38 @@ func (svc *imageService) ListImages(systemContext *types.SystemContext, filter s return nil, err } if image, err := istorage.Transport.GetStoreImage(svc.store, ref); err == nil { - img, err := ref.NewImageSource(systemContext) - if err != nil { - return nil, err - } - size := imageSize(img) - configDigest, err := imageConfigDigest(img, nil) - img.Close() - if err != nil { - return nil, err - } - imageFull, err := ref.NewImage(systemContext) - if err != nil { - return nil, err - } - defer imageFull.Close() - - imageConfig, err := imageFull.OCIConfig() - if err != nil { - return nil, err + var user string + var size *uint64 + var configDigest digest.Digest + if cacheItem, ok := svc.imageCache[image.ID]; ok { + user, size, configDigest = cacheItem.user, cacheItem.size, cacheItem.configDigest + } else { + img, err := ref.NewImageSource(systemContext) + if err != nil { + return nil, err + } + size = imageSize(img) + configDigest, err = imageConfigDigest(img, nil) + img.Close() + if err != nil { + return nil, err + } + imageFull, err := ref.NewImage(systemContext) + if err != nil { + return nil, err + } + defer imageFull.Close() + imageConfig, err := imageFull.OCIConfig() + if err != nil { + return nil, err + } + user = imageConfig.Config.User + cacheItem := imageCacheItem{ + user: user, + size: size, + configDigest: configDigest, + } + svc.imageCache[image.ID] = cacheItem } name, tags, digests := sortNamesByType(image.Names) imageDigest, repoDigests := svc.makeRepoDigests(digests, tags, image.ID) @@ -202,7 +225,7 @@ func (svc *imageService) ListImages(systemContext *types.SystemContext, filter s Size: size, Digest: imageDigest, ConfigDigest: configDigest, - User: imageConfig.Config.User, + User: user, }) } } else { @@ -210,30 +233,65 @@ func (svc *imageService) ListImages(systemContext *types.SystemContext, filter s if err != nil { return nil, err } + visited := make(map[string]struct{}) + defer func() { + // We built a map using IDs of images that we looked + // at, so remove any items from the cache that don't + // correspond to any of those IDs. + removedIDs := make([]string, 0, len(svc.imageCache)) + for imageID := range svc.imageCache { + if _, keep := visited[imageID]; !keep { + // We have cached data for an image + // with this ID, but it's not in the + // list of images now, so the image has + // been removed. + removedIDs = append(removedIDs, imageID) + } + } + // Handle the removals. + for _, removedID := range removedIDs { + delete(svc.imageCache, removedID) + } + }() for _, image := range images { - ref, err := istorage.Transport.ParseStoreReference(svc.store, "@"+image.ID) - if err != nil { - return nil, err - } - img, err := ref.NewImageSource(systemContext) - if err != nil { - return nil, err - } - size := imageSize(img) - configDigest, err := imageConfigDigest(img, nil) - img.Close() - if err != nil { - return nil, err - } - imageFull, err := ref.NewImage(systemContext) - if err != nil { - return nil, err - } - defer imageFull.Close() + visited[image.ID] = struct{}{} + var user string + var size *uint64 + var configDigest digest.Digest + if cacheItem, ok := svc.imageCache[image.ID]; ok { + user, size, configDigest = cacheItem.user, cacheItem.size, cacheItem.configDigest + } else { + ref, err := istorage.Transport.ParseStoreReference(svc.store, "@"+image.ID) + if err != nil { + return nil, err + } + img, err := ref.NewImageSource(systemContext) + if err != nil { + return nil, err + } + size = imageSize(img) + configDigest, err = imageConfigDigest(img, nil) + img.Close() + if err != nil { + return nil, err + } + imageFull, err := ref.NewImage(systemContext) + if err != nil { + return nil, err + } + defer imageFull.Close() - imageConfig, err := imageFull.OCIConfig() - if err != nil { - return nil, err + imageConfig, err := imageFull.OCIConfig() + if err != nil { + return nil, err + } + user = imageConfig.Config.User + cacheItem := imageCacheItem{ + user: user, + size: size, + configDigest: configDigest, + } + svc.imageCache[image.ID] = cacheItem } name, tags, digests := sortNamesByType(image.Names) imageDigest, repoDigests := svc.makeRepoDigests(digests, tags, image.ID) @@ -245,7 +303,7 @@ func (svc *imageService) ListImages(systemContext *types.SystemContext, filter s Size: size, Digest: imageDigest, ConfigDigest: configDigest, - User: imageConfig.Config.User, + User: user, }) } } @@ -619,6 +677,7 @@ func GetImageService(store storage.Store, defaultTransport string, insecureRegis indexConfigs: make(map[string]*indexInfo, 0), insecureRegistryCIDRs: make([]*net.IPNet, 0), registries: cleanRegistries, + imageCache: make(map[string]imageCacheItem), } insecureRegistries = append(insecureRegistries, "127.0.0.0/8")