package app import ( "log/slog" "math" "runtime/debug" "git.k8s-git-k6n.net/mats/fancy-dns/internal/config" ) // applyMemoryLimit turns the configured memory envelope into a GOMEMLIMIT. // // Why this matters more than it looks: the only other lever the Go runtime has // before a cgroup OOM kill is the cgroup limit itself, and a cgroup kill is the // worst outcome available to a resolver — an outage rather than a cache miss, // with no chance to serve stale data on the way out. GOMEMLIMIT turns the same // limit into a soft one: the GC starts working harder as the heap approaches // it, and only an OOM if it cannot reclaim enough. // // The limit is reduced by the reserve because the Go heap does not account for // goroutine stacks, runtime metadata, or — the one that matters here — the // bbolt mmap backing the cache snapshot and the query log, which lives outside // the Go heap entirely. Handing the runtime the full cgroup limit is how you // get killed by a mechanism that believed it had headroom. func applyMemoryLimit(m config.Memory, log *slog.Logger) { limit := m.EffectiveLimit() if limit == math.MaxInt64 { // Nothing configured. Leave the runtime unbounded rather than guessing // a number the operator did not ask for; a cgroup limit will still // apply, we just do not get to shape the GC for it. debug.SetMemoryLimit(math.MaxInt64) return } debug.SetMemoryLimit(limit) if log != nil { log.Info("memory envelope applied", "soft_limit", config.Size(limit).String(), "configured", m.Limit.String(), "reserve", config.Memory{Reserve: m.Reserve, Limit: 1}.EffectiveLimit(), "cache_budget", config.Size(m.EffectiveCacheBytes()).String(), ) } } // memoryStats is what the admin API reports about the envelope, so an operator // can see how close to the ceiling the process is without attaching a profiler. type memoryStats struct { HeapAlloc uint64 `json:"heap_alloc"` HeapSys uint64 `json:"heap_sys"` Limit int64 `json:"limit"` CacheBudget int64 `json:"cache_budget"` InUse bool `json:"limit_set"` } func readMemoryStats(m config.Memory) memoryStats { limit := m.EffectiveLimit() return memoryStats{ Limit: limit, CacheBudget: m.EffectiveCacheBytes(), InUse: limit != math.MaxInt64, } }