-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
High memory usage on Nomad agent due to static buffer in template client? #18508
Comments
Hi @deverton-godaddy! First, thanks so much for showing up with a heap profile here! 😀 I think you're hitting an unfortunate design constrain around The heap profile you're showing shows 1272MiB of heap being used by these buffers, so I suspect maybe only ~600 of the 1000 templates are querying Nomad services or variables. Given all that:
|
Still chatting with the team about this, but in the meanwhile I did a quick smoke test cranking down that buffer to only 10k and it worked just fine even with Variables that were 4x that size (as I'd expect). Obviously that means more read calls on that buffer so I'm not totally certain we'd recommend that yet without doing some more testing about the impact over many allocations. But if you wanted to try it yourself on a client the diff was: diff --git a/helper/bufconndialer/bufconndialer.go b/helper/bufconndialer/bufconndialer.go
index 2a7650d57d..22acb22758 100644
--- a/helper/bufconndialer/bufconndialer.go
+++ b/helper/bufconndialer/bufconndialer.go
@@ -24,7 +24,7 @@ type BufConnWrapper struct {
// New returns a new BufConnWrapper with a new bufconn.Listener. The wrapper
// provides a dialer for creating connections to the listener.
func New() (net.Listener, *BufConnWrapper) {
- ln := bufconn.Listen(1024 * 1024)
+ ln := bufconn.Listen(1024 * 10)
return ln, &BufConnWrapper{listener: ln}
} (Or split the difference and do |
In #12458 we added an in-memory connection buffer so that template runners that want access to the Nomad API for Service Registration and Variables can communicate with Nomad without having to create a real HTTP client. The size of this buffer (1 MiB) was taken directly from its usage in Vault, and each connection makes 2 such buffers (send and receive). Because each template runner has its own connection, when there are large numbers of allocations this adds up to significant memory usage. The largest Nomad Variable payload is 64KiB, and a small amount of metadata. Service Registration responses are much smaller, and we don't include check results in them (as Consul does), so the size is relatively bounded. We should be able to safely reduce the size of the buffer by a factor of 10 or more without forcing the template runner to make multiple read calls over the buffer. Fixes: #18508
After some discussion with the team, I've opened #18524 with a draft PR setting the buffer to 100KiB so as to allow reading the largest Variable with a single read call. |
I'll dig in to why we see a much higher RSS at some point because I was also surprised that the heap profile didn't match with what we see at the system level. I figured I'd report the obvious outlier object first as that seemed like it might be an easy fix which it seems like it might be. You mention that "each running template is its own instance of consul-template" which suggests if a job has say 4 templates there's 4 connections so 4 MB of buffers (before the patch)? How feasible is it that this might eventually be converted to a single (or a pool) of shared buffers? At the density and template use we're targeting, even the 10x reduction in memory usage might not be enough. |
In #12458 we added an in-memory connection buffer so that template runners that want access to the Nomad API for Service Registration and Variables can communicate with Nomad without having to create a real HTTP client. The size of this buffer (1 MiB) was taken directly from its usage in Vault, and each connection makes 2 such buffers (send and receive). Because each template runner has its own connection, when there are large numbers of allocations this adds up to significant memory usage. The largest Nomad Variable payload is 64KiB, and a small amount of metadata. Service Registration responses are much smaller, and we don't include check results in them (as Consul does), so the size is relatively bounded. We should be able to safely reduce the size of the buffer by a factor of 10 or more without forcing the template runner to make multiple read calls over the buffer. Fixes: #18508
Correct.
We're probably looking to make template runners more isolated, rather than less, as the sandbox the runners are in currently is all code in the agent, and this has been a historic source of privilege escalation bugs we'd like to solve by design rather than relying on being infallible developers (:grinning:). Having something more like what we've done with But we don't want to do that kind of thing and then make the memory usage problem worse, for sure, so we'd likely need to rework a lot of the |
Nomad version
Operating system and Environment details
Amazon Linux 2 on amd64 and aarch64
Issue
The Nomad agent has surprisingly high memory usage for our use case. We're seeing 8 GB of memory usage for about 1000 allocations on a single machine.
Reproduction steps
Not entirely sure what the exact scenario that causes this, but possible it's related to the number of templates. Each of the 1000 tasks has one template which for reasons discussed below, matches some of the memory usage numbers.
Expected Result
Not using 8GB of RAM for a 1000 allocations with one template.
Actual Result
We enabled pprof and grabbed a heap dump. The top 10 objects are shown below which has a pretty large outlier.
Tracing this through the code lands on this line
ln := bufconn.Listen(1024 * 1024)
whichHowever the only place that seems to call it is
agent.setupClient()
which should be called once at startup.So I'm a bit stumped on how we end up with so many instances of the buffer.
The full profile chart is below
The text was updated successfully, but these errors were encountered: