pkg-proxy/internal/server/eviction.go
Andrew Nesbitt 461a95c518
Enforce max_size config with LRU cache eviction
Closes #99. The max_size storage config was parsed and validated but
never enforced. This adds a background eviction loop that periodically
checks total cache size and evicts least recently used artifacts when
the limit is exceeded.
2026-04-30 18:09:01 +01:00

105 lines
2.2 KiB
Go

package server
import (
"context"
"log/slog"
"time"
"github.com/git-pkgs/proxy/internal/database"
"github.com/git-pkgs/proxy/internal/storage"
)
const (
evictionInterval = 1 * time.Minute
evictionBatch = 50
)
func (s *Server) startEvictionLoop(ctx context.Context) {
maxSize := s.cfg.ParseMaxSize()
if maxSize <= 0 {
return
}
s.logger.Info("cache eviction enabled", "max_size", s.cfg.Storage.MaxSize)
ticker := time.NewTicker(evictionInterval)
defer ticker.Stop()
s.runEviction(ctx, maxSize)
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
s.runEviction(ctx, maxSize)
}
}
}
func (s *Server) runEviction(ctx context.Context, maxSize int64) {
evictLRU(ctx, s.db, s.storage, s.logger, maxSize)
}
func evictLRU(ctx context.Context, db *database.DB, store storage.Storage, logger *slog.Logger, maxSize int64) {
totalSize, err := db.GetTotalCacheSize()
if err != nil {
logger.Warn("eviction: failed to get cache size", "error", err)
return
}
if totalSize <= maxSize {
return
}
logger.Info("eviction: cache size exceeds limit, evicting",
"current_size", totalSize, "max_size", maxSize)
evicted := 0
freedBytes := int64(0)
for totalSize-freedBytes > maxSize {
artifacts, err := db.GetLeastRecentlyUsedArtifacts(evictionBatch)
if err != nil {
logger.Warn("eviction: failed to get LRU artifacts", "error", err)
return
}
if len(artifacts) == 0 {
break
}
for _, art := range artifacts {
if totalSize-freedBytes <= maxSize {
break
}
if !art.StoragePath.Valid {
continue
}
if err := store.Delete(ctx, art.StoragePath.String); err != nil {
logger.Warn("eviction: failed to delete from storage",
"path", art.StoragePath.String, "error", err)
continue
}
if err := db.ClearArtifactCache(art.VersionPURL, art.Filename); err != nil {
logger.Warn("eviction: failed to clear artifact record",
"version_purl", art.VersionPURL, "filename", art.Filename, "error", err)
continue
}
size := int64(0)
if art.Size.Valid {
size = art.Size.Int64
}
freedBytes += size
evicted++
}
}
if evicted > 0 {
logger.Info("eviction: completed",
"evicted", evicted, "freed_bytes", freedBytes)
}
}