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

SimpleKeyGenerator does not handle Collections the same way it handles Arrays #23464

Closed
rlubbat opened this issue Aug 14, 2019 · 12 comments
Closed
Labels
status: invalid An issue that we don't feel is valid

Comments

@rlubbat
Copy link

rlubbat commented Aug 14, 2019

Affects: spring-context 5.1.6.RELEASE


Issue:

Using the @Cacheable annotation on a method with a single argument that is a Collection or Map while using a RedisCache often fails to serialize the argument as the cache key when no keyGenerator is specified (defaults to SimpleKeyGenerator).

Example:

Using a RedisCache with configuration defaults except for using JSON serialization for values:
RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));

@Cacheable
public ResultObject findSomething(final List<Something> somethings) {
    // do the expensive operation with the somethings list to return a ResultObject
    return doExpensiveOperation(somethings);
}

This results in a serialization error generating a cache key because there is no conversion service to convert List<Something> to String.

The reason List<Something> is being used to convert to String is because SimpleKeyGenerator has a condition when the params array is length 1. If the single param in the array (in our example, the List<Something>) is not null and is not an array, then SimpleKeyGenerator returns that single param to use as the key. If there is more than 1 item in the params array, then a SimpleKey is returned to use as the key.

If SimpleKeyGenerator were changed to handle Collection and Map as well as array in the case where the params array is length 1, then the example method would work fine since the key would be a SimpleKey instead of the actual List<Something>.

I propose changing the SimpleKeyGenerator.generateKey(Object... params) method to this:

	public static Object generateKey(Object... params) {
            if (params.length == 0) {
                return SimpleKey.EMPTY;
            }
            if (params.length == 1) {
                final Object param = params[0];
                if (param != null && !param.getClass().isArray()
                        // this is the part that differs from SimpleKeyGenerator
                        && !Collection.class.isAssignableFrom(param.getClass())
                        && !Map.class.isAssignableFrom(param.getClass())) {
                    return param;
                }
            }
            return new SimpleKey(params);
        }
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Aug 14, 2019
@snicoll snicoll changed the title org.springframework.cache.interceptor.SimpleKeyGenerator fails on single Collection or Map argument methods SimpleKeyGenerator does not handle Collection or Map argument methods Aug 15, 2019
@snicoll snicoll added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Aug 15, 2019
@snicoll snicoll added this to the 5.2 RC2 milestone Aug 15, 2019
@snicoll snicoll self-assigned this Aug 15, 2019
@snicoll
Copy link
Member

snicoll commented Aug 15, 2019

@rlubbat seems sensible, thanks for reporting it.

@mentallurg
Copy link

@snicoll, @rlubbat: Why do you want to have it in SimpleKeyGenerator? It was implemented to cover the basic simple cases. For other cases other generators should be used (and implemented, if not yet exist). This is a wrong approach to extend simple implementation with new cases.

@snicoll: If decide to include it into Spring, please do it at least in a separate class. Don't flood simple implementation with new complex cases. Try to keep Spring code clean.

@snicoll
Copy link
Member

snicoll commented Aug 29, 2019

@rlubbat I overlooked something. We can apply this to the Map use case as both the key and the value is part of the key. Using only the key of the single entry of the map to identify the cache entry sounds like a bad idea to me.

@mentallurg thanks for sharing your opinion but the only reason why I considered it is for consistency. If we handle a single entry array, not handing a single collection is inconsistent.

@snicoll snicoll changed the title SimpleKeyGenerator does not handle Collection or Map argument methods SimpleKeyGenerator does not handle Collections the same way it handles Arrays Aug 29, 2019
@snicoll
Copy link
Member

snicoll commented Aug 29, 2019

I read the original description backwards so please ignore my comment on Map. The special check on array was added as part of #16130 and I am wondering if it would be worth to add it for Collection as well.

@rlubbat the fact that Redis can't serialize the key if it's not a SimpleKey is not something that should be fixed here. Either your configuration is wrong or there is an issue in the Redis implementation.

