Skip to content

perf: Use map for InPredicate when reading dataobj - #18325

Merged
benclive merged 3 commits into
mainfrom
benclive/use-map-for-in-predicate
Jul 7, 2025
Merged

perf: Use map for InPredicate when reading dataobj#18325
benclive merged 3 commits into
mainfrom
benclive/use-map-for-in-predicate

Conversation

@benclive

@benclive benclive commented Jul 3, 2025

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:
When we use a large list of stream IDs, dataobjs spend a lot of time running CompareValues on each element of the Values list. Here I'm replacing it with a map of the underlying value (hence, it only supports int64, uint64 and byte arrays) and simply skipping the check if its not a relevant type.
For this benchmark, its 30% faster but when querying with many stream IDs I've seen my queries get >10x faster.

$ benchstat before_in.txt after_in.txt 
goos: darwin
goarch: arm64
pkg: github.com/grafana/loki/v3/pkg/logql/bench
cpu: Apple M3 Max
                                                                                                                                       │ before_in.txt │            after_in.txt             │
                                                                                                                                       │    sec/op     │   sec/op     vs base                │
LogQL/query=sum_by_(env,_component)_(rate({region="ap-southeast-1"}_|_detected_level=~"error|warn"_[5m]))/kind=metric/store=dataobj-14    11.465 ± 15%   7.758 ± 15%  -32.33% (p=0.000 n=10)
LogQL/query=sum_by_(env,_component)_(rate({region="ap-southeast-1"}_|_detected_level=~"error|warn"_[5m]))/kind=metric/store=chunk-14       3.319 ±  2%   3.294 ±  3%        ~ (p=0.529 n=10)
geomean                                                                                                                                    6.168         5.055        -18.05%

                                                                                                                                       │   before_in.txt    │                after_in.txt                 │
                                                                                                                                       │ kilobytesProcessed │ kilobytesProcessed  vs base                 │
LogQL/query=sum_by_(env,_component)_(rate({region="ap-southeast-1"}_|_detected_level=~"error|warn"_[5m]))/kind=metric/store=dataobj-14          430.6k ± 0%          430.6k ± 0%       ~ (p=1.000 n=10) ¹
LogQL/query=sum_by_(env,_component)_(rate({region="ap-southeast-1"}_|_detected_level=~"error|warn"_[5m]))/kind=metric/store=chunk-14            500.5k ± 0%          500.5k ± 0%       ~ (p=1.000 n=10) ¹
geomean                                                                                                                                         464.2k               464.2k       +0.00%
¹ all samples are equal

                                                                                                                                       │ before_in.txt  │              after_in.txt               │
                                                                                                                                       │ linesProcessed │ linesProcessed  vs base                 │
LogQL/query=sum_by_(env,_component)_(rate({region="ap-southeast-1"}_|_detected_level=~"error|warn"_[5m]))/kind=metric/store=dataobj-14      7.881M ± 0%      7.881M ± 0%       ~ (p=1.000 n=10) ¹
LogQL/query=sum_by_(env,_component)_(rate({region="ap-southeast-1"}_|_detected_level=~"error|warn"_[5m]))/kind=metric/store=chunk-14        1.478M ± 0%      1.478M ± 0%       ~ (p=1.000 n=10) ¹
geomean                                                                                                                                     3.412M           3.412M       +0.00%
¹ all samples are equal

                                                                                                                                       │  before_in.txt  │               after_in.txt               │
                                                                                                                                       │ postFilterLines │ postFilterLines  vs base                 │
LogQL/query=sum_by_(env,_component)_(rate({region="ap-southeast-1"}_|_detected_level=~"error|warn"_[5m]))/kind=metric/store=dataobj-14       822.8k ± 0%       822.8k ± 0%       ~ (p=1.000 n=10) ¹
LogQL/query=sum_by_(env,_component)_(rate({region="ap-southeast-1"}_|_detected_level=~"error|warn"_[5m]))/kind=metric/store=chunk-14         1.478M ± 0%       1.478M ± 0%       ~ (p=1.000 n=10) ¹
geomean                                                                                                                                      1.103M            1.103M       +0.00%
¹ all samples are equal

                                                                                                                                       │ before_in.txt │             after_in.txt             │
                                                                                                                                       │     B/op      │     B/op       vs base               │
