Files
wehub-resource-sync 1b8708893a
Security Scan / tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:12:26 +08:00

96 lines
3.8 KiB
Go

package prefixcache
import (
"fmt"
"time"
)
// Config holds prefix-cache-aware routing settings. Per-model overrides
// (policy, abs/rel thresholds, min-match) live on ModelSchedulingConfig; TTL
// and window/depth are global-only.
type Config struct {
GlobalPolicy RoutePolicy
MinPrefixMatch float64 // ratio matched/total, [0,1]
BalanceAbsThreshold int // absolute in-flight slack
BalanceRelThreshold float64 // relative load ratio, >= 1
TTL time.Duration // idle-timeout for entries
HalfLife time.Duration // recency decay for cacheWeight
WindowBytes int // chunk window size
MaxDepth int // max trailing blocks hashed
// PressureWindow is the rolling window over which forced-disturb events are
// counted for the autoscale signal (see Pressure). Default 1 minute.
PressureWindow time.Duration
// PressureScaleThreshold is the minimum forced-disturb count within
// PressureWindow that makes the reconciler treat the cache-warm replica as
// saturated and scale up (subject to MaxReplicas and capacity). Default 1,
// i.e. any sustained forced-disturb.
PressureScaleThreshold int
}
func DefaultConfig() Config {
return Config{
GlobalPolicy: RoutePolicyPrefixCache,
MinPrefixMatch: 0.3,
BalanceAbsThreshold: 2,
BalanceRelThreshold: 1.5,
TTL: 5 * time.Minute,
HalfLife: 2 * time.Minute,
WindowBytes: 256,
MaxDepth: 64,
PressureWindow: time.Minute,
PressureScaleThreshold: 1,
}
}
// validateThresholdBounds enforces the numeric bounds shared between the
// per-model override validator (ValidateThresholds) and Config.Validate:
// minMatch in [0,1]; absThr >= 0; relThr == 0 (inherit) or >= 1. It is the
// single source of truth for those bounds so the endpoint and the global
// config cannot drift apart.
func validateThresholdBounds(absThr int, relThr, minMatch float64) error {
if minMatch < 0 || minMatch > 1 {
return fmt.Errorf("prefixcache: min_prefix_match must be in [0,1], got %v", minMatch)
}
if absThr < 0 {
return fmt.Errorf("prefixcache: balance_abs_threshold must be >= 0, got %d", absThr)
}
if relThr != 0 && relThr < 1 {
return fmt.Errorf("prefixcache: balance_rel_threshold must be 0 (inherit) or >= 1, got %v", relThr)
}
return nil
}
// ValidateThresholds checks per-model override bounds. routePolicy must be one
// of "", "round_robin", "prefix_cache" (explicit allow-list - NOT ParsePolicy,
// which maps unknown to Default and would accept typos). minMatch in [0,1];
// absThr >= 0; relThr == 0 (inherit) or >= 1.
func ValidateThresholds(routePolicy string, absThr int, relThr, minMatch float64) error {
switch routePolicy {
case "", "round_robin", "prefix_cache":
default:
return fmt.Errorf(`prefixcache: route_policy must be one of "", "round_robin", "prefix_cache", got %q`, routePolicy)
}
return validateThresholdBounds(absThr, relThr, minMatch)
}
func (c Config) Validate() error {
// Config.BalanceRelThreshold has no "inherit" sentinel - it is a concrete
// global value that must be >= 1 - so pass 0 for relThr to the shared
// numeric check and assert the >= 1 floor here separately.
if err := validateThresholdBounds(c.BalanceAbsThreshold, 0, c.MinPrefixMatch); err != nil {
return err
}
if c.BalanceRelThreshold < 1 {
return fmt.Errorf("prefixcache: balance_rel_threshold must be >= 1, got %v", c.BalanceRelThreshold)
}
if c.WindowBytes <= 0 || c.MaxDepth <= 0 {
return fmt.Errorf("prefixcache: window_bytes and max_depth must be > 0")
}
// TTL must be positive: it is the entry idle-lifetime and the eviction
// ticker runs at TTL/2, so time.NewTicker would panic on TTL <= 0.
if c.TTL <= 0 {
return fmt.Errorf("prefixcache: ttl must be > 0, got %v", c.TTL)
}
return nil
}