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 }