Memory Leak

What is it: Allocated memory that is never freed, causing process memory to grow indefinitely. In GC languages like Go: retaining references to objects longer than needed — the GC cannot collect objects that are still reachable even if they're logically "done."

Why it's dangerous: A server leaking 1MB/hour is fine for hours. After 30 days: 720MB leak. After a year: OOM. The OOM killer terminates the process with no warning. Or worse: the process starts swapping, performance degrades for hours before the crash. Memory leaks are often invisible in testing (short runs) but catastrophic in production.

Common causes in Go:

Unbounded global map: cache := map[string]Data{} with items added but never removed. Each new unique key grows the map forever. Fix: use an LRU cache with a capacity limit (github.com/hashicorp/golang-lru).

Goroutine leak: goroutines stuck waiting on channels with no sender. Each goroutine holds its stack and any captured variables. 100K leaked goroutines = 200MB stack minimum + captured heap. See Goroutine Leaks section.

Appending to a growing slice: a background goroutine that appends to a global slice (logs, events) with no truncation.

HTTP response body not closed: resp.Body holds a reference to the TCP connection buffer. Not closing it = the buffer and connection are never returned to the pool.

Detection: runtime.ReadMemStats()HeapAlloc (current heap in use) and HeapSys (total heap acquired from OS). Expose via Prometheus: go_memstats_heap_alloc_bytes trending upward = leak. pprof heap profile: GET /debug/pprof/heap — shows allocation call stacks sorted by live bytes. The top entry is where the leak is.

Prevention: cap all caches with max size + eviction, always close response bodies (defer resp.Body.Close()), use contexts to ensure goroutines exit, prefer passing data through channels over shared global state.

Real-world usage: Proxel had a slow memory leak from an in-memory job result cache (map[string]JobResult) that was written to on every job completion but never evicted. After 7 days, the process used 4GB RAM. Fix: replaced with a TTL-based LRU cache capped at 10K entries.