Dgraph Alpha nodes repeatedly run out of memory and get OOM-killed in Kubernetes #9876
Replies: 2 comments 7 replies
|
To your first question: a slow climb toward the limit isn't expected, but I don't think v24.0.5 has a known leak that matches what you're seeing. There have been real posting-list cache fixes since v24.0.5. In that version the posting-list cache gives every entry a cost of zero, so it never evicts (fixed in #9515), and resizing it at runtime did nothing (fixed in #9526). Your config turns that cache off, though. The first value in percentage=0,80,20 is the posting-list cache share, and with it at 0 the cache is never created. Your 256 MB goes to Badger instead: about 205 MB of block cache and 51 MB of index cache. The gap between RSS and Go heap is most likely that Badger block cache. With compression=snappy, Badger decompresses blocks into memory it allocates itself (through jemalloc in the official builds), outside the Go heap. So it never appears in HeapAlloc, and GOMEMLIMIT can't see it or limit it -- this has been a unfortunately side-effect of jemalloc. A full ~205 MB block cache plus Go runtime overhead fits your ~524 MiB RSS against ~170 MiB of heap pretty well. The remaining ~140 MiB between RSS and cgroup usage is probably page cache from Badger's SST and value-log files. The kernel can usually reclaim that, but it still counts against the container. Related: GOMEMLIMIT only covers the Go heap. If it was also 1200MiB when the limit was 1 GiB, the Go runtime thought it had more room than the container did, and that alone could explain a lot of those 37 restarts. With 2 GiB and 1200MiB you have some headroom now, but it's worth keeping the gap between the two at least as big as your off-heap usage. To figure out which part is growing, the most useful thing is a series of samples running right up to an OOM, from at least one alpha:
A couple of questions:
On upgrading: v24.1.9 has #9221 and the posting-cache rework from #9237. The cache-sizing fixes (#9515, #9526) are only in v25.x. Since your posting-list cache is off, I wouldn't expect an upgrade alone to change this, but it's worth doing anyway. If you move to v25 and want the posting-list cache back on, it's now sized properly. If you can share a heap profile and a jemalloc dump from close to an OOM, I'm happy to take a look. |
|
Thanks for checking the logs. With no schema apply during the current run, #9301 can't explain alpha-1 reaching a memory.peak of 1.93 GiB. It could still explain Oct 19 and Oct 21, though. Both of those happened on a 2 GiB limit, and on Oct 21 all three alphas went down in the same hour. A schema update is applied through Raft, so every replica runs it at the same moment, which would fit. One quick check settles whether #9301 is possible on your setup at all. The size of that buffer depends on the alpha's open-file soft limit: If the soft limit is in the billions (1073741816 is common on containerd), a schema apply allocates about 2 GiB at once. If it's around 1M or less, the buffer is a few MiB and we can drop that theory. The 1.93 GiB peak may not be a spike. memory.peak tracks memory.current, and that includes page cache, both active and inactive. container_memory_working_set_bytes subtracts inactive_file. So a peak near the limit while working set stays around 820 MiB is consistent with inactive page cache filling the cgroup until the kernel reclaims it. That's normal kernel behavior. memory.events on the pod will show which one it was:
If max is non-zero and oom_kill is 0, the kernel reclaimed page cache and nothing was in danger. For the real OOM kills on Oct 19 and Oct 21, the kernel log on the node is the best evidence we can get. But I think you indicated those were no longer available? The OOM killer logs a line like To your questions about File-backed mem, no, there's no Dgraph or Badger setting that limits it. Badger in v24.0.5 (v4.3.0) always mmaps its SST tables, value log files, and memtable WAL files, with no option for plain file reads. I'd consider the growth expected. Each pod's file cache holds whatever that process has read recently. That depends on which queries land on it and which tables its compactions touched, and with no memory pressure the kernel has no reason to evict anything. So differences between replicas are normal and not worth chasing. It also shouldn't reduce your headroom for spikes much. Clean file pages, which is nearly all of Badger's read side, are reclaimed before the OOM killer runs. Dirty pages have to be written back first, but at your write rate there aren't many. The one real cost is that dashboards built on working set overstate how close you are to the limit. For the baseline question I'd focus on anon memory, which is what you're already doing. I found one thing in v24.0.5 worth checking related the this "gradual growth" issue. Besides the posting-list cache you've disabled, v24.0.5 has a second cache just for dgraph.type index keys (#9068), and --cache percentage doesn't turn it off. It holds a copy of the UID list for each type. Every commit that sets dgraph.type appends its delta to that copy, and only a rollup of that key clears it. At low write rates, those rollups can be delayed for a long time. Every GraphQL query roots at type(X), so every query reads from this cache and clones the pending deltas. The cache was removed in #9237, which shipped in v24.1.0. I'm not sure it's big enough on its own to account for 2–3 MiB/hour. It only grows when a mutation sets dgraph.type, which for GraphQL is mostly add mutations. If nearly all your writes are updates to existing nodes, this probably isn't it. The heap diff will tell us either way. Take /debug/pprof/heap a few hours apart and compare: If most of the growth is under Finally, if you have a staging environment I'd urge you to try v25.5.1. Among many small memory improvements was a pretty big change (#9515) to actually get the posting list cache to work correctly. My spidey sense tells me that you disabled the cache entirely to try to keep the alphas within their assigned memory space. Of course other things arrive on the heap, so it's not a hard upper limit but the posting list cache now works as intended. Finally (part 2) -- in the past I've seen rogue queries spike as you've described. Worth checking IMO, in v24 graphql responses have an |


Uh oh!
There was an error while loading. Please reload this page.
Gradual memory growth in Dgraph Alpha eventually leads to OOM kills
Hi Dgraph team,
We are running Dgraph v24.0.5 on Kubernetes with three Alpha replicas and are observing gradual memory growth over time, eventually leading to container OOM kills and restarts.
Environment
GOMEMLIMIT=1200MiBObserved behavior
Memory consumption tends to increase gradually over time rather than only during isolated spikes. Eventually, the container reaches its memory limit and is terminated with
OOMKilled(exit code 137). One Alpha pod had accumulated 37 restarts during our investigation.In one approximately two-hour observation window:
HeapAllocincreased from ~153 MiB to ~177 MiB.These measurements did not capture the eventual OOM event, so we cannot yet determine whether the growth is caused by the Go heap, Badger, memory-mapped files, or other process memory.
Relevant configuration
Alpha command flags:
We are testing the increased memory limit while keeping the cache and Badger settings unchanged.
Questions
We have attached a screenshot of the memory usage for reference and can provide sanitized logs or memory profiles if helpful.
Thanks for your guidance.
Attaching ScreenShot for reference.
All reactions