-
Notifications
You must be signed in to change notification settings - Fork 14
US66693: Refactor policy evaluation decision caching to allow eviction of keys #115
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
Conversation
|
Notes on new caching: |
a058d10 to
b2a2b41
Compare
3b1079f to
295c645
Compare
irinaepshteyn
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review is still in progress. These are the initial comments...
| } catch (Exception e) { | ||
| LOGGER.error(String.format( | ||
| "Unable to set cache key '%s' to value '%s' due to redis exception", key, result), e); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error message should not be tied to redis. Could have other cache implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
| } catch (Exception e) { | ||
| LOGGER.error(String.format("Unable to get cache key '%s'", key), e); | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a unit test that policy evaluation returns expected result if cache get or set fails
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
|
|
||
| String zone = key.getZoneId(); | ||
| values.addAll(multiGet(cachedResult.getResolvedResourceUris().stream() | ||
| .map(resourceUri -> resourceKey(zone, resourceUri)).collect(Collectors.toList()))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you thought of including resolved URIs as part of evaluation result key to avoid another trip to the cache store? Something like:
zoneId:resourceId:resolvedURIList:...
Are we limited on the length of the key?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a unit test that verifies that get returns null if one of the resolved resources change. Resolved URI of the changed does not match resource URI in the request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a unit test that verifies that get returns null if timestamps for any entity including resolved URIs are not present.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I take it back. Resolved UIRs could not be part of the eval result key as they are not known at the time of the request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
| // Only need to assemble keys for policy sets, the subject, and the policy evaluation result. | ||
| // Resource-related information will be captured in the resolved resource URIs from the cached | ||
| // policy evaluation result. | ||
| List<String> keys = new ArrayList<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If resolvedURI were part of evaluation result key we could assemble resource keys for resolved URIs here.
| } | ||
| private void setEntityTimestamps(final PolicyEvaluationRequestCacheKey key, final PolicyEvaluationResult result) { | ||
| // This will lower the overall hit rate for policy evaluation but is required to ensure that if | ||
| // the cached timestamp for any entity involved in this decision is aged out, the cached decision will be used |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean WILL NOT be used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do mean will, but I think the message is confusing. I rewrote it.
| String key = getKey.apply(zoneId, entityId); | ||
| setIfNotExists(key, timestampValue()); | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a difference between setEntityIfNotExists and resetForEntity?
When eval result is being cached we could call resetForEntity to set the timestamp. It will create a entity key if doesn't exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setEntityIfNotExists will set the timestamp for the object if the object is not in the cache. resetForEntity will reset the timestamp regardless if the value if the value is in the cache
| multisetForResources(zoneId, resourceEntities.stream().map(resource -> resource.getResourceIdentifier()) | ||
| multisetForResources(zoneId, resourceEntities.stream().map(ResourceEntity::getResourceIdentifier) | ||
| .collect(Collectors.toList())); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are collecting resource Ids into a list and then build a map of keys inside multisetForResources. Could we combine it all in one call? Map streamed resourceEntities to resourceKey and collect into a map with the timestamp logging debug message for each... :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
| multisetForSubjects(zoneId, | ||
| subjectEntities.stream().map(subject -> subject.getSubjectIdentifier()).collect(Collectors.toList())); | ||
| subjectEntities.stream().map(SubjectEntity::getSubjectIdentifier).collect(Collectors.toList())); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as for resetForResources above. Could we combine resetForSubjects and multisetForSubjects in one long beautiful fluent java 8 call? :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
| boolean havePrivilegeServiceAttributesChanged(final List<String> values, final DateTime policyEvalTimestampUTC) { | ||
| for (String value : values) { | ||
| if (null == value) { | ||
| return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Values were already tested for nulls by isRequestCached before calling this method.
| "Privilege service attributes have timestamp '%s' which is after " | ||
| + "policy evaluation timestamp '%s'", | ||
| invalidationTimestampUTC, policyEvalTimestampUTC); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you are using lazy string construction we could rely on debug to check the debug level. No need for extra isDebugEnabled call. Will message formatting work with %s or should we use {}?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
irinaepshteyn
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More comments...
| // all entities involved in the request. | ||
| values.addAll(multiGet(cachedResult.getResolvedResourceUris().stream() | ||
| .map(resourceUri -> resourceKey(zone, resourceUri)).collect(Collectors.toList()))); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One optimization we could do here is to compare resource from request with resolved resource. If policy doesn't contain any template URIs then request resource and resolved resource are the same. In that case we don't have to perform a multiGet again. In that case when assembling keys for the first call to multiGet we need to include a resource from request again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
|
|
||
| void resetForPolicySet(String zoneId, String policySetId); | ||
|
|
||
| void setPolicySetIfNotExists(String zoneId, String policySetId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setPolicySetIfNotExists, setResourceIfNotExists, setSubjectIfNotExists do not need to be part of cache interface. These methods are implemented and used by AbstractPolicyEvaluationCache only. However, setIfNotExists should be defined by interface and implemented by each cache implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that setPolicySetIfNotExists, setResourceIfNotExists and setSubjectIfNotExists should not be part of the cache interface (because these are only used internally in the AbstractPolicyEvaluationCache). But why should setIfNotExists be apart of the interface if it is also only use internally?
| } | ||
| }); | ||
| void setIfNotExists(final String key, final String value) { | ||
| this.redisTemplate.boundValueOps(key).setIfAbsent(value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we set an expiration timeout on the key similar to what's done in line #89? The same comment applies to set function above. Timeout is only set on the key for eval result and not for any other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Timeout should not be set for anything other then Policy Evaluation. This is because we would then be unnecessarily reevaluating requests when entities have no changed.
| assertEquals(evalCache.size(), 0); | ||
| Map<String, String> timestampCache = (Map<String, String>) getInternalState(this.cache, "timestampCache"); | ||
| assertEquals(timestampCache.size(), 1); | ||
| assertEquals(evalCache.size(), 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be rename the test to testSetPolicySetChangedTimestamp?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Current implementation of the in-memory cache stores eval result and timestamps in the same map, not in different maps as before. There is no need to have different tests for setting eval result and setting timestamps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do feel like there is value in this test because of the logic to check the key format.
| } | ||
|
|
||
| @Test | ||
| public void testGetWithCacheHit() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a test to assert that get returns cached result if eval result is cached but not timestamps for entities.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If eval is cached but timestamps for entities are not, then get should return null
| this.cache.resetForResource(ZONE_NAME, XFILES_ID); | ||
| this.cache.resetForPolicySet(ZONE_NAME, POLICY_ONE.getName()); | ||
| this.cache.resetForPolicySet(ZONE_NAME, POLICY_TWO.getName()); | ||
| request.setAction(ACTION_GET); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need to call resets for each entity i every test? Get will return cached result if no timestamps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do, get will return null if no timestamps.
irinaepshteyn
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done reviewing.
6eb663c to
b4ac16e
Compare
b4ac16e to
cd4ca5b
Compare
|
Siva ran performance tests and he saw a 3ms increase in Policy Evaluation calls |
…n of keys Signed-off-by: Anubhav <[email protected]>
|
@anubhavi25 @FrankGasparovic - pls review my 2nd commit |
| Set<String> cachedResolvedResourceUris = cachedEvalResult.getResolvedResourceUris(); | ||
| //is requested resource id same as resolved resource uri ? | ||
| if (cachedResolvedResourceUris.size() == 1 | ||
| && cachedResolvedResourceUris.toArray()[0].equals(key.getResourceId())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use cachedResolvedResourceUris.iterator().next() vs toArray() so that we don't have to copy contents to array.
FrankGasparovic
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
anubhavi25
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good for the most part 👍
| return entryValues.get(lastValueIndex - 2); | ||
| } | ||
|
|
||
| String getRequestedResourceLastModified() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing the name to just getResourceLastModified should suffice (and is also consistent with the naming convention used for the other get*LastModified methods).
|
Performance tests show no regression. A second test was run which only writes resource and subject keys (which have no TTL) to confirm that eviction is working as expected. |
Signed-off-by: Anubhav [email protected]