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

[Feature Request] - Data Structure API #126

Open
rahul-malik opened this issue Oct 14, 2016 · 1 comment
Open

[Feature Request] - Data Structure API #126

rahul-malik opened this issue Oct 14, 2016 · 1 comment

Comments

@rahul-malik
Copy link
Collaborator

rahul-malik commented Oct 14, 2016

Motivation

There are times where you are storing specific data types (lists, dictionaries, etc.) as a value in PINCache. The current implementation allows for these types to be values in the cache but to perform any mutation an outside caller would have to read, mutate and re-cache the object which could lead to mutations of stale data (due to thread-safety). The goal of this proposal is to identify a way to add atomic mutation support to power common data-structure mutations within the cache itself.

Example: List append race condition

NSString *key;
NSArray *objectsToAppend = @[......];

// Fetch current list from the cache
NSArray *list = [cache objectForKey:key];

// Compute the new list value
NSArray *updatedList = [list arrayByAddingObjectsFromArray:objectsToAppend];

// Overwrite the cache value for `key` with the updated list.
[cache setObject:updatedList];

This code seems like it would be a reasonable workaround for this use-case but actually has it's own issues with regards to thread-safety and performing two operations on the cache for what should be an atomic operation.

Potential solution

Expanding the PINCache API for discrete mutation events

The append example would have been safe to perform if the mutation was performed when the lock was acquired during the initial fetch of key. It would be more straightforward if there were API additions for performing specific mutations within the cache itself.

For example the above example could have a specific methods for lists:

- (NSArray *)listForKey:(NSString *)key;
- (void)setList:(NSArray *)list forKey:(NSString *)key;
- (void)appendObjects:(NSArray *)objects forListWithKey:(NSString *)key;

An implementation of @selector(appendObjects:forListWithKey:) would look like this:

- (void)appendObjects:(NSArray *)objects forListWithKey:(NSString *)key
{
    [self lock];
        NSArray *list = _dictionary[key];
       BOOL isValidType = [list isKindOfClass:[NSArray class]];
       NSAssert(isValidType, @"Trying to append objects to non-list type");
      if (isValidType) {
        NSArray *updatedList = [list arrayByAddingObjectsFromArray:objects];
        _dictionary[key] = updatedList;
      }
    [self unlock];
}

The advantage in the above proposal is that the append operation is contained within the lock so it is going to be appending values to the current list rather than potentially stale data.

Add a general API method for performing an atomic mutation

If we had a method to mutate the object for key atomically, we would be able to build extensible APIs for various data structures on top of PINCache through Objective-C categories.

The mutation method:

// Ideally we'd be able to constrain this type to have the same input and return values.
typedef id (^PINCacheMutationBlock)(id); 
- (void)mutateObjectForKey:(nonnull NSString *)key withBlock:(PINCacheMutationBlock)block:

Then the above extensions for lists would be a category on PINCache:

@interface PINCache(ListAdditions)
- (NSArray *)listForKey:(NSString *)key;
- (void)setList:(NSArray *)list forKey:(NSString *)key;
- (void)appendObjects:(NSArray *)objects forListWithKey:(NSString *)key;
@end

Conclusion

I think this direction of functionality can add a lot of value to the overall uses of PINCache but also comes with the cost of complexity in the API. This kind of idea is what made Redis a popular caching system over Memcached and allowed it to satisfy many use-cases. The above example only goes through one potential API addition (list append) but I would like to get feedback on the proposal before discussing additional API methods.

@garrettmoon
Copy link
Collaborator

This is awesome @rahul-malik, thank you for putting it together!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants