Skip to content

Commit b39fb13

Browse files
committed
Query Label Values from block store
Signed-off-by: Goutham Veeramachaneni <[email protected]>
1 parent 7616bcf commit b39fb13

File tree

11 files changed

+245
-50
lines changed

11 files changed

+245
-50
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* [ENHANCEMENT] Added `cortex_alertmanager_config_hash` metric to expose hash of Alertmanager Config loaded per user. #3388
1414
* [ENHANCEMENT] Query-Frontend / Query-Scheduler: New component called "Query-Scheduler" has been introduced. Query-Scheduler is simply a queue of requests, moved outside of Query-Frontend. This allows Query-Frontend to be scaled separately from number of queues. To make Query-Frontend and Querier use Query-Scheduler, they need to be started with `-frontend.scheduler-address` and `-querier.scheduler-address` options respectively. #3374 #3471
1515
* [ENHANCEMENT] Query-frontend / Querier / Ruler: added `-querier.max-query-lookback` to limit how long back data (series and metadata) can be queried. This setting can be overridden on a per-tenant basis and is enforced in the query-frontend, querier and ruler. #3452 #3458
16-
* [ENHANCEMENT] Querier: added `-querier.query-store-for-labels-enabled` to query store for series API. Only works with blocks storage engine. #3461
16+
* [ENHANCEMENT] Querier: added `-querier.query-store-for-labels-enabled` to query store for label names, label values and series APIs. Only works with blocks storage engine. #3461 #3520
1717
* [ENHANCEMENT] Ingester: exposed `-blocks-storage.tsdb.wal-segment-size-bytes` config option to customise the TSDB WAL segment max size. #3476
1818
* [ENHANCEMENT] Compactor: concurrently run blocks cleaner for multiple tenants. Concurrency can be configured via `-compactor.cleanup-concurrency`. #3483
1919
* [ENHANCEMENT] Compactor: shuffle tenants before running compaction. #3483

docs/api/_index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ GET,POST <prometheus-http-prefix>/api/v1/labels
294294
GET,POST <legacy-http-prefix>/api/v1/labels
295295
```
296296

297-
Get label names of ingested series. Differently than Prometheus and due to scalability and performances reasons, Cortex currently ignores the `start` and `end` request parameters and always fetches the label names from in-memory data stored in the ingesters.
297+
Get label names of ingested series. Differently than Prometheus and due to scalability and performances reasons, Cortex currently ignores the `start` and `end` request parameters and always fetches the label names from in-memory data stored in the ingesters. There is experimental support to query the long-term store with the *blocks* storage engine when `-querier.query-store-for-labels-enabled` is set.
298298

299299
_For more information, please check out the Prometheus [get label names](https://prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names) documentation._
300300

@@ -309,7 +309,7 @@ GET <prometheus-http-prefix>/api/v1/label/{name}/values
309309
GET <legacy-http-prefix>/api/v1/label/{name}/values
310310
```
311311

312-
Get label values for a given label name. Differently than Prometheus and due to scalability and performances reasons, Cortex currently ignores the `start` and `end` request parameters and always fetches the label values from in-memory data stored in the ingesters.
312+
Get label values for a given label name. Differently than Prometheus and due to scalability and performances reasons, Cortex currently ignores the `start` and `end` request parameters and always fetches the label values from in-memory data stored in the ingesters. There is experimental support to query the long-term store with the *blocks* storage engine when `-querier.query-store-for-labels-enabled` is set.
313313

314314
_For more information, please check out the Prometheus [get label values](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values) documentation._
315315

