@@ -37,6 +37,7 @@ import (
37
37
connectapi "github.com/grafana/pyroscope/pkg/api/connect"
38
38
"github.com/grafana/pyroscope/pkg/clientpool"
39
39
"github.com/grafana/pyroscope/pkg/distributor/aggregator"
40
+ "github.com/grafana/pyroscope/pkg/distributor/ingest_limits"
40
41
distributormodel "github.com/grafana/pyroscope/pkg/distributor/model"
41
42
writepath "github.com/grafana/pyroscope/pkg/distributor/write_path"
42
43
phlaremodel "github.com/grafana/pyroscope/pkg/model"
@@ -99,6 +100,7 @@ type Distributor struct {
99
100
ingestionRateLimiter * limiter.RateLimiter
100
101
aggregator * aggregator.MultiTenantAggregator [* pprof.ProfileMerge ]
101
102
asyncRequests sync.WaitGroup
103
+ ingestionLimitsSampler * ingest_limits.Sampler
102
104
103
105
subservices * services.Manager
104
106
subservicesWatcher * services.FailureWatcher
@@ -117,6 +119,7 @@ type Distributor struct {
117
119
type Limits interface {
118
120
IngestionRateBytes (tenantID string ) float64
119
121
IngestionBurstSizeBytes (tenantID string ) int
122
+ IngestionLimit (tenantID string ) * ingest_limits.Config
120
123
IngestionTenantShardSize (tenantID string ) int
121
124
MaxLabelNameLength (tenantID string ) int
122
125
MaxLabelValueLength (tenantID string ) int
@@ -187,7 +190,9 @@ func New(
187
190
return nil , err
188
191
}
189
192
190
- subservices = append (subservices , distributorsLifecycler , distributorsRing , d .aggregator )
193
+ d .ingestionLimitsSampler = ingest_limits .NewSampler (distributorsRing )
194
+
195
+ subservices = append (subservices , distributorsLifecycler , distributorsRing , d .aggregator , d .ingestionLimitsSampler )
191
196
192
197
d .ingestionRateLimiter = limiter .NewRateLimiter (newGlobalRateStrategy (newIngestionRateStrategy (limits ), d ), 10 * time .Second )
193
198
d .distributorsLifecycler = distributorsLifecycler
@@ -302,6 +307,12 @@ func (d *Distributor) PushParsed(ctx context.Context, req *distributormodel.Push
302
307
d .metrics .receivedCompressedBytes .WithLabelValues (string (profName ), tenantID ).Observe (float64 (req .RawProfileSize ))
303
308
}
304
309
310
+ d .calculateRequestSize (req )
311
+
312
+ if err := d .checkIngestLimit (tenantID , req ); err != nil {
313
+ return nil , err
314
+ }
315
+
305
316
if err := d .rateLimit (tenantID , req ); err != nil {
306
317
return nil , err
307
318
}
@@ -310,7 +321,7 @@ func (d *Distributor) PushParsed(ctx context.Context, req *distributormodel.Push
310
321
311
322
for _ , series := range req .Series {
312
323
profName := phlaremodel .Labels (series .Labels ).Get (ProfileName )
313
- groups := usageGroups .GetUsageGroups (tenantID , phlaremodel . Labels ( series .Labels ) )
324
+ groups := usageGroups .GetUsageGroups (tenantID , series .Labels )
314
325
profLanguage := d .GetProfileLanguage (series )
315
326
316
327
for _ , raw := range series .Samples {
@@ -709,6 +720,17 @@ func (d *Distributor) limitMaxSessionsPerSeries(maxSessionsPerSeries int, labels
709
720
}
710
721
711
722
func (d * Distributor ) rateLimit (tenantID string , req * distributormodel.PushRequest ) error {
723
+ if ! d .ingestionRateLimiter .AllowN (time .Now (), tenantID , int (req .TotalBytesUncompressed )) {
724
+ validation .DiscardedProfiles .WithLabelValues (string (validation .RateLimited ), tenantID ).Add (float64 (req .TotalProfiles ))
725
+ validation .DiscardedBytes .WithLabelValues (string (validation .RateLimited ), tenantID ).Add (float64 (req .TotalBytesUncompressed ))
726
+ return connect .NewError (connect .CodeResourceExhausted ,
727
+ fmt .Errorf ("push rate limit (%s) exceeded while adding %s" , humanize .IBytes (uint64 (d .limits .IngestionRateBytes (tenantID ))), humanize .IBytes (uint64 (req .TotalBytesUncompressed ))),
728
+ )
729
+ }
730
+ return nil
731
+ }
732
+
733
+ func (d * Distributor ) calculateRequestSize (req * distributormodel.PushRequest ) {
712
734
for _ , series := range req .Series {
713
735
// include the labels in the size calculation
714
736
for _ , lbs := range series .Labels {
@@ -720,14 +742,26 @@ func (d *Distributor) rateLimit(tenantID string, req *distributormodel.PushReque
720
742
req .TotalBytesUncompressed += int64 (raw .Profile .SizeVT ())
721
743
}
722
744
}
723
- // rate limit the request
724
- if ! d .ingestionRateLimiter .AllowN (time .Now (), tenantID , int (req .TotalBytesUncompressed )) {
725
- validation .DiscardedProfiles .WithLabelValues (string (validation .RateLimited ), tenantID ).Add (float64 (req .TotalProfiles ))
726
- validation .DiscardedBytes .WithLabelValues (string (validation .RateLimited ), tenantID ).Add (float64 (req .TotalBytesUncompressed ))
745
+ }
746
+
747
+ func (d * Distributor ) checkIngestLimit (tenantID string , req * distributormodel.PushRequest ) error {
748
+ l := d .limits .IngestionLimit (tenantID )
749
+ if l == nil {
750
+ return nil
751
+ }
752
+
753
+ if l .LimitReached {
754
+ // we want to allow a very small portion of the traffic after reaching the limit
755
+ if d .ingestionLimitsSampler .AllowRequest (tenantID , l .Sampling ) {
756
+ return nil
757
+ }
758
+ limitResetTime := time .Unix (l .LimitResetTime , 0 ).UTC ().Format (time .RFC3339 )
759
+ validation .DiscardedProfiles .WithLabelValues (string (validation .IngestLimitReached ), tenantID ).Add (float64 (req .TotalProfiles ))
760
+ validation .DiscardedBytes .WithLabelValues (string (validation .IngestLimitReached ), tenantID ).Add (float64 (req .TotalBytesUncompressed ))
727
761
return connect .NewError (connect .CodeResourceExhausted ,
728
- fmt .Errorf ("push rate limit (%s) exceeded while adding %s" , humanize .IBytes (uint64 (d .limits .IngestionRateBytes (tenantID ))), humanize .IBytes (uint64 (req .TotalBytesUncompressed ))),
729
- )
762
+ fmt .Errorf ("limit of %s/%s reached, next reset at %s" , humanize .IBytes (uint64 (l .PeriodLimitMb * 1024 * 1024 )), l .PeriodType , limitResetTime ))
730
763
}
764
+
731
765
return nil
732
766
}
733
767
0 commit comments