@@ -37,6 +37,7 @@ import (
3737 connectapi "github.com/grafana/pyroscope/pkg/api/connect"
3838 "github.com/grafana/pyroscope/pkg/clientpool"
3939 "github.com/grafana/pyroscope/pkg/distributor/aggregator"
40+ "github.com/grafana/pyroscope/pkg/distributor/ingest_limits"
4041 distributormodel "github.com/grafana/pyroscope/pkg/distributor/model"
4142 writepath "github.com/grafana/pyroscope/pkg/distributor/write_path"
4243 phlaremodel "github.com/grafana/pyroscope/pkg/model"
@@ -99,6 +100,7 @@ type Distributor struct {
99100 ingestionRateLimiter * limiter.RateLimiter
100101 aggregator * aggregator.MultiTenantAggregator [* pprof.ProfileMerge ]
101102 asyncRequests sync.WaitGroup
103+ ingestionLimitsSampler * ingest_limits.Sampler
102104
103105 subservices * services.Manager
104106 subservicesWatcher * services.FailureWatcher
@@ -117,6 +119,7 @@ type Distributor struct {
117119type Limits interface {
118120 IngestionRateBytes (tenantID string ) float64
119121 IngestionBurstSizeBytes (tenantID string ) int
122+ IngestionLimit (tenantID string ) * ingest_limits.Config
120123 IngestionTenantShardSize (tenantID string ) int
121124 MaxLabelNameLength (tenantID string ) int
122125 MaxLabelValueLength (tenantID string ) int
@@ -187,7 +190,9 @@ func New(
187190 return nil , err
188191 }
189192
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 )
191196
192197 d .ingestionRateLimiter = limiter .NewRateLimiter (newGlobalRateStrategy (newIngestionRateStrategy (limits ), d ), 10 * time .Second )
193198 d .distributorsLifecycler = distributorsLifecycler
@@ -302,6 +307,12 @@ func (d *Distributor) PushParsed(ctx context.Context, req *distributormodel.Push
302307 d .metrics .receivedCompressedBytes .WithLabelValues (string (profName ), tenantID ).Observe (float64 (req .RawProfileSize ))
303308 }
304309
310+ d .calculateRequestSize (req )
311+
312+ if err := d .checkIngestLimit (tenantID , req ); err != nil {
313+ return nil , err
314+ }
315+
305316 if err := d .rateLimit (tenantID , req ); err != nil {
306317 return nil , err
307318 }
@@ -310,7 +321,7 @@ func (d *Distributor) PushParsed(ctx context.Context, req *distributormodel.Push
310321
311322 for _ , series := range req .Series {
312323 profName := phlaremodel .Labels (series .Labels ).Get (ProfileName )
313- groups := usageGroups .GetUsageGroups (tenantID , phlaremodel . Labels ( series .Labels ) )
324+ groups := usageGroups .GetUsageGroups (tenantID , series .Labels )
314325 profLanguage := d .GetProfileLanguage (series )
315326
316327 for _ , raw := range series .Samples {
@@ -709,6 +720,17 @@ func (d *Distributor) limitMaxSessionsPerSeries(maxSessionsPerSeries int, labels
709720}
710721
711722func (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 ) {
712734 for _ , series := range req .Series {
713735 // include the labels in the size calculation
714736 for _ , lbs := range series .Labels {
@@ -720,14 +742,26 @@ func (d *Distributor) rateLimit(tenantID string, req *distributormodel.PushReque
720742 req .TotalBytesUncompressed += int64 (raw .Profile .SizeVT ())
721743 }
722744 }
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 ))
727761 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 ))
730763 }
764+
731765 return nil
732766}
733767
0 commit comments