Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix shadow mode metric is not collected when local cache hit #424

Merged
merged 1 commit into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions src/limiter/base_limiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ func (this *BaseRateLimiter) GetResponseDescriptorStatus(key string, limitInfo *
nil, 0)
}
var responseDescriptorStatus *pb.RateLimitResponse_DescriptorStatus
over_limit := false
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not follow the Golang naming convention.

isOverLimit := false
if isOverLimitWithLocalCache {
over_limit = true
isOverLimit = true
limitInfo.limit.Stats.OverLimit.Add(uint64(hitsAddend))
limitInfo.limit.Stats.OverLimitWithLocalCache.Add(uint64(hitsAddend))
responseDescriptorStatus = this.generateResponseDescriptorStatus(pb.RateLimitResponse_OVER_LIMIT,
Expand All @@ -94,7 +94,7 @@ func (this *BaseRateLimiter) GetResponseDescriptorStatus(key string, limitInfo *
limitInfo.nearLimitThreshold = uint32(math.Floor(float64(float32(limitInfo.overLimitThreshold) * this.nearLimitRatio)))
logger.Debugf("cache key: %s current: %d", key, limitInfo.limitAfterIncrease)
if limitInfo.limitAfterIncrease > limitInfo.overLimitThreshold {
over_limit = true
isOverLimit = true
responseDescriptorStatus = this.generateResponseDescriptorStatus(pb.RateLimitResponse_OVER_LIMIT,
limitInfo.limit.Limit, 0)

Expand Down Expand Up @@ -124,11 +124,11 @@ func (this *BaseRateLimiter) GetResponseDescriptorStatus(key string, limitInfo *
}

// If the limit is in ShadowMode, it should be always return OK
// We only want to increase stats if the limit was actually over the limit
if over_limit && limitInfo.limit.ShadowMode {
if isOverLimit && limitInfo.limit.ShadowMode {
logger.Debugf("Limit with key %s, is in shadow_mode", limitInfo.limit.FullKey)
responseDescriptorStatus.Code = pb.RateLimitResponse_OK
limitInfo.limit.Stats.ShadowMode.Add(uint64(hitsAddend))
// Increase shadow mode stats if the limit was actually over the limit
this.increaseShadowModeStats(isOverLimitWithLocalCache, limitInfo, hitsAddend)
}

return responseDescriptorStatus
Expand Down Expand Up @@ -178,6 +178,16 @@ func (this *BaseRateLimiter) checkNearLimitThreshold(limitInfo *LimitInfo, hitsA
}
}

func (this *BaseRateLimiter) increaseShadowModeStats(isOverLimitWithLocalCache bool, limitInfo *LimitInfo, hitsAddend uint32) {
// Increase shadow mode statistics. For the same reason as over limit stats,
// if the limit value before adding the N hits over the limit, then all N hits were over limit.
if isOverLimitWithLocalCache || limitInfo.limitBeforeIncrease >= limitInfo.overLimitThreshold {
limitInfo.limit.Stats.ShadowMode.Add(uint64(hitsAddend))
} else {
limitInfo.limit.Stats.ShadowMode.Add(uint64(limitInfo.limitAfterIncrease - limitInfo.overLimitThreshold))
}
}

func (this *BaseRateLimiter) generateResponseDescriptorStatus(responseCode pb.RateLimitResponse_Code,
limit *pb.RateLimitResponse_RateLimit, limitRemaining uint32) *pb.RateLimitResponse_DescriptorStatus {
if limit != nil {
Expand Down
4 changes: 1 addition & 3 deletions src/redis/fixed_cache_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,12 @@ func (this *fixedRateLimitCacheImpl) DoLimit(

// Check if key is over the limit in local cache.
if this.baseRateLimiter.IsOverLimitWithLocalCache(cacheKey.Key) {

if limits[i].ShadowMode {
logger.Debugf("Cache key %s would be rate limited but shadow mode is enabled on this rule", cacheKey.Key)
} else {
logger.Debugf("cache key is over the limit: %s", cacheKey.Key)
isOverLimitWithLocalCache[i] = true
}

isOverLimitWithLocalCache[i] = true
Copy link
Contributor Author

@upgle upgle May 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Shadow mode and local cache should be irrelevant.
  • If the local cache is hit, the continue statement does not perform the INCR command, but the GetResponseDescriptorStatus method does not handle it properly.

continue
}

Expand Down
13 changes: 8 additions & 5 deletions test/redis/fixed_cache_impl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,7 @@ func TestOverLimitWithLocalCacheShadowRule(t *testing.T) {
assert.Equal(uint64(3), limits[0].Stats.TotalHits.Value())
assert.Equal(uint64(1), limits[0].Stats.OverLimit.Value())
assert.Equal(uint64(0), limits[0].Stats.OverLimitWithLocalCache.Value())
assert.Equal(uint64(1), limits[0].Stats.ShadowMode.Value())
assert.Equal(uint64(1), limits[0].Stats.NearLimit.Value())
assert.Equal(uint64(2), limits[0].Stats.WithinLimit.Value())

Expand All @@ -579,15 +580,17 @@ func TestOverLimitWithLocalCacheShadowRule(t *testing.T) {
// The result should be OK since limit is in ShadowMode
assert.Equal(
[]*pb.RateLimitResponse_DescriptorStatus{
{Code: pb.RateLimitResponse_OK, CurrentLimit: limits[0].Limit, LimitRemaining: 15, DurationUntilReset: utils.CalculateReset(&limits[0].Limit.Unit, timeSource)},
{Code: pb.RateLimitResponse_OK, CurrentLimit: limits[0].Limit, LimitRemaining: 0, DurationUntilReset: utils.CalculateReset(&limits[0].Limit.Unit, timeSource)},
},
cache.DoLimit(context.Background(), request, limits))
// TODO: How should we handle statistics? Should there be a separate ShadowMode statistics? Should the other Stats remain as if they were unaffected by shadowmode?

// Even if you hit the local cache, other metrics should increase normally.
assert.Equal(uint64(4), limits[0].Stats.TotalHits.Value())
assert.Equal(uint64(1), limits[0].Stats.OverLimit.Value())
assert.Equal(uint64(0), limits[0].Stats.OverLimitWithLocalCache.Value())
assert.Equal(uint64(2), limits[0].Stats.OverLimit.Value())
assert.Equal(uint64(1), limits[0].Stats.OverLimitWithLocalCache.Value())
assert.Equal(uint64(2), limits[0].Stats.ShadowMode.Value())
assert.Equal(uint64(1), limits[0].Stats.NearLimit.Value())
assert.Equal(uint64(3), limits[0].Stats.WithinLimit.Value())
assert.Equal(uint64(2), limits[0].Stats.WithinLimit.Value())
Copy link
Contributor Author

@upgle upgle May 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This value should be 2. Please refer to the original test code for local cache.
(Even if shadow mode is enabled, this value should not be changed.)

assert.Equal(uint64(4), limits[0].Stats.TotalHits.Value())
assert.Equal(uint64(2), limits[0].Stats.OverLimit.Value())
assert.Equal(uint64(1), limits[0].Stats.OverLimitWithLocalCache.Value())
assert.Equal(uint64(1), limits[0].Stats.NearLimit.Value())
assert.Equal(uint64(2), limits[0].Stats.WithinLimit.Value())


// Check the local cache stats.
testLocalCacheStats(localCacheStats, statsStore, sink, 1, 3, 4, 0, 1)
Expand Down