integration/querier_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ func testMetadataQueriesWithBlocksStorage(
536536
labelValuesTests: []labelValuesTest{
537537
{
538538
label: labels.MetricName,
539-
resp: []string{lastSeriesInIngesterBlocksName, firstSeriesInIngesterHeadName},
539+
resp: []string{lastSeriesInStorageName, lastSeriesInIngesterBlocksName, firstSeriesInIngesterHeadName},
540540
},
541541
},
542542
labelNames: []string{labels.MetricName, lastSeriesInStorageName, lastSeriesInIngesterBlocksName, firstSeriesInIngesterHeadName},
@@ -563,7 +563,7 @@ func testMetadataQueriesWithBlocksStorage(
563563
labelValuesTests: []labelValuesTest{
564564
{
565565
label: labels.MetricName,
566-
resp: []string{firstSeriesInIngesterHeadName},
566+
resp: []string{lastSeriesInStorageName, firstSeriesInIngesterHeadName},
567567
},
568568
},
569569
labelNames: []string{labels.MetricName, lastSeriesInStorageName, firstSeriesInIngesterHeadName},
@@ -590,7 +590,7 @@ func testMetadataQueriesWithBlocksStorage(
590590
for _, val := range lvt.resp {
591591
exp = append(exp, model.LabelValue(val))
592592
}
593-
require.ElementsMatch(t, exp, labelsRes)
593+
require.Equal(t, exp, labelsRes)
594594
}
595595

596596
labelNames, err := c.LabelNames(tc.from, tc.to)

pkg/querier/blocks_store_queryable.go

Lines changed: 115 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"io"
7+
"sort"
78
"strings"
89
"sync"
910
"time"
@@ -293,11 +294,6 @@ func (q *blocksStoreQuerier) Select(_ bool, sp *storage.SelectHints, matchers ..
293294
return q.selectSorted(sp, matchers...)
294295
}
295296

296-
func (q *blocksStoreQuerier) LabelValues(name string) ([]string, storage.Warnings, error) {
297-
// Cortex doesn't use this. It will ask ingesters for metadata.
298-
return nil, nil, errors.New("not implemented")
299-
}
300-
301297
func (q *blocksStoreQuerier) LabelNames() ([]string, storage.Warnings, error) {
302298
spanLog, spanCtx := spanlogger.New(q.ctx, "blocksStoreQuerier.LabelNames")
303299
defer spanLog.Span.Finish()
@@ -333,6 +329,41 @@ func (q *blocksStoreQuerier) LabelNames() ([]string, storage.Warnings, error) {
333329
return strutil.MergeSlices(resNameSets...), resWarnings, nil
334330
}
335331

332+
func (q *blocksStoreQuerier) LabelValues(name string) ([]string, storage.Warnings, error) {
333+
spanLog, spanCtx := spanlogger.New(q.ctx, "blocksStoreQuerier.LabelValues")
334+
defer spanLog.Span.Finish()
335+
336+
minT, maxT := q.minT, q.maxT
337+
338+
var (
339+
resValueSets = [][]string{}
340+
resWarnings = storage.Warnings(nil)
341+
342+
resultMtx sync.Mutex
343+
)
344+
345+
queryFunc := func(clients map[BlocksStoreClient][]ulid.ULID, minT, maxT int64) ([]ulid.ULID, error) {
346+
valueSets, warnings, queriedBlocks, err := q.fetchLabelValuesFromStore(spanCtx, name, clients, minT, maxT)
347+
if err != nil {
348+
return nil, err
349+
}
350+
351+
resultMtx.Lock()
352+
resValueSets = append(resValueSets, valueSets...)
353+
resWarnings = append(resWarnings, warnings...)
354+
resultMtx.Unlock()
355+
356+
return queriedBlocks, nil
357+
}
358+
359+
err := q.queryWithConsistencyCheck(spanCtx, spanLog, minT, maxT, queryFunc)
360+
if err != nil {
361+
return nil, nil, err
362+
}
363+
364+
return strutil.MergeSlices(resValueSets...), resWarnings, nil
365+
}
366+
336367
func (q *blocksStoreQuerier) Close() error {
337368
return nil
338369
}
@@ -690,6 +721,85 @@ func (q *blocksStoreQuerier) fetchLabelNamesFromStore(
690721
return nameSets, warnings, queriedBlocks, nil
691722
}
692723

724+
func (q *blocksStoreQuerier) fetchLabelValuesFromStore(
725+
ctx context.Context,
726+
name string,
727+
clients map[BlocksStoreClient][]ulid.ULID,
728+
minT int64,
729+
maxT int64,
730+
) ([][]string, storage.Warnings, []ulid.ULID, error) {
731+
var (
732+
reqCtx = grpc_metadata.AppendToOutgoingContext(ctx, cortex_tsdb.TenantIDExternalLabel, q.userID)
733+
g, gCtx = errgroup.WithContext(reqCtx)
734+
mtx = sync.Mutex{}
735+
nameSets = [][]string{}
736+
warnings = storage.Warnings(nil)
737+
queriedBlocks = []ulid.ULID(nil)
738+
spanLog = spanlogger.FromContext(ctx)
739+
)
740+
741+
// Concurrently fetch series from all clients.
742+
for c, blockIDs := range clients {
743+
// Change variables scope since it will be used in a goroutine.
744+
c := c
745+
blockIDs := blockIDs
746+
747+
g.Go(func() error {
748+
req, err := createLabelValuesRequest(minT, maxT, name, blockIDs)
749+
if err != nil {
750+
return errors.Wrapf(err, "failed to create label names request")
751+
}
752+
753+
valuesResp, err := c.LabelValues(gCtx, req)
754+
if err != nil {
755+
return errors.Wrapf(err, "failed to fetch series from %s", c)
756+
}
757+
758+
myQueriedBlocks := []ulid.ULID(nil)
759+
if valuesResp.Hints != nil {
760+
hints := hintspb.LabelValuesResponseHints{}
761+
if err := types.UnmarshalAny(valuesResp.Hints, &hints); err != nil {
762+
return errors.Wrapf(err, "failed to unmarshal label names hints from %s", c)
763+
}
764+
765+
ids, err := convertBlockHintsToULIDs(hints.QueriedBlocks)
766+
if err != nil {
767+
return errors.Wrapf(err, "failed to parse queried block IDs from received hints")
768+
}
769+
770+
myQueriedBlocks = ids
771+
}
772+
773+
level.Debug(spanLog).Log("msg", "received label names from store-gateway",
774+
"instance", c,
775+
"num labels", len(valuesResp.Values),
776+
"requested blocks", strings.Join(convertULIDsToString(blockIDs), " "),
777+
"queried blocks", strings.Join(convertULIDsToString(myQueriedBlocks), " "))
778+
779+
// Values returned need not be sorted, but we need them to be sorted so we can merge.
780+
sort.Strings(valuesResp.Values)
781+
782+
// Store the result.
783+
mtx.Lock()
784+
nameSets = append(nameSets, valuesResp.Values)
785+
for _, w := range valuesResp.Warnings {
786+
warnings = append(warnings, errors.New(w))
787+
}
788+
queriedBlocks = append(queriedBlocks, myQueriedBlocks...)
789+
mtx.Unlock()
790+
791+
return nil
792+
})
793+
}
794+
795+
// Wait until all client requests complete.
796+
if err := g.Wait(); err != nil {
797+
return nil, nil, nil, err
798+
}
799+
800+
return nameSets, warnings, queriedBlocks, nil
801+
}
802+
693803
func createSeriesRequest(minT, maxT int64, matchers []storepb.LabelMatcher, skipChunks bool, blockIDs []ulid.ULID) (*storepb.SeriesRequest, error) {
694804
// Selectively query only specific blocks.
695805
hints := &hintspb.SeriesRequestHints{

pkg/querier/blocks_store_queryable_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -796,9 +796,9 @@ func (m *storeGatewayClientMock) LabelNames(context.Context, *storepb.LabelNames
796796
return nil, nil
797797
}
798798

799-
// func (m *storeGatewayClientMock) LabelValues(context.Context, *storepb.LabelValuesRequest, ...grpc.CallOption) (*storepb.LabelValuesResponse, error) {
800-
// return nil, nil
801-
// }
799+
func (m *storeGatewayClientMock) LabelValues(context.Context, *storepb.LabelValuesRequest, ...grpc.CallOption) (*storepb.LabelValuesResponse, error) {
800+
return nil, nil
801+
}
802802

803803
func (m *storeGatewayClientMock) RemoteAddress() string {
804804
return m.remoteAddr

pkg/querier/querier.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"flag"
77
"fmt"
8+
"sort"
89
"strings"
910
"sync"
1011
"time"
@@ -376,7 +377,51 @@ func (q querier) Select(_ bool, sp *storage.SelectHints, matchers ...*labels.Mat
376377

377378
// LabelsValue implements storage.Querier.
378379
func (q querier) LabelValues(name string) ([]string, storage.Warnings, error) {
379-
return q.metadataQuerier.LabelValues(name)
380+
if !q.queryStoreForLabels {
381+
return q.metadataQuerier.LabelValues(name)
382+
}
383+
384+
if len(q.queriers) == 1 {
385+
return q.queriers[0].LabelValues(name)
386+
}
387+
388+
// Using an errgroup here instead of channels, etc because this
389+
// is a better model imo and we should move to this everywhere.
390+
var (
391+
g, _ = errgroup.WithContext(q.ctx)
392+
sets = [][]string{}
393+
warnings = storage.Warnings(nil)
394+
395+
resMtx sync.Mutex
396+
)
397+
398+
for _, querier := range q.queriers {
399+
// Need to reassign as the original variable will change and can't be relied on in a goroutine.
400+
querier := querier
401+
g.Go(func() error {
402+
myValues, myWarnings, err := querier.LabelValues(name)
403+
if err != nil {
404+
return err
405+
}
406+
407+
// We need values to be sorted we can merge them.
408+
sort.Strings(myValues)
409+
410+
resMtx.Lock()
411+
sets = append(sets, myValues)
412+
warnings = append(warnings, myWarnings...)
413+
resMtx.Unlock()
414+
415+
return nil
416+
})
417+
}
418+
419+
err := g.Wait()
420+
if err != nil {
421+
return nil, nil, err
422+
}
423+
424+
return strutil.MergeSlices(sets...), warnings, nil
380425
}
381426

382427
func (q querier) LabelNames() ([]string, storage.Warnings, error) {

pkg/querier/store_gateway_client_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,6 @@ func (m *mockStoreGatewayServer) LabelNames(context.Context, *storepb.LabelNames
7979
return nil, nil
8080
}
8181

82-
// func (m *mockStoreGatewayServer) LabelValues(context.Context, *storepb.LabelValuesRequest) (*storepb.LabelValuesResponse, error) {
83-
// return nil, nil
84-
// }
82+
func (m *mockStoreGatewayServer) LabelValues(context.Context, *storepb.LabelValuesRequest) (*storepb.LabelValuesResponse, error) {
83+
return nil, nil
84+
}

pkg/storegateway/bucket_stores.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -266,22 +266,22 @@ func (u *BucketStores) LabelNames(ctx context.Context, req *storepb.LabelNamesRe
266266
}
267267

268268
// LabelValues implements the Storegateway proto service.
269-
// func (u *BucketStores) LabelValues(ctx context.Context, req *storepb.LabelValuesRequest) (*storepb.LabelValuesResponse, error) {
270-
// spanLog, spanCtx := spanlogger.New(ctx, "BucketStores.LabelNames")
271-
// defer spanLog.Span.Finish()
272-
273-
// userID := getUserIDFromGRPCContext(spanCtx)
274-
// if userID == "" {
275-
// return nil, fmt.Errorf("no userID")
276-
// }
277-
278-
// store := u.getStore(userID)
279-
// if store == nil {
280-
// return nil, nil
281-
// }
282-
283-
// return store.LabelValues(ctx, req)
284-
// }
269+
func (u *BucketStores) LabelValues(ctx context.Context, req *storepb.LabelValuesRequest) (*storepb.LabelValuesResponse, error) {
270+
spanLog, spanCtx := spanlogger.New(ctx, "BucketStores.LabelNames")
271+
defer spanLog.Span.Finish()
272+
273+
userID := getUserIDFromGRPCContext(spanCtx)
274+
if userID == "" {
275+
return nil, fmt.Errorf("no userID")
276+
}
277+
278+
store := u.getStore(userID)
279+
if store == nil {
280+
return nil, nil
281+
}
282+
283+
return store.LabelValues(ctx, req)
284+
}
285285

286286
// scanUsers in the bucket and return the list of found users. If an error occurs while
287287
// iterating the bucket, it may return both an error and a subset of the users in the bucket.

pkg/storegateway/gateway.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,9 @@ func (g *StoreGateway) LabelNames(ctx context.Context, req *storepb.LabelNamesRe
325325
}
326326

327327
// LabelValues implements the Storegateway proto service.
328-
// func (g *StoreGateway) LabelValues(ctx context.Context, req *storepb.LabelValuesRequest) (*storepb.LabelValuesResponse, error) {
329-
// return g.stores.LabelValues(ctx, req)
330-
// }
328+
func (g *StoreGateway) LabelValues(ctx context.Context, req *storepb.LabelValuesRequest) (*storepb.LabelValuesResponse, error) {
329+
return g.stores.LabelValues(ctx, req)
330+
}
331331

332332
func (g *StoreGateway) OnRingInstanceRegister(_ *ring.BasicLifecycler, ringDesc ring.Desc, instanceExists bool, instanceID string, instanceDesc ring.IngesterDesc) (ring.IngesterState, ring.Tokens) {
333333
// When we initialize the store-gateway instance in the ring we want to start from

0 commit comments

Comments
 (0)