LogQL/query=sum_by_(env,_component)_(rate({region="ap-southeast-1"}_|_detected_level=~"error|warn"_[5m]))/kind=metric/store=dataobj-14   1.575Gi ± 41%   1.461Gi ± 86%       ~ (p=0.684 n=10)
LogQL/query=sum_by_(env,_component)_(rate({region="ap-southeast-1"}_|_detected_level=~"error|warn"_[5m]))/kind=metric/store=chunk-14     720.9Mi ±  1%   720.7Mi ±  1%       ~ (p=0.529 n=10)
geomean                                                                                                                                  1.053Gi         1.014Gi        -3.72%

                                                                                                                                       │ before_in.txt │            after_in.txt            │
                                                                                                                                       │   allocs/op   │  allocs/op   vs base               │
LogQL/query=sum_by_(env,_component)_(rate({region="ap-southeast-1"}_|_detected_level=~"error|warn"_[5m]))/kind=metric/store=dataobj-14     15.87M ± 0%   15.87M ± 1%       ~ (p=0.579 n=10)
LogQL/query=sum_by_(env,_component)_(rate({region="ap-southeast-1"}_|_detected_level=~"error|warn"_[5m]))/kind=metric/store=chunk-14       5.470M ± 0%   5.470M ± 0%       ~ (p=0.796 n=10)
geomean                                                                                                                                    9.317M        9.317M       +0.00%
@benclive
benclive requested a review from a team as a code owner July 3, 2025 11:47
Comment thread pkg/dataobj/internal/dataset/reader.go Outdated
return found */

value := row.Values[columnIndex]
if value.Type() != p.Column.ColumnInfo().Type {

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.

this could fail for Nil values?

Comment thread pkg/dataobj/internal/dataset/reader.go Outdated
Comment on lines +344 to +346
case datasetmd.VALUE_TYPE_BYTE_ARRAY:
_, ok = p.ValuesMap[value.ByteArray()]
return ok

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should panic at runtime; Go maps don't support being keyed with a slice. This compiles because the slice is being casted to an interface{} / any, but it'll fail at runtime when the runtime tries to hash the slice value for populating the entry in the map.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good spot - fixed using your suggestion

@benclive benclive changed the title pref: Use map for InPredicate when reading dataobj Jul 4, 2025
@benclive
benclive requested review from ashwanthgoli and rfratto July 4, 2025 06:48

@rfratto rfratto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, two small things that need to be addressed but this is good to merge!

Does Ashwanth's comment about nulls need to be addressed still?

Values: NewInt64ValueSet([]Value{
Int64Value(150),
Int64Value(600),
}), // 2 values in range. ~200 matching rows

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this comment correct? the test name is that "all values are outside range"

Comment thread pkg/dataobj/sections/logs/reader.go Outdated
for i := range p.Values {
vals[i] = arrowconv.FromScalar(p.Values[i], mustConvertType(p.Values[i].DataType()))
value := arrowconv.FromScalar(p.Values[i], mustConvertType(p.Values[i].DataType()))
values = append(values, value)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This will break now; the length of the array is already p.Values, so appending to it will have the length be p.Values*2 after the end of this loop. (I guess there's no test for this path?)

Comment thread pkg/dataobj/sections/streams/reader.go Outdated
for i := range p.Values {
vals[i] = arrowconv.FromScalar(p.Values[i], mustConvertType(p.Values[i].DataType()))
value := arrowconv.FromScalar(p.Values[i], mustConvertType(p.Values[i].DataType()))
values = append(values, value)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same comment as above: this should be either assigning directly to values[i] or changing values to have a length of zero with a capacity of len(p.Values).

@benclive
benclive merged commit a238816 into main Jul 7, 2025
112 of 116 checks passed
@benclive
benclive deleted the benclive/use-map-for-in-predicate branch July 7, 2025 08:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3 participants