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  }