cache: Short TTL negative cache for 404/410 depot objects
Stop re-fetching gone depot objects on every miss: store 404/410 in the existing VFS cache under the same key with a short TTL (default 5m).
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -35,6 +36,11 @@ type CacheConfig struct {
|
||||
|
||||
// Disk cache settings
|
||||
Disk DiskConfig `yaml:"disk"`
|
||||
|
||||
// NegativeTTL is a Go duration string for cached 404/410 depot objects
|
||||
// (same VFS key as a positive hit). Empty defaults to 5m. "0" disables
|
||||
// storing negatives (the client still receives the upstream 404/410).
|
||||
NegativeTTL string `yaml:"negative_ttl"`
|
||||
}
|
||||
|
||||
type MemoryConfig struct {
|
||||
@@ -100,6 +106,9 @@ func LoadConfig(configPath string) (*Config, error) {
|
||||
if config.Cache.Disk.GCAlgorithm == "" {
|
||||
config.Cache.Disk.GCAlgorithm = "lru"
|
||||
}
|
||||
if config.Cache.NegativeTTL == "" {
|
||||
config.Cache.NegativeTTL = "5m"
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
@@ -126,6 +135,7 @@ func SaveDefaultConfig(configPath string) error {
|
||||
Path: "./disk",
|
||||
GCAlgorithm: "lru", // Better for gaming patterns (keeps recently played games)
|
||||
},
|
||||
NegativeTTL: "5m",
|
||||
},
|
||||
Upstream: "",
|
||||
}
|
||||
@@ -162,6 +172,7 @@ func GetDefaultConfig() Config {
|
||||
Path: "./disk",
|
||||
GCAlgorithm: "lru",
|
||||
},
|
||||
NegativeTTL: "5m",
|
||||
},
|
||||
Upstream: "",
|
||||
}
|
||||
@@ -188,6 +199,16 @@ func (c Config) Validate() error {
|
||||
return fmt.Errorf("disk cache enabled but no path specified")
|
||||
}
|
||||
|
||||
if c.Cache.NegativeTTL != "" {
|
||||
d, err := time.ParseDuration(c.Cache.NegativeTTL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid cache.negative_ttl: %w", err)
|
||||
}
|
||||
if d < 0 {
|
||||
return fmt.Errorf("invalid cache.negative_ttl: negative duration")
|
||||
}
|
||||
}
|
||||
|
||||
// Light validation for security/resource fields (mirrors existing GC + path checks; fails fast before New)
|
||||
if c.MaxObjectSize != "" && c.MaxObjectSize != "0" {
|
||||
if _, err := units.FromHumanSize(c.MaxObjectSize); err != nil {
|
||||
|
||||
@@ -156,6 +156,44 @@ func TestValidate(t *testing.T) {
|
||||
}(),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid negative_ttl duration",
|
||||
cfg: func() Config {
|
||||
c := GetDefaultConfig()
|
||||
c.Cache.NegativeTTL = "1m"
|
||||
return c
|
||||
}(),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty negative_ttl ok",
|
||||
cfg: func() Config {
|
||||
c := GetDefaultConfig()
|
||||
c.Cache.NegativeTTL = ""
|
||||
return c
|
||||
}(),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid negative_ttl",
|
||||
cfg: func() Config {
|
||||
c := GetDefaultConfig()
|
||||
c.Cache.NegativeTTL = "not-a-duration"
|
||||
return c
|
||||
}(),
|
||||
wantErr: true,
|
||||
errSub: "invalid cache.negative_ttl",
|
||||
},
|
||||
{
|
||||
name: "negative duration negative_ttl",
|
||||
cfg: func() Config {
|
||||
c := GetDefaultConfig()
|
||||
c.Cache.NegativeTTL = "-1s"
|
||||
return c
|
||||
}(),
|
||||
wantErr: true,
|
||||
errSub: "invalid cache.negative_ttl",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
Reference in New Issue
Block a user