Skip to content

feat: Added a generic LRUCache interface and a default implementation #482

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

Merged
merged 7 commits into from
Aug 5, 2022

Conversation

zashraf1985
Copy link
Contributor

Summary

Added an interface for a generic LRUCache and a default Implementation.

Test plan

  • All existing tests pass.
  • Added unit tests for the default cache implementation.

Issues

OASIS-8385

Verified

This commit was signed with the committer’s verified signature.
GromNaN Jérôme Tamarelle
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 suggestions and clarifications.

*/
package com.optimizely.ab.internal;

public interface LRUCache<T> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public interface LRUCache<T> {
public interface Cache<T> {

This interface can be for generic caches, not only for LRUCache. Not sure about the best name to avoid conflicts., but "LRU" should be dropped. I think this is for internal use (not for public custom segments cache). If this interface is for public, I have a few other suggestions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changing it to Cache for now as per your suggestion. We can discuss this further in subsequent PRs when things become more clear.

public void setMaxSize(Integer size) {
Integer sizeToSet = size;
if (size < 0 ) {
sizeToSet = DEFAULT_MAX_SIZE;
Copy link
Contributor

Choose a reason for hiding this comment

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

either way is fine, but let's use "sizeToSet = 0" for all SDKs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! Defaulting to zero now when max size is negative

Comment on lines 47 to 54
if (linkedList.size() > sizeToSet) {
// If cache is bigger than the new maxSize, remove additional items from the end.
int itemsToTrim = linkedList.size() - sizeToSet;
for (int i = 0; i < itemsToTrim; i++) {
ItemWrapper item = linkedList.removeLast();
hashMap.remove(item.key);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need this support for dynamic cache size changes? We can allow them cache size setting only when app starts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed it to only allow when maxSize passed in is less than the current size of the cache.


if (hashMap.containsKey(key)) {
ItemWrapper oldItem = hashMap.get(key);
linkedList.remove(oldItem);
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't this linear search? It may be a factor in large cache.

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 is a good catch. Because i was passing the item itself and it uses a doubly linked list, i thought the item should have references to its neighbors and should be able to cleanly remove itself in O(1) but it looks like the actual node with references is managed internally and remove actually searched for the item linearly. I then went ahead and redid the whole implementation using LunkedHashMap

}

@Test
public void saveAndLookupMultipleItems() {
Copy link
Contributor

Choose a reason for hiding this comment

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

This test looks good. It will be good to have the same ordering switch tests for save ops as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

return;
}

if (linkedList.size() == maxSize) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (linkedList.size() == maxSize) {
if (linkedList.size() >= maxSize) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed the whole implementation. It does not apply anymore.

}

@Test
public void whenMaxSizeIsReducedInBetween() {
Copy link
Contributor

Choose a reason for hiding this comment

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

We can remove this test

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Modified the test to check the changed implementation

}

@Test
public void saveAndPeekItems() {
Copy link
Contributor

Choose a reason for hiding this comment

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

A test for reset()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

@zashraf1985 zashraf1985 removed their assignment Aug 5, 2022
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.

The new solution with LinkedHashMap looks great!
I suggest to remove support for dynamic cache config changes (setMaxsize/setTimeout) for simplicity and also consistency with other SDKs. Other than that, all changes look good.

Comment on lines 25 to 26
void setMaxSize(Integer size);
void setTimeout(Long timeoutSeconds);
Copy link
Contributor

Choose a reason for hiding this comment

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

We can set these in constructor. Can we remove them for consistency with other SDKs unless we have a strong case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! I also kept a no argument constructor too that will use default values.

Comment on lines 75 to 77
synchronized (lock) {
linkedHashMap.put(key, new ItemWrapper(value));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Wow, a single-line solution :)

}
return null;
}
}

public T peek(String key) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It's surprising that they do not have this method in LinkedHashMap. It should be ok without this since it's mostly for testing/debugging.

@zashraf1985 zashraf1985 removed their assignment Aug 5, 2022
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

@zashraf1985 zashraf1985 merged commit fb06ca7 into master Aug 5, 2022
@zashraf1985 zashraf1985 deleted the zeeshan/ats-lrucache branch August 5, 2022 19:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants