Skip to content
This repository has been archived by the owner on Jun 5, 2024. It is now read-only.

Commit

Permalink
Add global expanded APK cache (#133)
Browse files Browse the repository at this point in the history
This dedupes fetching and expansion of APKs to avoid simultaneous builds
doing a lot of duplicate expensive work. As a bonus, we also deduplicate
indexing an APK's tar headers so we do it once per APK per process
instead of every time we open an APK.

The caching mechanisms that already existed are very useful for cache
hits on subsequent builds, but there was still some overhead loading
things from disk per cache hit. We avoid most of that now. There was
also no coordination across builds when fetching indexes, keys, and
APKs. Now, we should only fetch or load any given thing one time, which
will reduce the number of requests we send out to the internet but also
a lot of CPU in gunzip and untar.

Signed-off-by: Jon Johnson <[email protected]>
  • Loading branch information
jonjohnsonjr authored Oct 10, 2023
1 parent 5e3480e commit cb033c6
Showing 1 changed file with 52 additions and 0 deletions.
52 changes: 52 additions & 0 deletions pkg/apk/implementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"path/filepath"
"runtime"
"strings"
"sync"
"time"

"github.com/chainguard-dev/go-apk/pkg/expandapk"
Expand All @@ -48,6 +49,11 @@ import (
"github.com/hashicorp/go-retryablehttp"
)

// This is terrible but simpler than plumbing around a cache for now.
// We just hold the expanded APK in memory rather than re-parsing it every time,
// which is expensive. This also dedupes simultaneous fetches.
var globalApkCache = &apkCache{}

type APK struct {
arch string
version string
Expand Down Expand Up @@ -750,7 +756,53 @@ func (a *APK) cachedPackage(ctx context.Context, pkg *repository.RepositoryPacka
return &exp, nil
}

type apkResult struct {
exp *expandapk.APKExpanded
err error
}

type apkCache struct {
// url -> *sync.Once
onces sync.Map

// url -> apkResult
resps sync.Map
}

func (c *apkCache) get(ctx context.Context, a *APK, pkg *repository.RepositoryPackage) (*expandapk.APKExpanded, error) {
u := pkg.Url()
// Do all the expensive things inside the once.
once, _ := c.onces.LoadOrStore(u, &sync.Once{})
once.(*sync.Once).Do(func() {
exp, err := expandPackage(ctx, a, pkg)
c.resps.Store(u, apkResult{
exp: exp,
err: err,
})
})

v, ok := c.resps.Load(u)
if !ok {
panic(fmt.Errorf("did not see apk %q after writing it", u))
}

result := v.(apkResult)
return result.exp, result.err
}

func (a *APK) expandPackage(ctx context.Context, pkg *repository.RepositoryPackage) (*expandapk.APKExpanded, error) {
if a.cache == nil {
// If we don't have a cache configured, don't use the global cache.
// Calling APKExpanded.Close() will clean up a tempdir.
// This is fine when we have a cache because we move all the backing files into the cache.
// This is not fine when we don't have a cache because the tempdir contains all our state.
return expandPackage(ctx, a, pkg)
}

return globalApkCache.get(ctx, a, pkg)
}

func expandPackage(ctx context.Context, a *APK, pkg *repository.RepositoryPackage) (*expandapk.APKExpanded, error) {
ctx, span := otel.Tracer("go-apk").Start(ctx, "expandPackage", trace.WithAttributes(attribute.String("package", pkg.Name)))
defer span.End()

Expand Down

0 comments on commit cb033c6

Please sign in to comment.