github.com/thanos-io/thanos@v0.32.5/pkg/store/proxy.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package store 5 6 import ( 7 "context" 8 "fmt" 9 "math" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/go-kit/log" 15 "github.com/go-kit/log/level" 16 "github.com/opentracing/opentracing-go" 17 "github.com/pkg/errors" 18 "github.com/prometheus/client_golang/prometheus" 19 "github.com/prometheus/client_golang/prometheus/promauto" 20 "github.com/prometheus/prometheus/model/labels" 21 "golang.org/x/sync/errgroup" 22 "google.golang.org/grpc" 23 "google.golang.org/grpc/codes" 24 "google.golang.org/grpc/metadata" 25 "google.golang.org/grpc/status" 26 27 "github.com/thanos-io/thanos/pkg/component" 28 "github.com/thanos-io/thanos/pkg/info/infopb" 29 "github.com/thanos-io/thanos/pkg/store/labelpb" 30 "github.com/thanos-io/thanos/pkg/store/storepb" 31 "github.com/thanos-io/thanos/pkg/strutil" 32 "github.com/thanos-io/thanos/pkg/tenancy" 33 "github.com/thanos-io/thanos/pkg/tracing" 34 ) 35 36 type ctxKey int 37 38 // UninitializedTSDBTime is the TSDB start time of an uninitialized TSDB instance. 39 const UninitializedTSDBTime = math.MaxInt64 40 41 // StoreMatcherKey is the context key for the store's allow list. 42 const StoreMatcherKey = ctxKey(0) 43 44 // ErrorNoStoresMatched is returned if the query does not match any data. 45 // This can happen with Query servers trees and external labels. 46 var ErrorNoStoresMatched = errors.New("No StoreAPIs matched for this query") 47 48 // Client holds meta information about a store. 49 type Client interface { 50 // StoreClient to access the store. 51 storepb.StoreClient 52 53 // LabelSets that each apply to some data exposed by the backing store. 54 LabelSets() []labels.Labels 55 56 // TimeRange returns minimum and maximum time range of data in the store. 57 TimeRange() (mint int64, maxt int64) 58 59 // TSDBInfos returns metadata about each TSDB backed by the client. 60 TSDBInfos() []infopb.TSDBInfo 61 62 // SupportsSharding returns true if sharding is supported by the underlying store. 63 SupportsSharding() bool 64 65 // SupportsWithoutReplicaLabels returns true if trimming replica labels 66 // and sorted response is supported by the underlying store. 67 SupportsWithoutReplicaLabels() bool 68 69 // String returns the string representation of the store client. 70 String() string 71 72 // Addr returns address of the store client. If second parameter is true, the client 73 // represents a local client (server-as-client) and has no remote address. 74 Addr() (addr string, isLocalClient bool) 75 } 76 77 // ProxyStore implements the store API that proxies request to all given underlying stores. 78 type ProxyStore struct { 79 logger log.Logger 80 stores func() []Client 81 component component.StoreAPI 82 selectorLabels labels.Labels 83 buffers sync.Pool 84 85 responseTimeout time.Duration 86 metrics *proxyStoreMetrics 87 retrievalStrategy RetrievalStrategy 88 debugLogging bool 89 } 90 91 type proxyStoreMetrics struct { 92 emptyStreamResponses prometheus.Counter 93 } 94 95 func newProxyStoreMetrics(reg prometheus.Registerer) *proxyStoreMetrics { 96 var m proxyStoreMetrics 97 98 m.emptyStreamResponses = promauto.With(reg).NewCounter(prometheus.CounterOpts{ 99 Name: "thanos_proxy_store_empty_stream_responses_total", 100 Help: "Total number of empty responses received.", 101 }) 102 103 return &m 104 } 105 106 func RegisterStoreServer(storeSrv storepb.StoreServer, logger log.Logger) func(*grpc.Server) { 107 return func(s *grpc.Server) { 108 storepb.RegisterStoreServer(s, NewRecoverableStoreServer(logger, storeSrv)) 109 } 110 } 111 112 // BucketStoreOption are functions that configure BucketStore. 113 type ProxyStoreOption func(s *ProxyStore) 114 115 // WithProxyStoreDebugLogging enables debug logging. 116 func WithProxyStoreDebugLogging() ProxyStoreOption { 117 return func(s *ProxyStore) { 118 s.debugLogging = true 119 } 120 } 121 122 // NewProxyStore returns a new ProxyStore that uses the given clients that implements storeAPI to fan-in all series to the client. 123 // Note that there is no deduplication support. Deduplication should be done on the highest level (just before PromQL). 124 func NewProxyStore( 125 logger log.Logger, 126 reg prometheus.Registerer, 127 stores func() []Client, 128 component component.StoreAPI, 129 selectorLabels labels.Labels, 130 responseTimeout time.Duration, 131 retrievalStrategy RetrievalStrategy, 132 options ...ProxyStoreOption, 133 ) *ProxyStore { 134 if logger == nil { 135 logger = log.NewNopLogger() 136 } 137 138 metrics := newProxyStoreMetrics(reg) 139 s := &ProxyStore{ 140 logger: logger, 141 stores: stores, 142 component: component, 143 selectorLabels: selectorLabels, 144 buffers: sync.Pool{New: func() interface{} { 145 b := make([]byte, 0, initialBufSize) 146 return &b 147 }}, 148 responseTimeout: responseTimeout, 149 metrics: metrics, 150 retrievalStrategy: retrievalStrategy, 151 } 152 153 for _, option := range options { 154 option(s) 155 } 156 157 return s 158 } 159 160 // Info returns store information about the external labels this store have. 161 func (s *ProxyStore) Info(_ context.Context, _ *storepb.InfoRequest) (*storepb.InfoResponse, error) { 162 res := &storepb.InfoResponse{ 163 StoreType: s.component.ToProto(), 164 Labels: labelpb.ZLabelsFromPromLabels(s.selectorLabels), 165 } 166 167 minTime := int64(math.MaxInt64) 168 maxTime := int64(0) 169 stores := s.stores() 170 171 // Edge case: we have no data if there are no stores. 172 if len(stores) == 0 { 173 res.MaxTime = 0 174 res.MinTime = 0 175 176 return res, nil 177 } 178 179 for _, s := range stores { 180 mint, maxt := s.TimeRange() 181 if mint < minTime { 182 minTime = mint 183 } 184 if maxt > maxTime { 185 maxTime = maxt 186 } 187 } 188 189 res.MaxTime = maxTime 190 res.MinTime = minTime 191 192 labelSets := make(map[uint64]labelpb.ZLabelSet, len(stores)) 193 for _, st := range stores { 194 for _, lset := range st.LabelSets() { 195 mergedLabelSet := labelpb.ExtendSortedLabels(lset, s.selectorLabels) 196 labelSets[mergedLabelSet.Hash()] = labelpb.ZLabelSet{Labels: labelpb.ZLabelsFromPromLabels(mergedLabelSet)} 197 } 198 } 199 200 res.LabelSets = make([]labelpb.ZLabelSet, 0, len(labelSets)) 201 for _, v := range labelSets { 202 res.LabelSets = append(res.LabelSets, v) 203 } 204 205 // We always want to enforce announcing the subset of data that 206 // selector-labels represents. If no label-sets are announced by the 207 // store-proxy's discovered stores, then we still want to enforce 208 // announcing this subset by announcing the selector as the label-set. 209 if len(res.LabelSets) == 0 && len(res.Labels) > 0 { 210 res.LabelSets = append(res.LabelSets, labelpb.ZLabelSet{Labels: res.Labels}) 211 } 212 213 return res, nil 214 } 215 216 func (s *ProxyStore) LabelSet() []labelpb.ZLabelSet { 217 stores := s.stores() 218 if len(stores) == 0 { 219 return []labelpb.ZLabelSet{} 220 } 221 222 mergedLabelSets := make(map[uint64]labelpb.ZLabelSet, len(stores)) 223 for _, st := range stores { 224 for _, lset := range st.LabelSets() { 225 mergedLabelSet := labelpb.ExtendSortedLabels(lset, s.selectorLabels) 226 mergedLabelSets[mergedLabelSet.Hash()] = labelpb.ZLabelSet{Labels: labelpb.ZLabelsFromPromLabels(mergedLabelSet)} 227 } 228 } 229 230 labelSets := make([]labelpb.ZLabelSet, 0, len(mergedLabelSets)) 231 for _, v := range mergedLabelSets { 232 labelSets = append(labelSets, v) 233 } 234 235 // We always want to enforce announcing the subset of data that 236 // selector-labels represents. If no label-sets are announced by the 237 // store-proxy's discovered stores, then we still want to enforce 238 // announcing this subset by announcing the selector as the label-set. 239 selectorLabels := labelpb.ZLabelsFromPromLabels(s.selectorLabels) 240 if len(labelSets) == 0 && len(selectorLabels) > 0 { 241 labelSets = append(labelSets, labelpb.ZLabelSet{Labels: selectorLabels}) 242 } 243 244 return labelSets 245 } 246 247 func (s *ProxyStore) TimeRange() (int64, int64) { 248 stores := s.stores() 249 if len(stores) == 0 { 250 return math.MinInt64, math.MaxInt64 251 } 252 253 var minTime, maxTime int64 = math.MaxInt64, math.MinInt64 254 for _, s := range stores { 255 storeMinTime, storeMaxTime := s.TimeRange() 256 if storeMinTime < minTime { 257 minTime = storeMinTime 258 } 259 if storeMaxTime > maxTime { 260 maxTime = storeMaxTime 261 } 262 } 263 264 return minTime, maxTime 265 } 266 267 func (s *ProxyStore) TSDBInfos() []infopb.TSDBInfo { 268 infos := make([]infopb.TSDBInfo, 0) 269 for _, store := range s.stores() { 270 infos = append(infos, store.TSDBInfos()...) 271 } 272 return infos 273 } 274 275 func (s *ProxyStore) Series(originalRequest *storepb.SeriesRequest, srv storepb.Store_SeriesServer) error { 276 // TODO(bwplotka): This should be part of request logger, otherwise it does not make much sense. Also, could be 277 // tiggered by tracing span to reduce cognitive load. 278 reqLogger := log.With(s.logger, "component", "proxy", "request", originalRequest.String()) 279 280 match, matchers, err := matchesExternalLabels(originalRequest.Matchers, s.selectorLabels) 281 if err != nil { 282 return status.Error(codes.InvalidArgument, err.Error()) 283 } 284 if !match { 285 return nil 286 } 287 if len(matchers) == 0 { 288 return status.Error(codes.InvalidArgument, errors.New("no matchers specified (excluding selector labels)").Error()) 289 } 290 storeMatchers, _ := storepb.PromMatchersToMatchers(matchers...) // Error would be returned by matchesExternalLabels, so skip check. 291 292 storeDebugMsgs := []string{} 293 r := &storepb.SeriesRequest{ 294 MinTime: originalRequest.MinTime, 295 MaxTime: originalRequest.MaxTime, 296 Matchers: storeMatchers, 297 Aggregates: originalRequest.Aggregates, 298 MaxResolutionWindow: originalRequest.MaxResolutionWindow, 299 SkipChunks: originalRequest.SkipChunks, 300 QueryHints: originalRequest.QueryHints, 301 PartialResponseDisabled: originalRequest.PartialResponseDisabled, 302 PartialResponseStrategy: originalRequest.PartialResponseStrategy, 303 ShardInfo: originalRequest.ShardInfo, 304 WithoutReplicaLabels: originalRequest.WithoutReplicaLabels, 305 } 306 307 // We may arrive here either via the promql engine 308 // or as a result of a grpc call in layered queries 309 ctx := srv.Context() 310 tenant, foundTenant := tenancy.GetTenantFromGRPCMetadata(ctx) 311 if !foundTenant { 312 if ctx.Value(tenancy.TenantKey) != nil { 313 tenant = ctx.Value(tenancy.TenantKey).(string) 314 } 315 } 316 317 ctx = metadata.AppendToOutgoingContext(ctx, tenancy.DefaultTenantHeader, tenant) 318 level.Debug(s.logger).Log("msg", "Tenant info in Series()", "tenant", tenant) 319 320 stores := []Client{} 321 for _, st := range s.stores() { 322 // We might be able to skip the store if its meta information indicates it cannot have series matching our query. 323 if ok, reason := storeMatches(ctx, st, s.debugLogging, originalRequest.MinTime, originalRequest.MaxTime, matchers...); !ok { 324 if s.debugLogging { 325 storeDebugMsgs = append(storeDebugMsgs, fmt.Sprintf("store %s filtered out: %v", st, reason)) 326 } 327 continue 328 } 329 330 stores = append(stores, st) 331 } 332 333 if len(stores) == 0 { 334 level.Debug(reqLogger).Log("err", ErrorNoStoresMatched, "stores", strings.Join(storeDebugMsgs, ";")) 335 return nil 336 } 337 338 storeResponses := make([]respSet, 0, len(stores)) 339 340 for _, st := range stores { 341 st := st 342 if s.debugLogging { 343 storeDebugMsgs = append(storeDebugMsgs, fmt.Sprintf("store %s queried", st)) 344 } 345 346 respSet, err := newAsyncRespSet(ctx, st, r, s.responseTimeout, s.retrievalStrategy, &s.buffers, r.ShardInfo, reqLogger, s.metrics.emptyStreamResponses) 347 if err != nil { 348 level.Error(reqLogger).Log("err", err) 349 350 if !r.PartialResponseDisabled || r.PartialResponseStrategy == storepb.PartialResponseStrategy_WARN { 351 if err := srv.Send(storepb.NewWarnSeriesResponse(err)); err != nil { 352 return err 353 } 354 continue 355 } else { 356 return err 357 } 358 } 359 360 storeResponses = append(storeResponses, respSet) 361 defer respSet.Close() 362 } 363 364 level.Debug(reqLogger).Log("msg", "Series: started fanout streams", "status", strings.Join(storeDebugMsgs, ";")) 365 366 respHeap := NewDedupResponseHeap(NewProxyResponseHeap(storeResponses...)) 367 for respHeap.Next() { 368 resp := respHeap.At() 369 370 if resp.GetWarning() != "" && (r.PartialResponseDisabled || r.PartialResponseStrategy == storepb.PartialResponseStrategy_ABORT) { 371 return status.Error(codes.Aborted, resp.GetWarning()) 372 } 373 374 if err := srv.Send(resp); err != nil { 375 return status.Error(codes.Unknown, errors.Wrap(err, "send series response").Error()) 376 } 377 } 378 379 return nil 380 } 381 382 // storeMatches returns boolean if the given store may hold data for the given label matchers, time ranges and debug store matches gathered from context. 383 func storeMatches(ctx context.Context, s Client, debugLogging bool, mint, maxt int64, matchers ...*labels.Matcher) (ok bool, reason string) { 384 var storeDebugMatcher [][]*labels.Matcher 385 if ctxVal := ctx.Value(StoreMatcherKey); ctxVal != nil { 386 if value, ok := ctxVal.([][]*labels.Matcher); ok { 387 storeDebugMatcher = value 388 } 389 } 390 391 storeMinTime, storeMaxTime := s.TimeRange() 392 if mint > storeMaxTime || maxt < storeMinTime { 393 if debugLogging { 394 reason = fmt.Sprintf("does not have data within this time period: [%v,%v]. Store time ranges: [%v,%v]", mint, maxt, storeMinTime, storeMaxTime) 395 } 396 return false, reason 397 } 398 399 if ok, reason := storeMatchDebugMetadata(s, storeDebugMatcher); !ok { 400 return false, reason 401 } 402 403 extLset := s.LabelSets() 404 if !labelSetsMatch(matchers, extLset...) { 405 if debugLogging { 406 reason = fmt.Sprintf("external labels %v does not match request label matchers: %v", extLset, matchers) 407 } 408 return false, reason 409 } 410 return true, "" 411 } 412 413 // storeMatchDebugMetadata return true if the store's address match the storeDebugMatchers. 414 func storeMatchDebugMetadata(s Client, storeDebugMatchers [][]*labels.Matcher) (ok bool, reason string) { 415 if len(storeDebugMatchers) == 0 { 416 return true, "" 417 } 418 419 addr, isLocal := s.Addr() 420 if isLocal { 421 return false, "the store is not remote, cannot match __address__" 422 } 423 424 match := false 425 for _, sm := range storeDebugMatchers { 426 match = match || labelSetsMatch(sm, labels.FromStrings("__address__", addr)) 427 } 428 if !match { 429 return false, fmt.Sprintf("__address__ %v does not match debug store metadata matchers: %v", addr, storeDebugMatchers) 430 } 431 return true, "" 432 } 433 434 // labelSetsMatch returns false if all label-set do not match the matchers (aka: OR is between all label-sets). 435 func labelSetsMatch(matchers []*labels.Matcher, lset ...labels.Labels) bool { 436 if len(lset) == 0 { 437 return true 438 } 439 440 for _, ls := range lset { 441 notMatched := false 442 for _, m := range matchers { 443 if lv := ls.Get(m.Name); ls.Has(m.Name) && !m.Matches(lv) { 444 notMatched = true 445 break 446 } 447 } 448 if !notMatched { 449 return true 450 } 451 } 452 return false 453 } 454 455 // LabelNames returns all known label names. 456 func (s *ProxyStore) LabelNames(ctx context.Context, r *storepb.LabelNamesRequest) ( 457 *storepb.LabelNamesResponse, error, 458 ) { 459 var ( 460 warnings []string 461 names [][]string 462 mtx sync.Mutex 463 g, gctx = errgroup.WithContext(ctx) 464 storeDebugMsgs []string 465 ) 466 467 // We may arrive here either via the promql engine 468 // or as a result of a grpc call in layered queries 469 tenant, foundTenant := tenancy.GetTenantFromGRPCMetadata(gctx) 470 if !foundTenant { 471 level.Debug(s.logger).Log("msg", "using tenant from context instead of metadata") 472 if gctx.Value(tenancy.TenantKey) != nil { 473 tenant = gctx.Value(tenancy.TenantKey).(string) 474 } 475 } 476 477 gctx = metadata.AppendToOutgoingContext(gctx, tenancy.DefaultTenantHeader, tenant) 478 level.Debug(s.logger).Log("msg", "Tenant info in LabelNames()", "tenant", tenant) 479 480 for _, st := range s.stores() { 481 st := st 482 483 // We might be able to skip the store if its meta information indicates it cannot have series matching our query. 484 if ok, reason := storeMatches(gctx, st, s.debugLogging, r.Start, r.End); !ok { 485 if s.debugLogging { 486 storeDebugMsgs = append(storeDebugMsgs, fmt.Sprintf("Store %s filtered out due to %v", st, reason)) 487 } 488 continue 489 } 490 if s.debugLogging { 491 storeDebugMsgs = append(storeDebugMsgs, fmt.Sprintf("Store %s queried", st)) 492 } 493 494 g.Go(func() error { 495 resp, err := st.LabelNames(gctx, &storepb.LabelNamesRequest{ 496 PartialResponseDisabled: r.PartialResponseDisabled, 497 Start: r.Start, 498 End: r.End, 499 Matchers: r.Matchers, 500 }) 501 if err != nil { 502 err = errors.Wrapf(err, "fetch label names from store %s", st) 503 if r.PartialResponseDisabled { 504 return err 505 } 506 507 mtx.Lock() 508 warnings = append(warnings, err.Error()) 509 mtx.Unlock() 510 return nil 511 } 512 513 mtx.Lock() 514 warnings = append(warnings, resp.Warnings...) 515 names = append(names, resp.Names) 516 mtx.Unlock() 517 518 return nil 519 }) 520 } 521 522 if err := g.Wait(); err != nil { 523 return nil, err 524 } 525 526 level.Debug(s.logger).Log("msg", strings.Join(storeDebugMsgs, ";")) 527 return &storepb.LabelNamesResponse{ 528 Names: strutil.MergeUnsortedSlices(names...), 529 Warnings: warnings, 530 }, nil 531 } 532 533 // LabelValues returns all known label values for a given label name. 534 func (s *ProxyStore) LabelValues(ctx context.Context, r *storepb.LabelValuesRequest) ( 535 *storepb.LabelValuesResponse, error, 536 ) { 537 var ( 538 warnings []string 539 all [][]string 540 mtx sync.Mutex 541 g, gctx = errgroup.WithContext(ctx) 542 storeDebugMsgs []string 543 span opentracing.Span 544 ) 545 546 // We may arrive here either via the promql engine 547 // or as a result of a grpc call in layered queries 548 tenant, foundTenant := tenancy.GetTenantFromGRPCMetadata(gctx) 549 if !foundTenant { 550 level.Debug(s.logger).Log("msg", "using tenant from context instead of metadata") 551 if gctx.Value(tenancy.TenantKey) != nil { 552 tenant = gctx.Value(tenancy.TenantKey).(string) 553 } 554 } 555 556 gctx = metadata.AppendToOutgoingContext(gctx, tenancy.DefaultTenantHeader, tenant) 557 level.Debug(s.logger).Log("msg", "Tenant info in LabelValues()", "tenant", tenant) 558 559 for _, st := range s.stores() { 560 st := st 561 562 storeAddr, isLocalStore := st.Addr() 563 storeID := labelpb.PromLabelSetsToString(st.LabelSets()) 564 if storeID == "" { 565 storeID = "Store Gateway" 566 } 567 span, gctx = tracing.StartSpan(gctx, "proxy.label_values", tracing.Tags{ 568 "store.id": storeID, 569 "store.addr": storeAddr, 570 "store.is_local": isLocalStore, 571 }) 572 defer span.Finish() 573 574 // We might be able to skip the store if its meta information indicates it cannot have series matching our query. 575 if ok, reason := storeMatches(gctx, st, s.debugLogging, r.Start, r.End); !ok { 576 if s.debugLogging { 577 storeDebugMsgs = append(storeDebugMsgs, fmt.Sprintf("Store %s filtered out due to %v", st, reason)) 578 } 579 continue 580 } 581 if s.debugLogging { 582 storeDebugMsgs = append(storeDebugMsgs, fmt.Sprintf("Store %s queried", st)) 583 } 584 585 g.Go(func() error { 586 resp, err := st.LabelValues(gctx, &storepb.LabelValuesRequest{ 587 Label: r.Label, 588 PartialResponseDisabled: r.PartialResponseDisabled, 589 Start: r.Start, 590 End: r.End, 591 Matchers: r.Matchers, 592 }) 593 if err != nil { 594 msg := "fetch label values from store %s" 595 err = errors.Wrapf(err, msg, st) 596 if r.PartialResponseDisabled { 597 return err 598 } 599 600 mtx.Lock() 601 warnings = append(warnings, errors.Wrapf(err, msg, st).Error()) 602 mtx.Unlock() 603 return nil 604 } 605 606 mtx.Lock() 607 warnings = append(warnings, resp.Warnings...) 608 all = append(all, resp.Values) 609 mtx.Unlock() 610 611 return nil 612 }) 613 } 614 615 if err := g.Wait(); err != nil { 616 return nil, err 617 } 618 619 level.Debug(s.logger).Log("msg", strings.Join(storeDebugMsgs, ";")) 620 return &storepb.LabelValuesResponse{ 621 Values: strutil.MergeUnsortedSlices(all...), 622 Warnings: warnings, 623 }, nil 624 }