github.com/grafana/pyroscope@v1.18.0/pkg/querybackend/queryplan/query_plan.go (about) 1 package queryplan 2 3 import ( 4 "fmt" 5 "io" 6 "math" 7 8 metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1" 9 queryv1 "github.com/grafana/pyroscope/api/gen/proto/go/query/v1" 10 ) 11 12 // Build creates a query plan from the list of block metadata. 13 // 14 // NOTE(kolesnikovae): At this point it only groups blocks into uniform ranges, 15 // and builds a DAG of reads and merges. In practice, however, we may want to 16 // implement more sophisticated strategies. For example, it would be beneficial 17 // to group blocks based on the tenant services to ensure that a single read 18 // covers exactly one service, and does not have to deal with stack trace 19 // cardinality issues. Another example is grouping by shards to minimize the 20 // number of unique series (assuming the shards are still built based on the 21 // series labels) a reader or merger should handle. In general, the strategy 22 // should depend on the query type. 23 func Build( 24 blocks []*metastorev1.BlockMeta, 25 maxReads, maxMerges int, 26 ) *queryv1.QueryPlan { 27 if len(blocks) == 0 { 28 return new(queryv1.QueryPlan) 29 } 30 31 if maxReads < 1 { 32 return new(queryv1.QueryPlan) 33 } 34 35 if maxMerges < 2 { 36 return new(queryv1.QueryPlan) 37 } 38 39 // create leaf nodes and spread the blocks in a uniform way 40 var leafNodes []*queryv1.QueryNode 41 for i := 0; i < len(blocks); i += maxReads { 42 end := i + maxReads 43 if end > len(blocks) { 44 end = len(blocks) 45 } 46 leafNodes = append(leafNodes, &queryv1.QueryNode{ 47 Type: queryv1.QueryNode_READ, 48 Blocks: blocks[i:end], 49 }) 50 } 51 52 // create merge nodes until we reach a single root node 53 for len(leafNodes) > 1 { 54 numNodes := len(leafNodes) 55 numMerges := int(math.Ceil(float64(numNodes) / float64(maxMerges))) 56 57 var newLeafNodes []*queryv1.QueryNode 58 for i := 0; i < numMerges; i++ { 59 newNode := &queryv1.QueryNode{ 60 Type: queryv1.QueryNode_MERGE, 61 } 62 for j := 0; j < maxMerges && len(leafNodes) > 0; j++ { 63 newNode.Children = append(newNode.Children, leafNodes[0]) 64 leafNodes = leafNodes[1:] 65 } 66 newLeafNodes = append(newLeafNodes, newNode) 67 } 68 leafNodes = newLeafNodes 69 } 70 71 return &queryv1.QueryPlan{ 72 Root: leafNodes[0], 73 } 74 } 75 76 func printPlan(w io.Writer, pad string, n *queryv1.QueryNode, debug bool) { 77 if debug { 78 _, _ = fmt.Fprintf(w, pad+"%s {children: %d, blocks: %d}\n", 79 n.Type, len(n.Children), len(n.Blocks)) 80 } else { 81 _, _ = fmt.Fprintf(w, pad+"%s (%d)\n", n.Type, len(n.Children)) 82 } 83 84 switch n.Type { 85 case queryv1.QueryNode_MERGE: 86 for _, child := range n.Children { 87 printPlan(w, pad+"\t", child, debug) 88 } 89 90 case queryv1.QueryNode_READ: 91 for _, md := range n.Blocks { 92 _, _ = fmt.Fprintf(w, pad+"\t"+"id:\"%s\"\n", md.Id) 93 } 94 95 default: 96 panic("unknown type") 97 } 98 }