Skip to content
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

Replace string interner with an LRU and per-origin cache up top. #20943

Closed
wants to merge 18 commits into from

Conversation

lallydd
Copy link

@lallydd lallydd commented Nov 19, 2023

What does this PR do?

This is 1 of 3 PRs for moving strings out of the heap and into MMAP'd files.

  1. Per-origin LRU caches. No plumbing yet for origins (e.g. containers).
  2. mmap-file implementation (short but worth separate analysis)
  3. Plumbing - refcounting changes to agent, plumbing for origins.

Together they will provide explicit tracking of primary (strings in contexts
and metrics) memory use per container. Additionally, each container
will have a separate allocation pool drawing from temporary files on disk.

This should alleviate primary OOM concerns, as the primary driver for memory
use is taken out of the process's RSS. Memory pressure will result in IOPs
instead of agent termination. Each container's usage is separated and can be used
as a basis for throttling / backpressure decisions when IOPs go above desired
levels.

Motivation

This PR in isolation provides a better interner than we previously had, by gradual eviction
instead of dumping the entire map. It also provides tracking mechanisms for per-container
usage. It also provides a stub implementation of the mmap backend, to show how the
actual one would integrate. The next PR will show a linux implementation - although it should
be usable completely for a macos implementation. The interface exposed should allow a
windows CreateFileMapping() based implementation if desired.

Additional Notes

The agent should scale to handle higher workloads, but has the same deployment
for tiny and gigantic machines. The machines are almost exclusively idle and the
ample available resources are left unused by the agent due to its constant memory
cap and the workloads that can't use the machine for fear of overwhelming the agent.

Possible Drawbacks / Trade-offs

Describe how to test/QA your changes

Testcaess were provided, and the full test suite inv test was run on a linux-arm64 VM.

Reviewer's Checklist

  • If known, an appropriate milestone has been selected; otherwise the Triage milestone is set.
  • Use the major_change label if your change either has a major impact on the code base, is impacting multiple teams or is changing important well-established internals of the Agent. This label will be use during QA to make sure each team pay extra attention to the changed behavior. For any customer facing change use a releasenote.
  • A release note has been added or the changelog/no-changelog label has been applied.
  • Changed code has automated tests for its functionality.
  • Adequate QA/testing plan information is provided if the qa/skip-qa label is not applied.
  • At least one team/.. label has been applied, indicating the team(s) that should QA this change.
  • If applicable, docs team has been notified or an issue has been opened on the documentation repo.
  • If applicable, the need-change/operator and need-change/helm labels have been applied.
  • If applicable, the k8s/<min-version> label, indicating the lowest Kubernetes version compatible with this feature.
  • If applicable, the config template has been updated.

Copy link

cit-pr-commenter bot commented Nov 19, 2023

Go Package Import Differences

Baseline: 3f5d700
Comparison: 7508437

binaryosarchchange
agentlinuxamd64
+8, -0
+github.com/hashicorp/golang-lru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
agentlinuxarm64
+8, -0
+github.com/hashicorp/golang-lru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
agentwindowsamd64
+8, -0
+github.com/hashicorp/golang-lru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
agentwindows386
+8, -0
+github.com/hashicorp/golang-lru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
agentdarwinamd64
+8, -0
+github.com/hashicorp/golang-lru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
agentdarwinarm64
+8, -0
+github.com/hashicorp/golang-lru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
iot-agentlinuxamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
iot-agentlinuxarm64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
heroku-agentlinuxamd64
+8, -0
+github.com/hashicorp/golang-lru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
cluster-agentlinuxamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
cluster-agentlinuxarm64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
cluster-agent-cloudfoundrylinuxamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
cluster-agent-cloudfoundrylinuxarm64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
dogstatsdlinuxamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
dogstatsdlinuxarm64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
process-agentlinuxamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
process-agentlinuxarm64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
process-agentwindowsamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
process-agentdarwinamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
process-agentdarwinarm64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
heroku-process-agentlinuxamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
security-agentlinuxamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
security-agentlinuxarm64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
serverlesslinuxamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
serverlesslinuxarm64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
system-probelinuxamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
system-probelinuxarm64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
system-probewindowsamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
trace-agentlinuxamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
trace-agentlinuxarm64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
trace-agentwindowsamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
trace-agentwindows386
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
trace-agentdarwinamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
trace-agentdarwinarm64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog
heroku-trace-agentlinuxamd64
+9, -0
+github.com/hashicorp/golang-lru
+github.com/hashicorp/golang-lru/simplelru
+golang.org/x/text/feature/plural
+golang.org/x/text/internal/catmsg
+golang.org/x/text/internal/format
+golang.org/x/text/internal/number
+golang.org/x/text/internal/stringset
+golang.org/x/text/message
+golang.org/x/text/message/catalog

