forked from mirrors/pkg-proxy
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.
105 lines
2.2 KiB
Go
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)
|
|
}
|
|
}
|