Skip to content

Commit 6fcc57c

Browse files
authored
Optimize cached artifact serving (#245)
1 parent 538a15d commit 6fcc57c

6 files changed

Lines changed: 444 additions & 37 deletions

File tree

internal/database/database_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,86 @@ func TestArtifactCRUD(t *testing.T) {
239239
})
240240
}
241241

242+
func TestGetCachedArtifact(t *testing.T) {
243+
runWithBothDatabases(t, func(t *testing.T, db *DB) {
244+
const (
245+
packagePURL = "pkg:npm/lodash"
246+
versionPURL = "pkg:npm/lodash@4.17.21"
247+
filename = "lodash-4.17.21.tgz"
248+
)
249+
seedCachedArtifactTestData(t, db, packagePURL, versionPURL, filename)
250+
251+
cached, err := db.GetCachedArtifact(packagePURL, versionPURL, filename)
252+
if err != nil {
253+
t.Fatalf("GetCachedArtifact before cache failed: %v", err)
254+
}
255+
if cached != nil {
256+
t.Fatalf("expected no cached artifact, got %+v", cached)
257+
}
258+
259+
if err := db.MarkArtifactCached(versionPURL, filename, "/cache/npm/"+filename,
260+
"sha256-abc", 12345, "application/gzip"); err != nil {
261+
t.Fatalf("MarkArtifactCached failed: %v", err)
262+
}
263+
264+
cached, err = db.GetCachedArtifact(packagePURL, versionPURL, filename)
265+
if err != nil {
266+
t.Fatalf("GetCachedArtifact failed: %v", err)
267+
}
268+
if cached == nil {
269+
t.Fatal("expected cached artifact, got nil")
270+
}
271+
if cached.Ecosystem != "npm" {
272+
t.Errorf("expected npm ecosystem, got %q", cached.Ecosystem)
273+
}
274+
if cached.StoragePath != "/cache/npm/"+filename {
275+
t.Errorf("expected cached storage path, got %q", cached.StoragePath)
276+
}
277+
if cached.ContentHash.String != "sha256-abc" {
278+
t.Errorf("expected cached content hash, got %q", cached.ContentHash.String)
279+
}
280+
if cached.Size.Int64 != 12345 {
281+
t.Errorf("expected cached size 12345, got %d", cached.Size.Int64)
282+
}
283+
if cached.ContentType.String != "application/gzip" {
284+
t.Errorf("expected cached content type, got %q", cached.ContentType.String)
285+
}
286+
if cached.Integrity.String != "sha512-abc123" {
287+
t.Errorf("expected cached integrity, got %q", cached.Integrity.String)
288+
}
289+
290+
cached, err = db.GetCachedArtifact("pkg:npm/other", versionPURL, filename)
291+
if err != nil {
292+
t.Fatalf("GetCachedArtifact with wrong package failed: %v", err)
293+
}
294+
if cached != nil {
295+
t.Fatalf("expected package mismatch to miss cache, got %+v", cached)
296+
}
297+
})
298+
}
299+
300+
func seedCachedArtifactTestData(t *testing.T, db *DB, packagePURL, versionPURL, filename string) {
301+
t.Helper()
302+
303+
if err := db.UpsertPackage(&Package{PURL: packagePURL, Ecosystem: "npm", Name: "lodash"}); err != nil {
304+
t.Fatalf("UpsertPackage failed: %v", err)
305+
}
306+
if err := db.UpsertVersion(&Version{
307+
PURL: versionPURL,
308+
PackagePURL: packagePURL,
309+
Integrity: sql.NullString{String: "sha512-abc123", Valid: true},
310+
}); err != nil {
311+
t.Fatalf("UpsertVersion failed: %v", err)
312+
}
313+
if err := db.UpsertArtifact(&Artifact{
314+
VersionPURL: versionPURL,
315+
Filename: filename,
316+
UpstreamURL: "https://registry.npmjs.org/lodash/-/" + filename,
317+
}); err != nil {
318+
t.Fatalf("UpsertArtifact failed: %v", err)
319+
}
320+
}
321+
242322
func TestCacheManagement(t *testing.T) {
243323
runWithBothDatabases(t, func(t *testing.T, db *DB) {
244324
pkg := &Package{

internal/database/queries.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,28 @@ func (db *DB) GetArtifact(versionPURL, filename string) (*Artifact, error) {
191191
return &a, nil
192192
}
193193

194+
// GetCachedArtifact returns the fields needed to serve a cached artifact.
195+
func (db *DB) GetCachedArtifact(packagePURL, versionPURL, filename string) (*CachedArtifact, error) {
196+
var artifact CachedArtifact
197+
query := db.Rebind(`
198+
SELECT packages.ecosystem, artifacts.storage_path, artifacts.content_hash, artifacts.size,
199+
artifacts.content_type, versions.integrity
200+
FROM artifacts
201+
JOIN versions ON versions.purl = artifacts.version_purl
202+
JOIN packages ON packages.purl = versions.package_purl
203+
WHERE packages.purl = ? AND artifacts.version_purl = ? AND artifacts.filename = ?
204+
AND artifacts.storage_path IS NOT NULL AND artifacts.fetched_at IS NOT NULL
205+
`)
206+
err := db.Get(&artifact, query, packagePURL, versionPURL, filename)
207+
if err == sql.ErrNoRows {
208+
return nil, nil
209+
}
210+
if err != nil {
211+
return nil, err
212+
}
213+
return &artifact, nil
214+
}
215+
194216
func (db *DB) GetArtifactByPath(storagePath string) (*Artifact, error) {
195217
var a Artifact
196218
query := db.Rebind(`

internal/database/types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ func (a *Artifact) IsCached() bool {
7676
return a.StoragePath.Valid && a.FetchedAt.Valid
7777
}
7878

79+
// CachedArtifact contains the fields needed to serve a cached artifact.
80+
type CachedArtifact struct {
81+
Ecosystem string `db:"ecosystem"`
82+
StoragePath string `db:"storage_path"`
83+
ContentHash sql.NullString `db:"content_hash"`
84+
Size sql.NullInt64 `db:"size"`
85+
ContentType sql.NullString `db:"content_type"`
86+
Integrity sql.NullString `db:"integrity"`
87+
}
88+
7989
// MetadataCacheEntry represents a cached metadata blob for offline serving.
8090
type MetadataCacheEntry struct {
8191
ID int64 `db:"id" json:"id"`

internal/handler/handler.go

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"net/url"
1414
"strconv"
1515
"strings"
16+
"sync"
1617
"time"
1718

1819
"github.com/git-pkgs/cooldown"
@@ -48,6 +49,15 @@ func hasDotDotSegment(path string) bool {
4849

4950
const defaultHTTPTimeout = 30 * time.Second
5051

52+
const artifactCopyBufferSize = 32 << 10
53+
54+
var artifactCopyBufferPool = sync.Pool{ //nolint:gochecknoglobals // shared across artifact responses
55+
New: func() any {
56+
buffer := make([]byte, artifactCopyBufferSize)
57+
return &buffer
58+
},
59+
}
60+
5161
// canonicalPackagePURL returns a versionless PURL in canonical form so cooldown
5262
// lookups match keys produced by config.CooldownConfig.NormalizedPackages.
5363
func canonicalPackagePURL(ecosystem, name string) string {
@@ -157,27 +167,11 @@ func (p *Proxy) GetCachedArtifact(ctx context.Context, ecosystem, name, version,
157167

158168
// checkCache looks up an artifact in the cache. Returns nil if not cached.
159169
func (p *Proxy) checkCache(ctx context.Context, pkgPURL, versionPURL, filename string) (*CacheResult, error) {
160-
pkg, err := p.DB.GetPackageByPURL(pkgPURL)
161-
if err != nil {
162-
return nil, fmt.Errorf("checking package cache: %w", err)
163-
}
164-
if pkg == nil {
165-
return nil, nil
166-
}
167-
168-
ver, err := p.DB.GetVersionByPURL(versionPURL)
169-
if err != nil {
170-
return nil, fmt.Errorf("checking version cache: %w", err)
171-
}
172-
if ver == nil {
173-
return nil, nil
174-
}
175-
176-
artifact, err := p.DB.GetArtifact(versionPURL, filename)
170+
artifact, err := p.DB.GetCachedArtifact(pkgPURL, versionPURL, filename)
177171
if err != nil {
178172
return nil, fmt.Errorf("checking artifact cache: %w", err)
179173
}
180-
if artifact == nil || !artifact.IsCached() {
174+
if artifact == nil {
181175
return nil, nil
182176
}
183177

@@ -189,39 +183,39 @@ func (p *Proxy) checkCache(ctx context.Context, pkgPURL, versionPURL, filename s
189183
}
190184

191185
if p.DirectServe {
192-
signed, err := p.Storage.SignedURL(ctx, artifact.StoragePath.String, p.DirectServeTTL)
186+
signed, err := p.Storage.SignedURL(ctx, artifact.StoragePath, p.DirectServeTTL)
193187
if err == nil {
194188
result.RedirectURL = rewriteSignedURLHost(signed, p.DirectServeBaseURL)
195-
p.recordCacheHit(pkgPURL, versionPURL, filename)
189+
p.recordCacheHit(artifact.Ecosystem, versionPURL, filename)
196190
return result, nil
197191
}
198192
if !errors.Is(err, storage.ErrSignedURLUnsupported) {
199193
p.Logger.Warn("failed to sign storage URL, falling back to streaming",
200-
"path", artifact.StoragePath.String, "error", err)
194+
"path", artifact.StoragePath, "error", err)
201195
}
202196
}
203197

204198
start := time.Now()
205-
reader, err := p.Storage.Open(ctx, artifact.StoragePath.String)
199+
reader, err := p.Storage.Open(ctx, artifact.StoragePath)
206200
metrics.RecordStorageOperation("read", time.Since(start))
207201
if err != nil {
208202
metrics.RecordStorageError("read")
209203
p.Logger.Warn("cached artifact missing from storage, will refetch",
210-
"path", artifact.StoragePath.String, "error", err)
204+
"path", artifact.StoragePath, "error", err)
211205
return nil, nil
212206
}
213207

214-
result.Reader = newVerifyingReader(reader, artifact.ContentHash.String, ver.Integrity.String,
208+
result.Reader = newVerifyingReader(reader, artifact.ContentHash.String, artifact.Integrity.String,
215209
func(reason string) {
216210
p.Logger.Error("cached artifact failed integrity check",
217211
"purl", versionPURL, "filename", filename,
218-
"path", artifact.StoragePath.String, "reason", reason)
219-
metrics.RecordIntegrityFailure(pkg.Ecosystem)
212+
"path", artifact.StoragePath, "reason", reason)
213+
metrics.RecordIntegrityFailure(artifact.Ecosystem)
220214
if err := p.DB.ClearArtifactCache(versionPURL, filename); err != nil {
221215
p.Logger.Warn("failed to clear corrupt artifact from cache", "error", err)
222216
}
223217
})
224-
p.recordCacheHit(pkgPURL, versionPURL, filename)
218+
p.recordCacheHit(artifact.Ecosystem, versionPURL, filename)
225219
return result, nil
226220
}
227221

@@ -245,11 +239,9 @@ func rewriteSignedURLHost(signed, baseURL string) string {
245239
return s.String()
246240
}
247241

248-
func (p *Proxy) recordCacheHit(pkgPURL, versionPURL, filename string) {
242+
func (p *Proxy) recordCacheHit(ecosystem, versionPURL, filename string) {
249243
_ = p.DB.RecordArtifactHit(versionPURL, filename)
250-
if parsed, err := purl.Parse(pkgPURL); err == nil {
251-
metrics.RecordCacheHit(purl.PURLTypeToEcosystem(parsed.Type))
252-
}
244+
metrics.RecordCacheHit(purl.NormalizeEcosystem(ecosystem))
253245
}
254246

255247
func (p *Proxy) fetchAndCache(ctx context.Context, ecosystem, name, version, filename, pkgPURL, versionPURL string) (*CacheResult, error) {
@@ -376,7 +368,7 @@ func ServeArtifact(w http.ResponseWriter, result *CacheResult) {
376368
func serveArtifact(w http.ResponseWriter, method string, result *CacheResult) {
377369
if result.RedirectURL != "" {
378370
if result.Hash != "" {
379-
w.Header().Set("ETag", fmt.Sprintf(`"%s"`, result.Hash))
371+
w.Header().Set("ETag", `"`+result.Hash+`"`)
380372
}
381373
w.Header().Set("Location", result.RedirectURL)
382374
w.WriteHeader(http.StatusFound)
@@ -391,15 +383,18 @@ func serveArtifact(w http.ResponseWriter, method string, result *CacheResult) {
391383
w.Header().Set("Content-Type", result.ContentType)
392384
}
393385
if result.Size > 0 || (method == http.MethodHead && result.Size == 0) {
394-
w.Header().Set("Content-Length", fmt.Sprintf("%d", result.Size))
386+
w.Header().Set("Content-Length", strconv.FormatInt(result.Size, 10))
395387
}
396388
if result.Hash != "" {
397-
w.Header().Set("ETag", fmt.Sprintf(`"%s"`, result.Hash))
389+
w.Header().Set("ETag", `"`+result.Hash+`"`)
398390
}
399391

400392
w.WriteHeader(http.StatusOK)
401393
if method != http.MethodHead && result.Reader != nil {
402-
_, _ = io.Copy(w, result.Reader)
394+
buffer := artifactCopyBufferPool.Get().(*[]byte)
395+
defer artifactCopyBufferPool.Put(buffer)
396+
// Hide optional ReaderFrom methods so io.CopyBuffer uses the pooled buffer.
397+
_, _ = io.CopyBuffer(struct{ io.Writer }{w}, result.Reader, *buffer)
403398
}
404399
}
405400

0 commit comments

Comments
 (0)