github.com/thanos-io/thanos@v0.32.5/pkg/query/remote_engine.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package query
     5  
     6  import (
     7  	"context"
     8  	"io"
     9  	"math"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/go-kit/log"
    14  	"github.com/go-kit/log/level"
    15  	"github.com/pkg/errors"
    16  	"github.com/prometheus/prometheus/model/labels"
    17  	"github.com/prometheus/prometheus/promql"
    18  	"github.com/prometheus/prometheus/promql/parser"
    19  	"github.com/prometheus/prometheus/util/stats"
    20  	"github.com/thanos-io/promql-engine/api"
    21  
    22  	"github.com/thanos-io/thanos/pkg/api/query/querypb"
    23  	"github.com/thanos-io/thanos/pkg/info/infopb"
    24  	"github.com/thanos-io/thanos/pkg/store/labelpb"
    25  	"github.com/thanos-io/thanos/pkg/store/storepb/prompb"
    26  )
    27  
    28  // Opts are the options for a PromQL query.
    29  type Opts struct {
    30  	AutoDownsample        bool
    31  	ReplicaLabels         []string
    32  	Timeout               time.Duration
    33  	EnablePartialResponse bool
    34  }
    35  
    36  // Client is a query client that executes PromQL queries.
    37  type Client struct {
    38  	querypb.QueryClient
    39  	address   string
    40  	tsdbInfos infopb.TSDBInfos
    41  }
    42  
    43  // NewClient creates a new Client.
    44  func NewClient(queryClient querypb.QueryClient, address string, tsdbInfos infopb.TSDBInfos) Client {
    45  	return Client{
    46  		QueryClient: queryClient,
    47  		address:     address,
    48  		tsdbInfos:   tsdbInfos,
    49  	}
    50  }
    51  
    52  func (c Client) GetAddress() string { return c.address }
    53  
    54  func (c Client) LabelSets() []labels.Labels {
    55  	return c.tsdbInfos.LabelSets()
    56  }
    57  
    58  type remoteEndpoints struct {
    59  	logger     log.Logger
    60  	getClients func() []Client
    61  	opts       Opts
    62  }
    63  
    64  func NewRemoteEndpoints(logger log.Logger, getClients func() []Client, opts Opts) api.RemoteEndpoints {
    65  	return remoteEndpoints{
    66  		logger:     logger,
    67  		getClients: getClients,
    68  		opts:       opts,
    69  	}
    70  }
    71  
    72  func (r remoteEndpoints) Engines() []api.RemoteEngine {
    73  	clients := r.getClients()
    74  	engines := make([]api.RemoteEngine, len(clients))
    75  	for i := range clients {
    76  		engines[i] = newRemoteEngine(r.logger, clients[i], r.opts)
    77  	}
    78  	return engines
    79  }
    80  
    81  type remoteEngine struct {
    82  	opts   Opts
    83  	logger log.Logger
    84  
    85  	client Client
    86  
    87  	mintOnce      sync.Once
    88  	mint          int64
    89  	maxtOnce      sync.Once
    90  	maxt          int64
    91  	labelSetsOnce sync.Once
    92  	labelSets     []labels.Labels
    93  }
    94  
    95  func newRemoteEngine(logger log.Logger, queryClient Client, opts Opts) api.RemoteEngine {
    96  	return &remoteEngine{
    97  		logger: logger,
    98  		client: queryClient,
    99  		opts:   opts,
   100  	}
   101  }
   102  
   103  // MinT returns the minimum timestamp that is safe to query in the remote engine.
   104  // In order to calculate it, we find the highest min time for each label set, and we return
   105  // the lowest of those values.
   106  // Calculating the MinT this way makes remote queries resilient to cases where one tsdb replica would delete
   107  // a block due to retention before other replicas did the same.
   108  // See https://github.com/thanos-io/promql-engine/issues/187.
   109  func (r *remoteEngine) MinT() int64 {
   110  	r.mintOnce.Do(func() {
   111  		var (
   112  			hashBuf               = make([]byte, 0, 128)
   113  			highestMintByLabelSet = make(map[uint64]int64)
   114  		)
   115  		for _, lset := range r.infosWithoutReplicaLabels() {
   116  			key, _ := labelpb.ZLabelsToPromLabels(lset.Labels.Labels).HashWithoutLabels(hashBuf)
   117  			lsetMinT, ok := highestMintByLabelSet[key]
   118  			if !ok {
   119  				highestMintByLabelSet[key] = lset.MinTime
   120  				continue
   121  			}
   122  
   123  			if lset.MinTime > lsetMinT {
   124  				highestMintByLabelSet[key] = lset.MinTime
   125  			}
   126  		}
   127  
   128  		var mint int64 = math.MaxInt64
   129  		for _, m := range highestMintByLabelSet {
   130  			if m < mint {
   131  				mint = m
   132  			}
   133  		}
   134  		r.mint = mint
   135  	})
   136  
   137  	return r.mint
   138  }
   139  
   140  func (r *remoteEngine) MaxT() int64 {
   141  	r.maxtOnce.Do(func() {
   142  		r.maxt = r.client.tsdbInfos.MaxT()
   143  	})
   144  	return r.maxt
   145  }
   146  
   147  func (r *remoteEngine) LabelSets() []labels.Labels {
   148  	r.labelSetsOnce.Do(func() {
   149  		r.labelSets = r.infosWithoutReplicaLabels().LabelSets()
   150  	})
   151  	return r.labelSets
   152  }
   153  
   154  func (r *remoteEngine) infosWithoutReplicaLabels() infopb.TSDBInfos {
   155  	replicaLabelSet := make(map[string]struct{})
   156  	for _, lbl := range r.opts.ReplicaLabels {
   157  		replicaLabelSet[lbl] = struct{}{}
   158  	}
   159  
   160  	// Strip replica labels from the result.
   161  	infos := make(infopb.TSDBInfos, 0, len(r.client.tsdbInfos))
   162  	var builder labels.ScratchBuilder
   163  	for _, info := range r.client.tsdbInfos {
   164  		builder.Reset()
   165  		for _, lbl := range info.Labels.Labels {
   166  			if _, ok := replicaLabelSet[lbl.Name]; ok {
   167  				continue
   168  			}
   169  			builder.Add(lbl.Name, lbl.Value)
   170  		}
   171  		infos = append(infos, infopb.NewTSDBInfo(
   172  			info.MinTime,
   173  			info.MaxTime,
   174  			labelpb.ZLabelsFromPromLabels(builder.Labels())),
   175  		)
   176  	}
   177  
   178  	return infos
   179  }
   180  
   181  func (r *remoteEngine) NewRangeQuery(_ context.Context, opts promql.QueryOpts, qs string, start, end time.Time, interval time.Duration) (promql.Query, error) {
   182  	return &remoteQuery{
   183  		logger: r.logger,
   184  		client: r.client,
   185  		opts:   r.opts,
   186  
   187  		qs:       qs,
   188  		start:    start,
   189  		end:      end,
   190  		interval: interval,
   191  	}, nil
   192  }
   193  
   194  type remoteQuery struct {
   195  	logger log.Logger
   196  	client Client
   197  	opts   Opts
   198  
   199  	qs       string
   200  	start    time.Time
   201  	end      time.Time
   202  	interval time.Duration
   203  
   204  	cancel context.CancelFunc
   205  }
   206  
   207  func (r *remoteQuery) Exec(ctx context.Context) *promql.Result {
   208  	start := time.Now()
   209  
   210  	qctx, cancel := context.WithCancel(ctx)
   211  	r.cancel = cancel
   212  	defer cancel()
   213  
   214  	var maxResolution int64
   215  	if r.opts.AutoDownsample {
   216  		maxResolution = int64(r.interval.Seconds() / 5)
   217  	}
   218  
   219  	request := &querypb.QueryRangeRequest{
   220  		Query:                 r.qs,
   221  		StartTimeSeconds:      r.start.Unix(),
   222  		EndTimeSeconds:        r.end.Unix(),
   223  		IntervalSeconds:       int64(r.interval.Seconds()),
   224  		TimeoutSeconds:        int64(r.opts.Timeout.Seconds()),
   225  		EnablePartialResponse: r.opts.EnablePartialResponse,
   226  		// TODO (fpetkovski): Allow specifying these parameters at query time.
   227  		// This will likely require a change in the remote engine interface.
   228  		ReplicaLabels:        r.opts.ReplicaLabels,
   229  		MaxResolutionSeconds: maxResolution,
   230  		EnableDedup:          true,
   231  	}
   232  	qry, err := r.client.QueryRange(qctx, request)
   233  	if err != nil {
   234  		return &promql.Result{Err: err}
   235  	}
   236  
   237  	result := make(promql.Matrix, 0)
   238  	for {
   239  		msg, err := qry.Recv()
   240  		if err == io.EOF {
   241  			break
   242  		}
   243  		if err != nil {
   244  			return &promql.Result{Err: err}
   245  		}
   246  
   247  		if warn := msg.GetWarnings(); warn != "" {
   248  			return &promql.Result{Err: errors.New(warn)}
   249  		}
   250  
   251  		ts := msg.GetTimeseries()
   252  		if ts == nil {
   253  			continue
   254  		}
   255  		series := promql.Series{
   256  			Metric:     labelpb.ZLabelsToPromLabels(ts.Labels),
   257  			Floats:     make([]promql.FPoint, 0, len(ts.Samples)),
   258  			Histograms: make([]promql.HPoint, 0, len(ts.Histograms)),
   259  		}
   260  		for _, s := range ts.Samples {
   261  			series.Floats = append(series.Floats, promql.FPoint{
   262  				T: s.Timestamp,
   263  				F: s.Value,
   264  			})
   265  		}
   266  		for _, hp := range ts.Histograms {
   267  			series.Histograms = append(series.Histograms, promql.HPoint{
   268  				T: hp.Timestamp,
   269  				H: prompb.FloatHistogramProtoToFloatHistogram(hp),
   270  			})
   271  		}
   272  		result = append(result, series)
   273  	}
   274  	level.Debug(r.logger).Log("Executed query", "query", r.qs, "time", time.Since(start))
   275  
   276  	return &promql.Result{Value: result}
   277  }
   278  
   279  func (r *remoteQuery) Close() { r.Cancel() }
   280  
   281  func (r *remoteQuery) Statement() parser.Statement { return nil }
   282  
   283  func (r *remoteQuery) Stats() *stats.Statistics { return nil }
   284  
   285  func (r *remoteQuery) String() string { return r.qs }
   286  
   287  func (r *remoteQuery) Cancel() {
   288  	if r.cancel != nil {
   289  		r.cancel()
   290  	}
   291  }