Skip to content

lib/logstorage: fix uniq_values merge in cluster queries#1384

Open
cuongleqq wants to merge 5 commits into
VictoriaMetrics:masterfrom
cuongleqq:fix-uniq-values-empty-state-merge
Open

lib/logstorage: fix uniq_values merge in cluster queries#1384
cuongleqq wants to merge 5 commits into
VictoriaMetrics:masterfrom
cuongleqq:fix-uniq-values-empty-state-merge

Conversation

@cuongleqq
Copy link
Copy Markdown
Contributor

Fixes #1383

This PR fixed uniq_values() state merging when the destination state has an empty map and the source state has values (See the issue for how to reproduce).

I fixed this bug by simply create a new map if the destination map is currently nil.

I have a question though:

There is an optimization I think of when I fix this bug: In the sup.m == nil case, we could avoid allocation by moving src.m directly to sup.m instead of allocating a new map and copying keys.

I kept the safer copy-based approach for now to avoid changing semantics. Would you prefer the map moving optimization here? If so, I can make anothe commit.

Thanks.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

@func25 func25 added the bug Something isn't working label May 14, 2026
Copy link
Copy Markdown
Contributor

@valyala valyala left a comment

Choose a reason for hiding this comment

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

It would be great adding a test for this case. The best type of the test would be an integration test inside apptest folder for the VictoriaLogs cluster setup. You can add the needed logs to one vmstorage node, while skipping adding the logs to another vmstorage node, and then issue a select query and verify its results. Note that you can ingest logs individually per every vmstorage node in the same way like you do for a single-node VictoriaLogs according to these docs.

Comment thread lib/logstorage/stats_uniq_values.go Outdated
}

src := sfp.(*statsUniqValuesProcessor)
if len(src.m) == 0 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It is OK to skip this check - the loop below will work correctly and fast on empty map and on nil map.

Copy link
Copy Markdown
Contributor Author

@cuongleqq cuongleqq May 15, 2026

Choose a reason for hiding this comment

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

I kept the check because:

  1. It avoids allocating an empty map on a nil destination when the source is empty or nil.
  2. I am not entirely sure whether merging two nil maps should result in an empty map.

That said, the allocation is tiny and the case should not happen often, so I am fine with dropping the check if you prefer the simpler flow.

@valyala
Copy link
Copy Markdown
Contributor

valyala commented May 14, 2026

There is an optimization I think of when I fix this bug: In the sup.m == nil case, we could avoid allocation by moving src.m directly to sup.m instead of allocating a new map and copying keys.

There is no need in this optimization here, since merging the state shouldn't be a performance bottleneck here. This optimization will make the code more fragile without achieving visible performance benefits.

@valyala
Copy link
Copy Markdown
Contributor

valyala commented May 14, 2026

@cuongleqq , could you also check other LogsQL pipes for the same type of bug, and fix it in separate pull requests?

@cuongleqq
Copy link
Copy Markdown
Contributor Author

@cuongleqq , could you also check other LogsQL pipes for the same type of bug, and fix it in separate pull requests?

@valyala it seems like only uniq_values has this problem.

I checked other stats functions and LogsQL pipes with similar map-based merge paths.

Other stats functions initialize the destination map before writing to it, either via helpers:

func mergeUint64Set(dstPtr *map[uint64]struct{}, src map[uint64]struct{}, stopCh <-chan struct{}) {
if len(src) == 0 {
return
}
dst := *dstPtr
if dst == nil {
dst = make(map[uint64]struct{})
*dstPtr = dst
}

or directly:

for vmrange, count := range src.bucketsMap {
if shp.bucketsMap == nil {
shp.bucketsMap = make(map[string]uint64)
}
shp.bucketsMap[vmrange] += count

Other pipes also have safe initialization patterns.

field_names and stream_context use getM() to initialize the destination map before merging:

func (shard *pipeFieldNamesProcessorShard) getM() map[string]*uint64 {
if shard.m == nil {
shard.m = make(map[string]*uint64)
}
return shard.m
}

Pipes that use hitsMap are also safe, because missing keys are inserted through setStateUint64, setStateNegativeInt64, and setStateString, which initialize the corresponding map before writing:

func (hm *hitsMap) mergeState(src *hitsMap, stopCh <-chan struct{}) {
for n, pHitsSrc := range src.u64 {
if needStop(stopCh) {
return
}
pHitsDst := hm.u64[n]
if pHitsDst == nil {
hm.setStateUint64(n, pHitsSrc)
} else {
*pHitsDst += *pHitsSrc
}
}
for n, pHitsSrc := range src.negative64 {
if needStop(stopCh) {
return
}
pHitsDst := hm.negative64[n]
if pHitsDst == nil {
hm.setStateNegativeInt64(int64(n), pHitsSrc)
} else {
*pHitsDst += *pHitsSrc
}
}
for k, pHitsSrc := range src.strings {
if needStop(stopCh) {
return
}
pHitsDst := hm.strings[k]
if pHitsDst == nil {
hm.setStateString(k, pHitsSrc)
} else {
*pHitsDst += *pHitsSrc
}
}
}

So I do not see another similar nil map merge issue at the moment.

@cuongleqq
Copy link
Copy Markdown
Contributor Author

It would be great adding a test for this case. The best type of the test would be an integration test inside apptest folder for the VictoriaLogs cluster setup. You can add the needed logs to one vmstorage node, while skipping adding the logs to another vmstorage node, and then issue a select query and verify its results. Note that you can ingest logs individually per every vmstorage node in the same way like you do for a single-node VictoriaLogs according to these docs.

Done. I have added TestVlclusterUniqValuesMerge in apptest/tests/vlcluster_test.go. Thanks.

Signed-off-by: Cuong Le <cuongleqq@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Panic in statsUniqValuesProcessor.mergeState: assignment to entry in nil map

3 participants