cache: add prefix/glob watch support to LinearCache for LEDS#1411
Conversation
aefeae0 to
441aa0e
Compare
wdauchy
left a comment
There was a problem hiding this comment.
fyi I started to merge this on Datadog production to see how far we can go with it
valerian-roche
left a comment
There was a problem hiding this comment.
Logic looks okay, but it should not impact users not using the feature.
I'm also concerned on all those full cache iterations which can be very costly on endpoints cache (where wildcards are not used hence never happen today).
As xds TP1 mandates a path to be along the line of xds:://<type>/<some>/<segments>/..., we should potentially build a tree matcher to guarantee O(segments) instead of O(entries)
| return true | ||
| } | ||
| for sub := range subscribedResources { | ||
| if prefix, ok := isPrefixGlob(sub); ok { |
There was a problem hiding this comment.
That's a lot of matching everyone will pay even if not using the feature, changing the check from O(1) to O(subscribed).
Could it be changed to be stored in a different set?
TP1 does not define rules on the use of / within resources so I don't think O(1) is achievable, but the cost should at least be restricted to collections and not all resources
There was a problem hiding this comment.
agreed, fixed. isSubscribedTo now does a single O(1) map lookup on SubscribedResources() first. the prefix iteration only kicks in when SubscribedPrefixes() is non-empty, so users not using the feature pay zero extra cost
| } | ||
| } else { | ||
| for resourceName := range sub.SubscribedResources() { | ||
| if prefix, ok := isPrefixGlob(resourceName); ok { |
There was a problem hiding this comment.
Could this be reverted? Identify all prefixes then iterate once above the cache? Iterating over a large map is slow and thrash caches
There was a problem hiding this comment.
done, prefix expansion now does a single pass over the cache checking all prefixes per resource (inner loop over prefixes, outer loop over cache entries). the whole block is also guarded by len(subscribedPrefixes) > 0 so it's completely skipped when no prefix subscriptions exist
| } | ||
| } | ||
| // Detect resources previously returned under this prefix that were deleted. | ||
| for knownName := range knownVersions { |
There was a problem hiding this comment.
Can you clarify this one? A resource can still be directly requested or match another collection (theoretically). We should not check for negative this way, but by checking what's still matching at the end compared to previously known
Your check at line 270 should be handling this
There was a problem hiding this comment.
good point, removed the inline deletion check entirely. the consolidated loop over knownVersions now handles both cases in one place: unsubscription (!isSubscribedTo) and cache deletion (still subscribed but !inCache). this also correctly handles a resource that matches both an exact subscription and a prefix
76e794f to
307eaa3
Compare
|
addressed the feedback. re: tree matcher for O(segments), makes sense as a follow-up, the current approach is O(prefixes) per resource which should be fine for now to start with something |
|
@valerian-roche if you have some time to have a look |
valerian-roche
left a comment
There was a problem hiding this comment.
One minor comment left, otherwise lgtm.
Thanks for rewriting the PR in a way that avoids most impact for non-prefix users
| if _, ok := sub.SubscribedResources()[resourceName]; !ok { | ||
| if !isSubscribedTo(sub, resourceName) { | ||
| removedResources = append(removedResources, resourceName) | ||
| } else if _, inCache := cache.resources[resourceName]; !inCache { |
There was a problem hiding this comment.
Minor: I'd expect cache evolution to be more common than watch removal, and isSubscribedTo is more expensive when prefixes are used, so might be better to revert evaluation order
There was a problem hiding this comment.
good point, swapped the order, cache deletion check (O(1) map lookup) now comes first, isSubscribedTo only runs if the resource is still in cache
Envoy's LEDS uses delta xDS with glob collection subscriptions where clients subscribe to resource names ending in /* (e.g. ns_svc_8080/us-east-1a/*) and expect to receive all individual resources whose name starts with that prefix. Currently LinearCache only supports exact-name watches and full wildcard watches. A subscription to collection/* is stored as a literal key in resourceWatches and never matches individual resources under that prefix. Add a new prefixWatches map to LinearCache that is keyed by the prefix (with trailing * stripped, keeping the separator). When a subscribed resource name ends with /*, the watch is registered in prefixWatches instead of resourceWatches. notifyAll iterates prefixWatches for each modified resource and triggers matching prefix watches. computeResourceChange expands prefix subscriptions to all matching resources in the cache, detecting new, updated, and deleted resources under the prefix. Signed-off-by: William Dauchy <william.dauchy@datadoghq.com>
Address review feedback: - Move glob detection into Subscription (SubscribedPrefixes) so prefix sets are precomputed once at subscription time instead of checked on every cache operation. - Single pass over cache.resources for prefix expansion instead of one iteration per prefix subscription. - Consolidated removal check: detect both unsubscription and cache deletion in a single loop over knownVersions. - isSubscribedTo is O(1) when no prefix subscriptions are active. - Remove isPrefixGlob and isResourceMatchingSubscription helpers from the cache package. Signed-off-by: William Dauchy <william.dauchy@datadoghq.com>
Signed-off-by: William Dauchy <william.dauchy@datadoghq.com>
Signed-off-by: William Dauchy <william.dauchy@datadoghq.com>
Signed-off-by: William Dauchy <william.dauchy@datadoghq.com>
d0daea0 to
1c25d95
Compare
|
golangci-lint are failing but I don't think it is related to my PR |
|
@valerian-roche is it ready to merge? |
@wdauchy Can you rebase? The linter issue has been addressed on |
should be good now! |
Envoy's LEDS (Locality Endpoint Discovery Service) uses delta xDS with
glob collection subscriptions. Clients subscribe to resource names ending
in
/*(e.g.ns_svc_8080/us-east-1a/*) viaresource_names_subscribein
DeltaDiscoveryRequest, and expect to receive all individual resourceswhose name starts with that prefix.
Currently
LinearCacheonly supports exact-name watches (resourceWatches)and full wildcard (
wildcardWatches). A subscription tocollection/*isstored as a literal key in
resourceWatchesand never matches individualresources under that prefix.
This PR adds prefix/glob watch support to
LinearCache:Subscription layer (
subscription.go):subscribedPrefixesmap (keyed by the prefix, e.g."collection/")SubscribedPrefixes()is added to theSubscriptioninterface so thecache never has to check for globbing at operation time
Cache layer (
linear.go):prefixWatchesmap handles glob subscriptionstrackWatch/removeWatch: useSubscribedPrefixes()directly toregister/clean up prefix watches — no glob parsing at watch time
notifyAll: iteratesprefixWatchesfor each modified resource andtriggers matching prefix watches
computeResourceChange: single pass over the cache for prefixexpansion (all prefixes checked per resource). Consolidated removal loop
handles both unsubscription and cache deletion in one place
computeResponse: single cache pass for prefix expansion in thefull-state non-wildcard branch
isSubscribedTo: O(1) map lookup when no prefix subscriptions areactive, zero overhead for users not using the feature
Existing exact-name and wildcard behavior is unchanged.