Skip to content

Commit 3b7aa3b

Browse files
authored
Returning custom grpc code when reaching series/chunk limits (#3903)
Signed-off-by: Alan Protasio <approtas@amazon.com>
1 parent 84c73dc commit 3b7aa3b

File tree

3 files changed

+70
-11
lines changed

3 files changed

+70
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ We use _breaking :warning:_ to mark changes that are not backward compatible (re
1313
## Unreleased
1414

1515
### Added
16+
- [#3903](https://github.com/thanos-io/thanos/pull/3903) Store: Returning custom grpc code when reaching series/chunk limits.
1617

1718
### Fixed
1819
- [#3204](https://github.com/thanos-io/thanos/pull/3204) Mixin: Use sidecar's metric timestamp for healthcheck.

pkg/store/bucket.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1026,7 +1026,11 @@ func (s *BucketStore) Series(req *storepb.SeriesRequest, srv storepb.Store_Serie
10261026
err = g.Wait()
10271027
})
10281028
if err != nil {
1029-
return status.Error(codes.Aborted, err.Error())
1029+
code := codes.Aborted
1030+
if s, ok := status.FromError(errors.Cause(err)); ok {
1031+
code = s.Code()
1032+
}
1033+
return status.Error(code, err.Error())
10301034
}
10311035
stats.blocksQueried = len(res)
10321036
stats.getAllDuration = time.Since(begin)

pkg/store/bucket_e2e_test.go

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,15 @@ import (
1414
"time"
1515

1616
"github.com/go-kit/kit/log"
17+
"github.com/gogo/status"
1718
"github.com/oklog/ulid"
19+
"github.com/prometheus/client_golang/prometheus"
1820
"github.com/prometheus/prometheus/pkg/labels"
1921
"github.com/prometheus/prometheus/pkg/relabel"
2022
"github.com/prometheus/prometheus/pkg/timestamp"
23+
"github.com/weaveworks/common/httpgrpc"
24+
"google.golang.org/grpc/codes"
25+
2126
"github.com/thanos-io/thanos/pkg/block"
2227
"github.com/thanos-io/thanos/pkg/block/metadata"
2328
"github.com/thanos-io/thanos/pkg/model"
@@ -45,6 +50,20 @@ type swappableCache struct {
4550
ptr storecache.IndexCache
4651
}
4752

53+
type customLimiter struct {
54+
limiter *Limiter
55+
code codes.Code
56+
}
57+
58+
func (c *customLimiter) Reserve(num uint64) error {
59+
err := c.limiter.Reserve(num)
60+
if err != nil {
61+
return httpgrpc.Errorf(int(c.code), err.Error())
62+
}
63+
64+
return nil
65+
}
66+
4867
func (c *swappableCache) SwapWith(ptr2 storecache.IndexCache) {
4968
c.ptr = ptr2
5069
}
@@ -113,7 +132,25 @@ func prepareTestBlocks(t testing.TB, now time.Time, count int, dir string, bkt o
113132
return
114133
}
115134

116-
func prepareStoreWithTestBlocks(t testing.TB, dir string, bkt objstore.Bucket, manyParts bool, maxChunksLimit uint64, relabelConfig []*relabel.Config, filterConf *FilterConfig) *storeSuite {
135+
func newCustomChunksLimiterFactory(limit uint64, code codes.Code) ChunksLimiterFactory {
136+
return func(failedCounter prometheus.Counter) ChunksLimiter {
137+
return &customLimiter{
138+
limiter: NewLimiter(limit, failedCounter),
139+
code: code,
140+
}
141+
}
142+
}
143+
144+
func newCustomSeriesLimiterFactory(limit uint64, code codes.Code) SeriesLimiterFactory {
145+
return func(failedCounter prometheus.Counter) SeriesLimiter {
146+
return &customLimiter{
147+
limiter: NewLimiter(limit, failedCounter),
148+
code: code,
149+
}
150+
}
151+
}
152+
153+
func prepareStoreWithTestBlocks(t testing.TB, dir string, bkt objstore.Bucket, manyParts bool, chunksLimiterFactory ChunksLimiterFactory, seriesLimiterFactory SeriesLimiterFactory, relabelConfig []*relabel.Config, filterConf *FilterConfig) *storeSuite {
117154
series := []labels.Labels{
118155
labels.FromStrings("a", "1", "b", "1"),
119156
labels.FromStrings("a", "1", "b", "2"),
@@ -151,8 +188,8 @@ func prepareStoreWithTestBlocks(t testing.TB, dir string, bkt objstore.Bucket, m
151188
s.cache,
152189
nil,
153190
nil,
154-
NewChunksLimiterFactory(maxChunksLimit),
155-
NewSeriesLimiterFactory(0),
191+
chunksLimiterFactory,
192+
seriesLimiterFactory,
156193
NewGapBasedPartitioner(PartitionerMaxGapSize),
157194
false,
158195
20,
@@ -425,7 +462,7 @@ func TestBucketStore_e2e(t *testing.T) {
425462
testutil.Ok(t, err)
426463
defer func() { testutil.Ok(t, os.RemoveAll(dir)) }()
427464

428-
s := prepareStoreWithTestBlocks(t, dir, bkt, false, 0, emptyRelabelConfig, allowAllFilterConf)
465+
s := prepareStoreWithTestBlocks(t, dir, bkt, false, NewChunksLimiterFactory(0), NewSeriesLimiterFactory(0), emptyRelabelConfig, allowAllFilterConf)
429466

430467
if ok := t.Run("no index cache", func(t *testing.T) {
431468
s.cache.SwapWith(noopCache{})
@@ -480,7 +517,7 @@ func TestBucketStore_ManyParts_e2e(t *testing.T) {
480517
testutil.Ok(t, err)
481518
defer func() { testutil.Ok(t, os.RemoveAll(dir)) }()
482519

483-
s := prepareStoreWithTestBlocks(t, dir, bkt, true, 0, emptyRelabelConfig, allowAllFilterConf)
520+
s := prepareStoreWithTestBlocks(t, dir, bkt, true, NewChunksLimiterFactory(0), NewSeriesLimiterFactory(0), emptyRelabelConfig, allowAllFilterConf)
484521

485522
indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(s.logger, nil, storecache.InMemoryIndexCacheConfig{
486523
MaxItemSize: 1e5,
@@ -508,7 +545,7 @@ func TestBucketStore_TimePartitioning_e2e(t *testing.T) {
508545
// The query will fetch 2 series from 2 blocks, so we do expect to hit a total of 4 chunks.
509546
expectedChunks := uint64(2 * 2)
510547

511-
s := prepareStoreWithTestBlocks(t, dir, bkt, false, expectedChunks, emptyRelabelConfig, &FilterConfig{
548+
s := prepareStoreWithTestBlocks(t, dir, bkt, false, NewChunksLimiterFactory(expectedChunks), NewSeriesLimiterFactory(0), emptyRelabelConfig, &FilterConfig{
512549
MinTime: minTimeDuration,
513550
MaxTime: filterMaxTime,
514551
})
@@ -554,14 +591,28 @@ func TestBucketStore_Series_ChunksLimiter_e2e(t *testing.T) {
554591

555592
cases := map[string]struct {
556593
maxChunksLimit uint64
594+
maxSeriesLimit uint64
557595
expectedErr string
596+
code codes.Code
558597
}{
559598
"should succeed if the max chunks limit is not exceeded": {
560599
maxChunksLimit: expectedChunks,
561600
},
562-
"should fail if the max chunks limit is exceeded": {
601+
"should fail if the max chunks limit is exceeded - ResourceExhausted": {
563602
maxChunksLimit: expectedChunks - 1,
564603
expectedErr: "exceeded chunks limit",
604+
code: codes.ResourceExhausted,
605+
},
606+
"should fail if the max chunks limit is exceeded - 422": {
607+
maxChunksLimit: expectedChunks - 1,
608+
expectedErr: "exceeded chunks limit",
609+
code: 422,
610+
},
611+
"should fail if the max series limit is exceeded - 422": {
612+
maxChunksLimit: expectedChunks,
613+
expectedErr: "exceeded series limit",
614+
maxSeriesLimit: 1,
615+
code: 422,
565616
},
566617
}
567618

@@ -575,7 +626,7 @@ func TestBucketStore_Series_ChunksLimiter_e2e(t *testing.T) {
575626
testutil.Ok(t, err)
576627
defer func() { testutil.Ok(t, os.RemoveAll(dir)) }()
577628

578-
s := prepareStoreWithTestBlocks(t, dir, bkt, false, testData.maxChunksLimit, emptyRelabelConfig, allowAllFilterConf)
629+
s := prepareStoreWithTestBlocks(t, dir, bkt, false, newCustomChunksLimiterFactory(testData.maxChunksLimit, testData.code), newCustomSeriesLimiterFactory(testData.maxSeriesLimit, testData.code), emptyRelabelConfig, allowAllFilterConf)
579630
testutil.Ok(t, s.store.SyncBlocks(ctx))
580631

581632
req := &storepb.SeriesRequest{
@@ -595,6 +646,9 @@ func TestBucketStore_Series_ChunksLimiter_e2e(t *testing.T) {
595646
} else {
596647
testutil.NotOk(t, err)
597648
testutil.Assert(t, strings.Contains(err.Error(), testData.expectedErr))
649+
status, ok := status.FromError(err)
650+
testutil.Equals(t, true, ok)
651+
testutil.Equals(t, testData.code, status.Code())
598652
}
599653
})
600654
}
@@ -609,7 +663,7 @@ func TestBucketStore_LabelNames_e2e(t *testing.T) {
609663
testutil.Ok(t, err)
610664
defer func() { testutil.Ok(t, os.RemoveAll(dir)) }()
611665

612-
s := prepareStoreWithTestBlocks(t, dir, bkt, false, 0, emptyRelabelConfig, allowAllFilterConf)
666+
s := prepareStoreWithTestBlocks(t, dir, bkt, false, NewChunksLimiterFactory(0), NewSeriesLimiterFactory(0), emptyRelabelConfig, allowAllFilterConf)
613667

614668
mint, maxt := s.store.TimeRange()
615669
testutil.Equals(t, s.minTime, mint)
@@ -642,7 +696,7 @@ func TestBucketStore_LabelValues_e2e(t *testing.T) {
642696
testutil.Ok(t, err)
643697
defer func() { testutil.Ok(t, os.RemoveAll(dir)) }()
644698

645-
s := prepareStoreWithTestBlocks(t, dir, bkt, false, 0, emptyRelabelConfig, allowAllFilterConf)
699+
s := prepareStoreWithTestBlocks(t, dir, bkt, false, NewChunksLimiterFactory(0), NewSeriesLimiterFactory(0), emptyRelabelConfig, allowAllFilterConf)
646700

647701
mint, maxt := s.store.TimeRange()
648702
testutil.Equals(t, s.minTime, mint)

0 commit comments

Comments
 (0)