[Proposal] Share Threshold for Wildcard Rate Limiting#1016
Conversation
…eshold Signed-off-by: Nam Dang <xuannam230201@gmail.com>
|
Hi @collin-lee , could you please help to take a look at this proposal? Does it make sense to apply this? Thanks in advance! |
collin-lee
left a comment
There was a problem hiding this comment.
Think it's a good idea. I left some comments.
|
|
||
| // Track share_threshold patterns for entries matched via wildcard (using indexes) | ||
| // This allows share_threshold to work when wildcard has nested descriptors | ||
| shareThresholdPatterns := make(map[int]string) |
There was a problem hiding this comment.
// Instead of unconditional allocation:
var shareThresholdPatterns map[int]string // nil initially
// Only allocate when needed:
if matchedViaWildcard && nextDescriptor != nil && nextDescriptor.shareThreshold {
if shareThresholdPatterns == nil {
shareThresholdPatterns = make(map[int]string)
}
// ... rest of logic
}
| Replaces: originalLimit.Replaces, | ||
| DetailedMetric: originalLimit.DetailedMetric, | ||
| // Initialize ShareThresholdKeyPattern with correct length, empty strings for entries without share_threshold | ||
| ShareThresholdKeyPattern: make([]string, len(descriptor.Entries)), |
There was a problem hiding this comment.
Maybe only allocate ShareThresholdKeyPattern when patterns are tracked:
if i == len(descriptor.Entries)-1 {
// Create a copy of the rate limit to avoid modifying the shared object
originalLimit := nextDescriptor.limit
rateLimit = &RateLimit{
FullKey: originalLimit.FullKey,
Stats: originalLimit.Stats,
Limit: originalLimit.Limit,
Unlimited: originalLimit.Unlimited,
ShadowMode: originalLimit.ShadowMode,
Name: originalLimit.Name,
Replaces: originalLimit.Replaces,
DetailedMetric: originalLimit.DetailedMetric,
// Initialize ShareThresholdKeyPattern to nil - will be allocated only if needed
ShareThresholdKeyPattern: nil,
}
// Apply all tracked share_threshold patterns when we find the rate_limit
// This works whether the rate_limit is at the wildcard level or deeper
// Only allocate the slice if we actually have patterns to store
if len(shareThresholdPatterns) > 0 {
rateLimit.ShareThresholdKeyPattern = make([]string, len(descriptor.Entries))
for idx, pattern := range shareThresholdPatterns {
rateLimit.ShareThresholdKeyPattern[idx] = pattern
logger.Debugf("share_threshold enabled for entry index %d, using wildcard pattern %s", idx, pattern)
}
}
}
Signed-off-by: Nam Dang <xuannam230201@gmail.com>
|
@collin-lee , I updated based on your comments. Could you please take a look at this PR again, thank you! |
|
Hi @collin-lee , I see the build was failed after this PR was merged. But I think the failed thing doesn't relate to this change. Could you please help to take a look? Does it make sense to re-trigger the build? I think it's flaky because I ran few times locally and it was passed. Thanks in advance.
|

Proposal: Share Threshold for Wildcard Rate Limiting
Problem Statement
Currently, rate limiting rules for wildcard values create isolated thresholds for each matching value. While this provides fine-grained control, there are scenarios where multiple values matching a wildcard pattern should share a single rate limit threshold.
Current Behavior
When a wildcard pattern like
files/*is defined, each matching value (e.g.,files/a.pdf,files/b.csv,files/c.txt) gets its own isolated rate limit counter. This means:files/a.pdfhas its own quota of 100 requests/hourfiles/b.csvhas its own quota of 100 requests/hourfiles/c.txthas its own quota of 100 requests/hourEach can consume up to 100 requests/hour independently.
Use Cases Requiring Shared Thresholds
There are legitimate use cases where all values matching a wildcard should share a single threshold:
File-based rate limiting: When rate limiting access to files under a path pattern (e.g.,
api/files/{file_name}), you may want to limit total access across all files rather than per-file.Resource-based rate limiting: For resources like models (e.g.,
api/models/{model_name}), you may want to limit total API calls across all models rather than per-model.Version-based rate limiting: For API versions (e.g.,
api/v1/*,api/v2/*,api/v3/*), you may want to limit total requests across all versions.Cost control: When different wildcard values represent resources with similar costs, sharing thresholds helps control aggregate resource consumption.
Example Scenario
Consider a rate limit configuration:
With current behavior:
files/a.pdfuses 1 of 10 quotafiles/b.csvuses 1 of 10 quota (separate from a.pdf)With shared threshold (desired):
files/a.pdfuses 1 of 10 shared quotafiles/b.csvuses 1 of 10 shared quota (same pool)Proposed Solution
Add a new optional field
share_threshold: trueto descriptor entries with wildcard values. When enabled, all values matching that wildcard pattern will share the same rate limit threshold.Configuration Syntax
Behavior
Default behavior (backward compatible): When
share_thresholdis not specified or set tofalse, each wildcard value gets its own isolated threshold (current behavior).Shared threshold: When
share_threshold: trueis set:Validation:
share_thresholdcan only be used with wildcard values (values ending with*). Using it with non-wildcard values will result in a configuration error.Explicit value precedence: Explicit values (non-wildcard) always take precedence over wildcard matches, even when
share_thresholdis enabled on the wildcard.Example Configurations
Example 1: Basic Shared Threshold
files/a.pdf,files/b.csv,files/subdir/c.txtall share a pool of 100 requests/hourfiles/a.pdfand 50 forfiles/b.csv, a request forfiles/c.txtwill be rate limitedExample 2: Mixed Shared and Isolated
files/*values share 10 requests/hourmodels/*value gets its own 20 requests/hourImplementation Details
Cache Key Generation
When
share_threshold: trueis enabled, the cache key uses the wildcard pattern instead of the actual runtime value:domain_key_files/a.pdf_<timestamp>domain_key_files/*_<timestamp>This ensures all matching values use the same cache key and share the same counter.
Metrics
When
share_thresholdis combined withvalue_to_metricordetailed_metric:*) instead of the full runtime valueExample:
route=api/v1,method=GETdomain.route_api.method_GET(includes wildcard prefixapiinstead of full valueapi/v1)Backward Compatibility
false, maintaining current isolated threshold behaviorshare_threshold: trueto enable shared thresholdsTesting
The implementation includes:
value_to_metricanddetailed_metric