package cache

import (
"fmt"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's how we usually sort imports in this project

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just let the IDE do it. Is there a doc for what the preference is?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/DataDog/datadog-agent/blob/main/docs/dev/imports.md?plain=1
Note that this is not well-known and not enforced (but setting up your IDE to do it automatically is quick and helps having a consistent import order).

@@ -188,7 +189,7 @@ func (p *parser) parseMetricSample(message []byte) (dogstatsdMetricSample, error
}

return dogstatsdMetricSample{
name: p.interner.LoadOrStore(name),
name: p.interner.LoadOrStore(name, "", nil),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If LoadOrStore() is always called with , "", nil) is probably worth adding an helper that doesn't require those arguments

Copy link
Author

@lallydd lallydd Nov 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The third PR in the series has the plumbing - that includes useful values for all 3 args. Use the lally/exp-mem-metrefs branch for reference: https://github.com/DataDog/datadog-agent/blob/lally/exp-mem-metrefs/comp/dogstatsd/server/parse.go#L190

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think i'd be better to only add it when we need it no ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we could start passing in the origin IDs now (I'll have to start integrating the plumbing for this bit) so that we get per-origin (e.g. per-container) tracking now with this PR. How does that sound?


// backingBytesPerInCoreEntry is the number of bytes to allocate in the mmap file per
// element in our LRU. E.g., some value of initialInternerSize * POW(growthFactor, N).
const backingBytesPerInCoreEntry = 4096
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we want this to be configurable no ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is set to match the page size on the platform. For the MMUs in x86 and ARM, thats 4k (hugepages goes bigger).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI os.Getpagesize() is available

)

// initialInternerSize is the size of the LRU cache (in #strings). This is HEAP, so
// don't let this get too big compared to the MMAP region.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't mention "MMAP" here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a series of 3 PRs, staged. Do you expect me to rewrite the first two as if there are no successors?

