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

feat: Added ODPSegmentManager #321

Merged
merged 24 commits into from
Nov 23, 2022
Merged

Conversation

mikechu-optimizely
Copy link
Contributor

Summary

Added an ODPSegmentManager which provides functionality to fetch segments from ODP server and cache and re-use them.

Test plan

  • Manually tested thoroughly
  • Added new unit tests
  • Existing unit tests pass
  • All Full-stack compatibility suite tests pass

Issues

FSSDK-8408

@mikechu-optimizely mikechu-optimizely marked this pull request as ready for review November 18, 2022 21:42
@mikechu-optimizely mikechu-optimizely requested a review from a team as a code owner November 18, 2022 21:42
Copy link
Contributor

@msohailhussain msohailhussain left a comment

Choose a reason for hiding this comment

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

lgtm. 2 nits please address.

select cacheItem.Key).ToArray();
lock (_mutex)
{
return _list.Join(_cache,
Copy link
Contributor

Choose a reason for hiding this comment

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

don't return inside the lock statement. get a result in a variable inside lock and return outside of lock.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great advice. Thanks for the learning.

if (!_odpConfig.HasSegments())
{
_logger.Log(LogLevel.DEBUG,
"No Segments are used in the project, Not Fetching segments. Returning empty list");
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please check with jae, Returning empty list may be revised.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do. @jaeopt ☝️

Copy link
Contributor Author

@mikechu-optimizely mikechu-optimizely Nov 22, 2022

Choose a reason for hiding this comment

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

I'm not completely sure how to read it, but it looks like the Swift implementation returns null

Java returns empty collection.

Python looks like it returns an empty array.

Go also seems to return an empty slice.


I do see I need a correction when ODP config is not ready. I should have returned null instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

@mikechu-optimizely For any fetch error (odp not integrated, network errors, etc), we should return null. For empty segmentToCheck, we should return empty array.

Copy link
Contributor

@jaeopt jaeopt left a comment

Choose a reason for hiding this comment

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

Looks good. A few changes suggested.

OptimizelySDK/Odp/OdpSegmentManager.cs Outdated Show resolved Hide resolved
OptimizelySDK/Odp/OdpSegmentManager.cs Outdated Show resolved Hide resolved
OptimizelySDK/Odp/OdpSegmentManager.cs Show resolved Hide resolved
Comment on lines 118 to 119
_cache.Where(
cacheItem => cacheItem.Value == leastRecentlyUsedItem.Value).
Copy link
Contributor

@jaeopt jaeopt Nov 21, 2022

Choose a reason for hiding this comment

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

Isn't this too slow O(n) for every item search when cache is full? Should be O(1). Wondering why your original "OrderedSet"-based one was discarded.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for asking this question. It got me thinking more critically and then researching.


The previous version is LINQ syntactic sugar on top of this new chained version to satisfy the linter. 🤷


Regarding time complexity, as I understood/understand it, you're right the .Where() would be O(N). The index .FirstOrDefault() should allow looping over the collection and stop on the first matching case, giving us O(1) [correct?]

The .Select() will return the matching Key which is what's needed to remove the item from the cache.

cc @msohailhussain

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jaeopt Thinking more on this: LINQ does have its own overhead.

Should I change this to a standard loop?

Copy link
Contributor

Choose a reason for hiding this comment

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

@mikechu-optimizely the current looping should be fixed since it'll slow down the replacement search significantly.
Wondering why OrderedSet solution was discarded?
If it can't be used, we can also consider keeping key in the ItemWrapper. See https://github.com/optimizely/swift-sdk/blob/86a328f942da7955873727247ccd675d6b246f80/Sources/ODP/LruCache.swift#L84

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jaeopt I've added the Key as you suggested to ItemWrapper. This allowed me to remove the loop/LINQ. 👍

I'm not unsure I see the OrderedSet. Do you mean these lines that I'm replacing?

 var leastRecentlyUsedItemKey = (from cacheItem in _cache
    where cacheItem.Value == leastRecentlyUsedItem.Value
    select cacheItem.Key).FirstOrDefault();

if so, this LINQ expression is not different from the method chaining version. Happy to discuss on the scheduled meeting I sent for later today so we can close up this PR.

I like the newest solution and hope it meets your standard. If you like the submission and Approve, we can cancel the meeting if you'd like.

Copy link
Contributor

Choose a reason for hiding this comment

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

@mikechu-optimizely I saw your origin LRUCache PR uses private readonly OrderedDictionary _orderedDictionary = new OrderedDictionary();. It looked like a good solution and wondering why it was discarded.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ohhh. I understand. Thank you for clarifying and digging into the history.

It's likely that I was following the Java implementation before it was updated to use LinkedHashMap.

OrderedDictionary is a better solution. I've updated.

Please review the latest commit.

if (!_odpConfig.HasSegments())
{
_logger.Log(LogLevel.DEBUG,
"No Segments are used in the project, Not Fetching segments. Returning empty list");
Copy link
Contributor

Choose a reason for hiding this comment

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

@mikechu-optimizely For any fetch error (odp not integrated, network errors, etc), we should return null. For empty segmentToCheck, we should return empty array.

OptimizelySDK/Odp/OdpSegmentManager.cs Outdated Show resolved Hide resolved
Copy link
Contributor

@jaeopt jaeopt left a comment

Choose a reason for hiding this comment

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

LGTM

@mikechu-optimizely mikechu-optimizely merged commit 3cd6745 into master Nov 23, 2022
@mikechu-optimizely mikechu-optimizely deleted the mike/odp-segment-manager branch November 23, 2022 18:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants