-
Notifications
You must be signed in to change notification settings - Fork 9.8k
Labels: reduce memory by de-duplicating strings in multiple SymbolTables #12304
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
Conversation
Can you explain in a few words how this deals with the churn of series in the Head? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking a look!
Can you explain in a few words how this deals with the churn of series in the Head?
Not implemented yet. Suggestion: after head compaction and series GC, create a new SymbolTable
then run through all live series and rebuild their Labels
.
Then reset the SymbolTable
s used by scraping parsers.
model/labels/labels_string.go
Outdated
var aName, bName string | ||
aName, ia = decodeString(a.data, ia) | ||
bName, ib = decodeString(b.data, ib) | ||
aName, ia = decodeString(a.syms, a.data, ia) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the two symbol tables are equal, would it be possible to avoid decoding label symbols and compare them directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For Equal
, yes, but not Compare
since we don’t have an ordering in the symbol table.
Might be worth skipping all the strings that are equal and just decoding the first one that’s different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point, decoding only what is different would probably speed things up when doing a merge of two sorted label sets. For example, when merging data from two TSDBs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar idea for current main: #12451
edc771c
to
d7f1d83
Compare
061364b
to
c6bf0fd
Compare
f3164cb
to
b1b4874
Compare
I have rebased the code, and made some important updates since the last review:
|
b1b4874
to
b43402d
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
Memory usage is ~20% down, which is great; CPU and latency of queries is up a bit which I shall look into. |
model/labels/labels_internlabels.go
Outdated
// ToName maps an integer to a string. | ||
func (t *nameTable) ToName(num int) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this should be ToValue
, but it's opinionated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given we have explicit names and values for labels, I don't think I should prefer one here.
I can't remember why it's a "name table" and not a "string table".
model/labels/labels_internlabels.go
Outdated
// Idea: have a constant symbol for blank, then we don't have to look it up. | ||
blank, ok := ls.syms.symbolTable.byName[""] | ||
if !ok { // Symbol table has no entry for blank - none of the values can be blank. | ||
return ls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that the "Idea:" would perform worse, as if we don't look it up, we have to check whether the constant symbol is present in data
. I like this hack.
Edit: maybe if the symbol table is too broad, we always have the empty symbol...
Edit2: actually with a constant symbol like 0x00 we could optimize the first pass lookup checking whether there's a 0-byte in the entire data slice (we would still need to decode varints if it's present, unless we avoid using any 0x00-ending positions in the symbol table)
96f1664
to
e1a741a
Compare
I've tested v2.51.0 with dedupelabels tag on one server (green) and compared it with another server running v2.51.0 with stringlabels tag.
|
Thanks for looking at this. I really need a bit more context for the profile, to see where those functions were called. Could you upload the raw data? |
Svg is better but I still can't see the line numbers. However in this case I think it's enough; the main growth is under the scrape loop. |
I suspect it's the
Could it be possible that SymbolTable instances are being kept referenced there for too long (forever?) ? |
Yes that is a good point: I could flush the cache at the point it decides to discard the old SymbolTable. |
I wonder if the unique package added to the next go release would help here - https://pkg.go.dev/unique@master |
That one gets you to 8 bytes per string, or 16 per label. However, it might be preferable considering the amount of work needed to get this one to behave. |
Small update: (If you have a lot of long and non-unique labels then your saving will look better) |
The new Docker image is |
Not yet, I will give it a try soon |
I have a pair of identical servers running 2.54.0 - one with stringlabels & the other with dedupelabels, I think I need to give it a week or two to tell anything conclusive since last time I tried it it was slowly using more memory over time - #12304 (comment) So far the heap profile looks good, without SymbolTable getting to the top:
|
Hi, I hope to give a lightning update at PromCon today - please post any statistics from people trying it out. |
I'm on leave most of September so I didn't pay much attention to these instances but here's a screenshot of a comparison for our two instances - one vanilla and the other is deduplabels. |
Here's a latest screenshot. Although there is a reduction in memory usage dedupelabels is a lot more unpredictable with how much memory will be used, compared to pretty much flat memory usage of stringlabels, which is not unexpected given how dedupelabels works. |
If I understand your charts correctly, there is a point around 09/16 where A profile at the time where memory goes higher would be useful. |
This builds on #10991, implementing "Option 5" in the design doc.
The code adds a new
labels.Labels
implementation which can be selected at build time with-tags dedupelabels
.Previous attempts to do string interning in Prometheus have used a single lookup table for the entire program; this PR allows different sets of labels to have their own tables. Example: where you fetch series via remote-read, or receive series via remote-write with a symbol-table (see #11999).
Most of the code changes outside of the
model/labels
package are to manage the symbol tables.Managing concurrent access to symbol tables:
sync.Mutex
is locked around reads and writes.This branch includes two other PRs #13452 and #13491.Update: now merged.