@@ -38,7 +39,7 @@ var (
// parser parses dogstatsd messages
// not safe for concurent use
type parser struct {
interner *stringInterner
interner *cache.KeyedInterner
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't remove the old interner implementation, do we want instead to add an interface that will (for a time) let us choose between one implementation or the other ?

This will make sure all this code stays optional until we know it makes performances better

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will want to provide a write-up of how this change impacts low and high-end use cases, see this notebook as an example. If we are going to swap out the implementation like this I'd be interested in seeing the regression detector run here but I also think if the swap were configurable it'd be easier to rig experiments to understand the user implications of the work being proposed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone can pass me appropriate API keys (the back-to-back summits have made this tricky) for ddev, I can get the benchmark to start uploading data to the benchmark notebook.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can help you run this in the aml benchmark cluster, but I'd also like to see this run in the SMP regression detector.

once this passes enough of the lint/test/package build steps, the regression detector should run automatically on this PR.

const noFileCache = ""

// OriginTimeSampler marks allocations to the Time Sampler.
const OriginTimeSampler = "!Timesampler"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this really be defined here ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are for internal diagnostic use. I don't care where they go, but it's easier to see the full list if they're all in one place. I can move them to their use sites if preferred.

)

// MaxValueSize is the largest possible value we can store.
const MaxValueSize = 4080
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does it come from ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copied from the mmap_hash_linux, which is based on hardware constraints & datastructure decisions that I don't expect to change across platforms.

https://github.com/DataDog/datadog-agent/blob/lally/exp-mem-metrefs/pkg/util/cache/mmap_hash_linux.go#L52

pkg/util/cache/intern.go Outdated Show resolved Hide resolved
@iksaif
Copy link
Contributor

iksaif commented Nov 20, 2023

High level comment before going further: can you remove all the mmap code and just keep the in-memory part ? I think we want to validate performances and behavior with the in-memory version before adding the mmap complexity.

Also I don't think we want to shard by origin with origin being OriginTimeSampler or such no? If we want to shard it's probably going by tenant no (a mix or dogstatsd container origin and listener id ?) ?

pkg/util/cache/intern.go Outdated Show resolved Hide resolved

// LoadOrStore interns a byte-array to a string, for an origin
func (i *KeyedInterner) LoadOrStore(key []byte, origin string, retainer InternRetainer) string {
sGlobalQueryCount.Add(1)
Copy link
Contributor

@blt blt Nov 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me that this needs to be atomic. We increment here only -- and you've got it atomic to skip taking the lock that's implicit in loadOrStore -- but the sum is only used in a debug log. I guess, how accurate does this need to be? This is going to be a sequentially consistent operation: we're forcing all CPUs to sync, then we skip some takes of the mutex, then we force all the CPUs to sync again (on x86, better on ARM).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using a guideline of atomic operations on MESIF 'E' states being ~8ns in the common (l1 uncontested) case https://arxiv.org/pdf/2010.09852.pdf

I think a good part of that 8ns is still the regular unexclusive op.

Generally, I just wanted rough statistics without too much bother :)

Copy link
Contributor

@blt blt Nov 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair, but in a large piece of software like the Agent as core counts ramp it's not always clear that atomics cheap in the micro are cheap in the macro. A nit flag since we don't have data one way or the other yet and there's other low hanging sync issues in the Agent yet.

if Check(s) {
return i.LoadOrStore(unsafe.Slice(unsafe.StringData(s), len(s)), origin, retainer)
}
sFailedInternalCount.Add(1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flagging another potential seqcst here. Although presumably rare if most strings are valid, pushing known invalid strings does ramp cost.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only way they fail the Check() is if there's a fairly severe bug. I've been catching them with this function. sFailedInternalCount is zero on my internal testing. But I thought to leave the diagnostics in for any future changes that might need it.

I'm happy to take them out, or to hide behind a config flag.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, both are nit flags and this instance is less likely to fire than the other.

pkg/util/cache/intern.go Outdated Show resolved Hide resolved
pkg/util/cache/intern.go Outdated Show resolved Hide resolved
pkg/util/cache/lru_cache.go Show resolved Hide resolved
pkg/util/cache/lru_cache.go Outdated Show resolved Hide resolved
pkg/util/cache/lru_cache.go Outdated Show resolved Hide resolved
pkg/util/cache/lru_cache.go Outdated Show resolved Hide resolved
@blt blt added this to the 7.51.0 milestone Nov 21, 2023
@blt blt added changelog/no-changelog [deprecated] qa/skip-qa - use other qa/ labels [DEPRECATED] Please use qa/done or qa/no-code-change to skip creating a QA card team/single-machine-performance Single Machine Performance labels Nov 21, 2023
@lallydd
Copy link
Author

lallydd commented Nov 21, 2023

High level comment before going further: can you remove all the mmap code and just keep the in-memory part ? I think we want to validate performances and behavior with the in-memory version before adding the mmap complexity.

Also I don't think we want to shard by origin with origin being OriginTimeSampler or such no? If we want to shard it's probably going by tenant no (a mix or dogstatsd container origin and listener id ?) ?

This is the in-memory part. The mmap code is (a) disabled by default and (b) stubbed out here.

I reserved the ! prefix to bucket them all into the same place, I'll add that change here.

@lallydd
Copy link
Author

lallydd commented Nov 22, 2023

Hi I still have to figure out what has happened in this go.mod rabbit hole. Also still need to integrate changes from #20420.

@lallydd lallydd force-pushed the lally/mem-cacheonly branch from ff93ef4 to 6468dc7 Compare November 22, 2023 15:09
@lallydd
Copy link
Author

lallydd commented Nov 22, 2023

I put everything behind a switch & interface after talking to @blt and @iksaif. Telemetry w.r.t. #20420 should be comparable (there aren't any resets, but there are drops, and they're different metrics).

@brycekahle
Copy link
Member

AFAIK the OOM killer uses Working Set Memory for decisions (at least in k8s/namespaces), which does include mmap-ed regions. Is this being taken into account?

@lallydd
Copy link
Author

lallydd commented Nov 27, 2023

Sorry, can you point to any docs where working set includes mmap-ed files? AFAIK it doesn't include the page cache used by the container. I can understand why it'd use anonymously-mmap'd memory, but file-backed mmaps should morally count as page cache only. I can dig into the appropriate sources tomorrow to get a canonical answer.

@brycekahle
Copy link
Member

I can understand why it'd use anonymously-mmap'd memory, but file-backed mmaps should morally count as page cache only

I don't have extensive knowledge of how it treats the different types. I do know the fd-backed (but not real files) mmap regions we use when interacting with the perf subsystem are accounted for in WSS. They are created with the MAP_SHARED flag.

@lallydd
Copy link
Author

lallydd commented Nov 29, 2023

I think the in memory LRU part looks great, but I'd really like to see micro-benchmarks (or real life benchmarks) before merging such a large amount of code (the regressions tests are useful, but I don't think they dispense us from actual benchmarks) - it should not take too long and they will be useful to later PRs on this part.

Ah! I think that's where we're missing each other. The regression detector is a performance regression detector - AFAIK it's a series of benchmarks to detect negative changes in performance.

Related to the MMAP code, I also have the intuition that it should be useful, but I think before we merge anything related to mmap it would be very useful to do a build of your changes and test it with a known-to-be-memory-consuming scenarios and gather some data supporting the idea (similar to @brycekahle's comment).

It's all in https://github.com/DataDog/datadog-agent/tree/lally/exp-mem-metrefs - I've developed it against the stress-test benchmark. I agree it's necessary to verify the behavior against the memory-heavy situations. Especially when the current agent OOMs. I've been, frankly, bashing my head against the wall on the go mod / linter situation but that's clearing up now. I'll start taking a look on setting that up.

Maybe It could be split out of this PR if the in-memory LRU interner already has demonstrated value for us ? (not blocking, just a suggestion)

The mmap changes aren't in this PR. There's some hooks for the mmap changes to go in, but the mmap changes aren't there. See pkg/util/cache/mmap_hash.go - it's a stub that'll be used for non-linux platforms. The linux impl isn't in this PR.

Another open question (not blocking this PR) is how does it fit in the big picture of memory usage within the agent ? What if next it's the python checks that are taking all the memory ? We can't trigger backpressure on statsd because python checks are using memory, that would be unfair. I think it would be interesting to see that written down somewhere (for context, it's the issue that the current memory limiter has)

That's a fine question. Generally, we want to find ways to throttle the component making us use excessive amounts of memory. I know nothing about the python checks. For them, the questions are: what controls do we have on them? If the checks' results linger in memory for a while, we can try reducing frequency. Or we can try to find ways to contain its usage (e.g., move parallel ops to serial, split up the work into batches, run in a separate container with a mem limit and control over an IPC bridge, etc).

@lallydd lallydd force-pushed the lally/mem-cacheonly branch from 3992888 to c7a9567 Compare November 29, 2023 18:02
@blt blt requested a review from a team as a code owner November 29, 2023 18:37
Signed-off-by: Brian L. Troutwine <[email protected]>
@val06
Copy link
Contributor

val06 commented Nov 30, 2023

@val06 thanks these are great. When we're near OOM-killing state in the mmap-enabled situation, where I expect that the container limits block is set quite high or unset, I think this is when we should look for strategies to split up the larger mmaps (>100 MiB?) into many files that we can open/close -- perhaps with LRU. That'll keep more of that dataset in the inactive list. WDYT?

Yes I also consider this scenario as the main candidate for benefits of using mmap as a backend for string interning. One of the main gains (subject to PoC'ing) is avoiding OOMs caused by k8s hard memory limits and GO's gc behavior. For reference: in System-Probe, we are using this pkg for string interning (in heap, no LRU)

@kacper-murzyn kacper-murzyn modified the milestones: 7.51.0, 7.52.0 Jan 8, 2024
@kacper-murzyn kacper-murzyn modified the milestones: 7.52.0, 7.53.0 Feb 18, 2024
@scottopell scottopell closed this Mar 5, 2024
@dd-devflow dd-devflow bot deleted the lally/mem-cacheonly branch May 31, 2024 00:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
7.51.0-drop 7.52.0-drop changelog/no-changelog [deprecated] qa/skip-qa - use other qa/ labels [DEPRECATED] Please use qa/done or qa/no-code-change to skip creating a QA card team/single-machine-performance Single Machine Performance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants