Files
Martin DisibioandGitHub 8e57f1972c vParquet5 Preview 1 - low resolution timestamp columns (#5495)
* Experiments with next parquet version. Added rounded start time column

* Vparquet5 use lower precision timestamp column when possible based on step interval

* microoptimization

* leftjoiniterator use dynamic branch reordering, plus other performance improvements

* Add columns with other granularities

* Add new functionality: nocompactflg and short trace ID search

* Fix handling of large step intervals and split between backend and generator requests

* Repair tests and reduce divergence from main

* Cleanup, separate benchmark env vars

* Set version string to preview

* rename/comments

* Oops undo change to default encoding

* Fix tests and lint

* Bring in more updates from vparquet4

* Improve accuracy of estimate function, cleanup debug statements

* Reduce diff from main

* lint

* Add test parquet file

* Fix to maintain nano-second precision for blocks overlapping query range edges and recent/backend handoff where things don't align

* Fix tests after merging main, one more fix to maintain nanosecond precision when needed

* changelog

* Cleanup/reduce diff from main

* Review feedback
2025-08-18 15:06:58 -04:00

84 lines
3.5 KiB
Go

package vparquet5
import "github.com/grafana/tempo/pkg/traceql"
// coalesceConditions reduces the amount of data pulled from the backend when the same column is used in multiple conditions
//
// todo: There is a lot that can be done with this method. for example we can coalesce conditions if they have equivalent
// operands. consider a query like: { span.foo >= 1 } && { span.foo = 1 } the conditions could be coalesced to only
// the >= condition. it also is currently ignoring situations where allconditions is true, but some of these
// improvements apply to that case as well.
func coalesceConditions(f *traceql.FetchSpansRequest) {
// only do this if all conditions is false for now. it's safer and all conditions queries tend to be quite quick
if !f.AllConditions {
prevLen := 0
for len(f.Conditions) != prevLen { // check combinations until we can't coalesce any more
prevLen = len(f.Conditions)
// search for conditions with the same attribute name and consider coalescing them
for i := 0; i < len(f.Conditions); i++ {
for j := i + 1; j < len(f.Conditions); j++ {
if c, ok := coalesce(f.Conditions[i], f.Conditions[j]); ok {
f.Conditions[i] = c
f.Conditions = append(f.Conditions[:j], f.Conditions[j+1:]...)
j--
}
}
}
}
}
}
// coalesce takes two conditions and turns them into one. it returns a bool to indicate
// if the returned condition is valid or if it should just continue using the original 2 conditions
//
// it is very difficult to coalesce conditions in a way that will always be more performant at the fetch layer
// consider: { resource.service.name = "foo" || resource.service.name = "bar" }
// if foo and bar are rare then it will be faster to double pull the column and intersect the results. if
// foo and bar are common then it will be faster to single pull the column and allow the engine to do the work.
// currently the fetch layer does not support the = condition with multiple operands. we should add capability
// and then extend this method.
//
// therefore! we will only coalesce conditions in the following cases:
// - they are exactly the same
// - they will pull every span anyway. example: { span.foo = "bar" } >> { span.foo != "bar" }
func coalesce(c1, c2 traceql.Condition) (traceql.Condition, bool) {
// if the conditions are exactly the same then we can just return one of them
if c1.Attribute == c2.Attribute &&
c1.Op == c2.Op &&
operandsEqual(c1, c2) {
return c1, true
}
// if the operations are != and = and the operands are the same this is going to pull every row. let's
// collapse to one condition with OpNone and no operands
if c1.Attribute == c2.Attribute && // attributes equal
(c1.Op == traceql.OpEqual && c2.Op == traceql.OpNotEqual || c1.Op == traceql.OpNotEqual && c2.Op == traceql.OpEqual) && // operands are != and =
operandsEqual(c1, c2) { // operands equal
return traceql.Condition{Attribute: c1.Attribute, Op: traceql.OpNone, Operands: nil}, true
}
// if one of the operations is opnone we're already pulling every row. coalesce
if c1.Attribute == c2.Attribute && // attributes equal
(c1.Op == traceql.OpNone || c2.Op == traceql.OpNone) { // one operand is opnone
return traceql.Condition{Attribute: c1.Attribute, Op: traceql.OpNone, Operands: nil}, true
}
return traceql.Condition{}, false
}
func operandsEqual(c1, c2 traceql.Condition) bool {
if len(c1.Operands) != len(c2.Operands) {
return false
}
// todo: sort first?
for i := 0; i < len(c1.Operands); i++ {
if !c1.Operands[i].StrictEquals(&c2.Operands[i]) {
return false
}
}
return true
}