github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ingester/limiter.go (about)

     1  package ingester
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"sync"
     7  	"time"
     8  
     9  	"golang.org/x/time/rate"
    10  
    11  	"github.com/grafana/loki/pkg/validation"
    12  )
    13  
    14  const (
    15  	errMaxStreamsPerUserLimitExceeded = "tenant '%v' per-user streams limit exceeded, streams: %d exceeds calculated limit: %d (local limit: %d, global limit: %d, global/ingesters: %d)"
    16  )
    17  
    18  // RingCount is the interface exposed by a ring implementation which allows
    19  // to count members
    20  type RingCount interface {
    21  	HealthyInstancesCount() int
    22  }
    23  
    24  // Limiter implements primitives to get the maximum number of streams
    25  // an ingester can handle for a specific tenant
    26  type Limiter struct {
    27  	limits            *validation.Overrides
    28  	ring              RingCount
    29  	replicationFactor int
    30  	metrics           *ingesterMetrics
    31  
    32  	mtx      sync.RWMutex
    33  	disabled bool
    34  }
    35  
    36  func (l *Limiter) DisableForWALReplay() {
    37  	l.mtx.Lock()
    38  	defer l.mtx.Unlock()
    39  	l.disabled = true
    40  	l.metrics.limiterEnabled.Set(0)
    41  }
    42  
    43  func (l *Limiter) Enable() {
    44  	l.mtx.Lock()
    45  	defer l.mtx.Unlock()
    46  	l.disabled = false
    47  	l.metrics.limiterEnabled.Set(1)
    48  }
    49  
    50  // NewLimiter makes a new limiter
    51  func NewLimiter(limits *validation.Overrides, metrics *ingesterMetrics, ring RingCount, replicationFactor int) *Limiter {
    52  	return &Limiter{
    53  		limits:            limits,
    54  		ring:              ring,
    55  		replicationFactor: replicationFactor,
    56  		metrics:           metrics,
    57  	}
    58  }
    59  
    60  func (l *Limiter) UnorderedWrites(userID string) bool {
    61  	// WAL replay should not discard previously ack'd writes,
    62  	// so allow out of order writes while the limiter is disabled.
    63  	// This allows replaying unordered WALs into ordered configurations.
    64  	if l.disabled {
    65  		return true
    66  	}
    67  	return l.limits.UnorderedWrites(userID)
    68  }
    69  
    70  // AssertMaxStreamsPerUser ensures limit has not been reached compared to the current
    71  // number of streams in input and returns an error if so.
    72  func (l *Limiter) AssertMaxStreamsPerUser(userID string, streams int) error {
    73  	// Until the limiter actually starts, all accesses are successful.
    74  	// This is used to disable limits while recovering from the WAL.
    75  	l.mtx.RLock()
    76  	defer l.mtx.RUnlock()
    77  	if l.disabled {
    78  		return nil
    79  	}
    80  
    81  	// Start by setting the local limit either from override or default
    82  	localLimit := l.limits.MaxLocalStreamsPerUser(userID)
    83  
    84  	// We can assume that streams are evenly distributed across ingesters
    85  	// so we do convert the global limit into a local limit
    86  	globalLimit := l.limits.MaxGlobalStreamsPerUser(userID)
    87  	adjustedGlobalLimit := l.convertGlobalToLocalLimit(globalLimit)
    88  
    89  	// Set the calculated limit to the lesser of the local limit or the new calculated global limit
    90  	calculatedLimit := l.minNonZero(localLimit, adjustedGlobalLimit)
    91  
    92  	// If both the local and global limits are disabled, we just
    93  	// use the largest int value
    94  	if calculatedLimit == 0 {
    95  		calculatedLimit = math.MaxInt32
    96  	}
    97  
    98  	if streams < calculatedLimit {
    99  		return nil
   100  	}
   101  
   102  	return fmt.Errorf(errMaxStreamsPerUserLimitExceeded, userID, streams, calculatedLimit, localLimit, globalLimit, adjustedGlobalLimit)
   103  }
   104  
   105  func (l *Limiter) convertGlobalToLocalLimit(globalLimit int) int {
   106  	if globalLimit == 0 {
   107  		return 0
   108  	}
   109  
   110  	// Given we don't need a super accurate count (ie. when the ingesters
   111  	// topology changes) and we prefer to always be in favor of the tenant,
   112  	// we can use a per-ingester limit equal to:
   113  	// (global limit / number of ingesters) * replication factor
   114  	numIngesters := l.ring.HealthyInstancesCount()
   115  
   116  	// May happen because the number of ingesters is asynchronously updated.
   117  	// If happens, we just temporarily ignore the global limit.
   118  	if numIngesters > 0 {
   119  		return int((float64(globalLimit) / float64(numIngesters)) * float64(l.replicationFactor))
   120  	}
   121  
   122  	return 0
   123  }
   124  
   125  func (l *Limiter) minNonZero(first, second int) int {
   126  	if first == 0 || (second != 0 && first > second) {
   127  		return second
   128  	}
   129  
   130  	return first
   131  }
   132  
   133  type RateLimiterStrategy interface {
   134  	RateLimit(tenant string) validation.RateLimit
   135  }
   136  
   137  func (l *Limiter) RateLimit(tenant string) validation.RateLimit {
   138  	if l.disabled {
   139  		return validation.Unlimited
   140  	}
   141  
   142  	return l.limits.PerStreamRateLimit(tenant)
   143  }
   144  
   145  type StreamRateLimiter struct {
   146  	recheckPeriod time.Duration
   147  	recheckAt     time.Time
   148  	strategy      RateLimiterStrategy
   149  	tenant        string
   150  	lim           *rate.Limiter
   151  }
   152  
   153  func NewStreamRateLimiter(strategy RateLimiterStrategy, tenant string, recheckPeriod time.Duration) *StreamRateLimiter {
   154  	rl := strategy.RateLimit(tenant)
   155  	return &StreamRateLimiter{
   156  		recheckPeriod: recheckPeriod,
   157  		strategy:      strategy,
   158  		tenant:        tenant,
   159  		lim:           rate.NewLimiter(rl.Limit, rl.Burst),
   160  	}
   161  }
   162  
   163  func (l *StreamRateLimiter) AllowN(at time.Time, n int) bool {
   164  	now := time.Now()
   165  	if now.After(l.recheckAt) {
   166  		l.recheckAt = now.Add(l.recheckPeriod)
   167  
   168  		oldLim := l.lim.Limit()
   169  		oldBurst := l.lim.Burst()
   170  
   171  		next := l.strategy.RateLimit(l.tenant)
   172  
   173  		if oldLim != next.Limit || oldBurst != next.Burst {
   174  			// Edge case: rate.Inf doesn't advance nicely when reconfigured.
   175  			// To simplify, we just create a new limiter after reconfiguration rather
   176  			// than alter the existing one.
   177  			l.lim = rate.NewLimiter(next.Limit, next.Burst)
   178  		}
   179  	}
   180  
   181  	return l.lim.AllowN(at, n)
   182  }