@jhoeller what are your thoughts to extend that check to Collection ?

@snicoll snicoll added the status: waiting-for-feedback We need additional information before we can continue label Aug 29, 2019
@rlubbat
Copy link
Author

rlubbat commented Aug 29, 2019

This isn't about Redis specifically, it was only my example case. If you refer to the reported issue, I noted that the Redis cache configuration in my example was using the defaults. I was not doing any special configuration for key serialization so I don't think it is a problem with my configuration. If it is a problem with the Redis implementation, I can't seem to find where in the Spring code the problem would be.

This is more about #16130, as you mentioned. It is natural to add Collection for the same reason array was added. I could see leaving Map out of it, but I think the same rationale for array and Collection could also apply to Map.

Regarding the question of simplicity, I don't think extending the rationale for array to Collection makes this class any less simple. In fact, it makes it so that developers don't have to do any more complex work like creating a new class and configuring its use on each method that has a Collection as its sole argument. Spring is supposed to help developers by reducing the plumbing code and this request is right in line with that ideal.

Thank you both for looking into the issue.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Aug 29, 2019
@snicoll
Copy link
Member

snicoll commented Aug 30, 2019

This isn't about Redis specifically, it was only my example case.

I don't really understand how you claim this can't be related to Redis considering your original description states:

This results in a serialization error generating a cache key because there is no conversion service to convert List to String.

I'd like us to take a step back now. I've created a sample project which is my understanding of your setup. With that setup and both redis or simple cache, things are working as expected.

Even though I think using a List<Something> as the key of an entity using the default key generation is an odd setup, there shouldn't be any problem with it and I haven't been able to find one so far.

I don't understand why the ConversionService would try to translate List<Something> to String either. Perhaps there is something in your configuration that you're not showing? If you want us to investigate this any further, please submit a PR to my sample project that showcases the problem you are experiencing. You can also provide your own github repo if that's any easier. Thanks!

@snicoll snicoll added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Aug 30, 2019
@rlubbat
Copy link
Author

rlubbat commented Aug 30, 2019

Hi @snicoll. You're right, it is related to the conversion service. I've been on vacation this week and let slip the details from my mind :-) I will take a look at the sample project you created and see if I can get it to fail the way I'm experiencing the issue. I get back in a couple of days, so please don't interpret my lack of action as a lack of interest. Thanks!

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Aug 30, 2019
@snicoll snicoll added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Aug 31, 2019
@rlubbat
Copy link
Author

rlubbat commented Sep 3, 2019 via email

@rlubbat
Copy link
Author

rlubbat commented Sep 3, 2019 via email

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Sep 3, 2019
@rlubbat
Copy link
Author

rlubbat commented Sep 3, 2019 via email

@snicoll
Copy link
Member

snicoll commented Sep 3, 2019

Thanks for following up. This is, as I suspected, a Redis-specific problem. Redis has an optimization for SimpleKey that ensures that such instance can be serialized to a String. If you try to use an argument that isn't a SimpleKey, a proper converter should be in place to get a String out of it.

IMO, your setup is a little brittle. If you want to use Something as a component of the cache, I think it's important to configure how Something contributes to the identity of the entry. Regardless, I think the situation in Redis can be improved. If you want to pursue this, please open an issue against the Spring Data Redis issue tracker.

@snicoll snicoll closed this as completed Sep 3, 2019
@snicoll snicoll added status: invalid An issue that we don't feel is valid and removed status: feedback-provided Feedback has been provided type: enhancement A general enhancement labels Sep 3, 2019
@snicoll snicoll removed this from the 5.2 RC2 milestone Sep 3, 2019
@snicoll snicoll removed their assignment Sep 3, 2019
@christophstrobl
Copy link
Member

Created DATAREDIS-1032 to see what we can do about this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: invalid An issue that we don't feel is valid
Projects
None yet
Development

No branches or pull requests

5 participants