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

Add support for GETEX in Spring Redis Cache #2643

Closed
wants to merge 3 commits into from

Conversation

bytestreme
Copy link
Contributor

@bytestreme bytestreme commented Jul 16, 2023

  • You have read the Spring Data contribution guidelines.
  • You use the code formatters provided here and have them applied to your changes. Don’t submit any formatting related changes.
  • You submit test cases (unit or integration tests) that back your changes.
  • You added yourself as author in the headers of the classes you touched. Amend the date range in the Apache license header if needed. For new types, add the license header (copy from another file and set the current year only).

@bytestreme bytestreme changed the title Gh 2351 #GH-2351 Jul 16, 2023
@bytestreme
Copy link
Contributor Author

resolves #2351

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jul 16, 2023
@bytestreme
Copy link
Contributor Author

@mp911de

@mp911de mp911de changed the title #GH-2351 Adding support to GETEX in Spring Redis Cache Jul 17, 2023
@mp911de mp911de self-assigned this Jul 17, 2023
@@ -133,7 +144,7 @@ public byte[] get(String name, byte[] key) {
}

@Override
public byte[] putIfAbsent(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
public byte[] putIfAbsent(String name, byte[] key, byte[] value, @Nullable Duration ttl, @Nullable Duration maxIdle) {
Copy link
Member

Choose a reason for hiding this comment

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

This change doesn't make sense because there can be only one TTL and instead of changing public API, we rather should apply maxIdle defaulting on the calling side.

Copy link
Contributor Author

@bytestreme bytestreme Jul 17, 2023

Choose a reason for hiding this comment

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

How about default method in RedisCacheWriter?

Copy link
Contributor

@jxblum jxblum Jul 19, 2023

Choose a reason for hiding this comment

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

I don't think this signature change makes any sense in any of the changed RedisCacheWriter methods: get(..), put(..) or putIfAbsent(..); see below.

I'd prefer, if we decide to add any option to configure TTI (overriding TTL when TTI < TTL on gets) per cache entry, by key, at all, then the configuration would be included in the RedisCacheConfiguration per cache (not unlike TTL) and the RedisCacheConfiguration object would be passed to the components of the cache implementation to alter the caching behavior as needed. Otherwise, each new option will only increase the signature footprint, which is not desirable.

@@ -93,7 +94,7 @@ class DefaultRedisCacheWriter implements RedisCacheWriter {
}

@Override
public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl, @Nullable Duration maxIdle) {
Copy link
Member

Choose a reason for hiding this comment

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

This change doesn't make sense because there can be only one TTL and instead of changing public API, we rather should apply maxIdle defaulting on the calling side.

Copy link
Contributor

@jxblum jxblum Jul 19, 2023

Choose a reason for hiding this comment

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

Additionally, "maxIdle" is a misnomer in this case and does not accurately reflect the behavior of "idle" data access in general and idle-timeout expiration (TTL) policies in particular. See my comment in Issue #2351.

@mp911de mp911de added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Jul 18, 2023
@mp911de
Copy link
Member

mp911de commented Jul 18, 2023

@jxblum can you have a look as well? Introducing an expire after idle (Setting the TTL on GET) makes sense as we have already support for GETEX. I just wonder about the configuration side, whether we want to enable our TTL function for idle or whether we want to introduce another function to determine the Idle time for a key when getting the value. WDYT?

Comment on lines 105 to +112
if (shouldExpireWithin(ttl)) {
connection.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), SetOption.upsert());
} else {
connection.set(key, value);
if (shouldExpireMaxIdleWithin(maxIdle)) {
connection.set(key, value, Expiration.from(maxIdle), SetOption.upsert());
} else {
connection.set(key, value);
}
Copy link
Contributor

@jxblum jxblum Jul 19, 2023

Choose a reason for hiding this comment

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

As @mp911de alluded to above, there is technically only 1 expiration policy in Redis for a given key: TTL.

Expiration (and specifically, TTL) is something that is already handled in Spring Data Redis as of 2.7.x (and beyond: 3.0.x, 3.1.x, and currently, 3.2.x), which is apparent in lines 105-107 shown above.

Plus, what happens when TTL is less than TTI? The lesser of TTL and TTI should take precedence. (???)

Also note, as of Spring Data Redis 3.2.x (currently in development; latest version is 3.2.0-M1) TTL expiration can now be computed dynamically at runtime, per cache entry by key and value using the new TtlFunction interface.

Copy link
Contributor

@jxblum jxblum Jul 20, 2023

Choose a reason for hiding this comment

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

Technically, TTI expiration policies (e.g. timeouts) have no bearing on cache entry creates or updates (upserts). TTL expiration is only updated when another create or update occurs after the initial create. Read access (e.g. get(key)) does not change TTL.

This would apply to RedisCacheWriter.putIfAbsent(..) as well.

byte[] result;

if (shouldExpireMaxIdleWithin(maxIdle)) {
result = execute(name, connection -> connection.getEx(key, Expiration.from(maxIdle)));
Copy link
Contributor

@jxblum jxblum Jul 19, 2023

Choose a reason for hiding this comment

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

The lesser of TTL and TTI should take precedence.

Comment on lines 163 to +167
if (shouldExpireWithin(ttl)) {
put = connection.set(key, value, Expiration.from(ttl), SetOption.ifAbsent());
} else {
put = connection.setNX(key, value);
if (shouldExpireMaxIdleWithin(maxIdle)) {
put = connection.set(key, value, Expiration.from(maxIdle), SetOption.ifAbsent());
Copy link
Contributor

Choose a reason for hiding this comment

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

The lesser of TTL and TTI should take precedence.

@jxblum
Copy link
Contributor

jxblum commented Jul 20, 2023

@bytestreme & @mp911de -

First, please see my comments in Issue #2351, here, specifically here, and in conclusion, here.

@jxblum
Copy link
Contributor

jxblum commented Jul 20, 2023

@mp911de - I agree. Adding an expiration on Cache.get(key) in the context of (Spring Data) Redis's cache implementation makes sense.

However, I would caution that Redis really does not technically have a proper notion of cache entry, idle-timeout expiration (TTI). It is essentially only TTL, or effectively TTI when GETEX along with SET including expiration options are used "consistently" across the application system architecture, and specifically, when caching.

It seems to me, frameworks like Redisson offer TTI as an additional feature implemented and supported by the framework itself outside of Redis. Without the use of OBJECT IDLETIME and an external expiration monitor, there is really no TTI, and juggling TTL with TTI could get messy and confusing for users.

As such, I think we should keep with our current TtlFunction interface contract, rather than adding an additional setting for TTI, and modify RedisCache.lookup(key) along with (Default)RedisCacheWriter.get(cacheName, key) to be, something like:

class RedisCache {

    protected Object lookup(Object key) {

        byte[] value = getCacheWriter().get(getName(), createAndConvertCacheKey(key), 
            getTtlFunction().getTimeToLive(key, null)); 

         // return deserialized, non-null value or null
    }
}

And:

class DefaultRedisCacheWriter {

    public byte[] get(String name, byte[] key, Duration ttl) {

        // ...

        byte[] result = ttl != null 
            ? execute(name, connection -> connection.getEx(key, toExpiration(ttl)))
            : execute(name, connection -> connection.get(key));
    }

jxblum added a commit to jxblum/spring-data-redis that referenced this pull request Jul 20, 2023
We now support time-to-idle (TTI) expiration policies in Spring Data Redis's Cache implementation for cache reads.

This TTI behavior is achieved with the use of the Redis GETEX command an consistently using the same TTL configuration for all cache operations when TTI is enabled.

Closes spring-projects#2351
Original pull request: spring-projects#2643
jxblum added a commit to jxblum/spring-data-redis that referenced this pull request Jul 20, 2023
We now support time-to-idle (TTI) expiration policies for cache reads.

The TTI implementation is achieved with the use of the Redis GETEX command on Cache.get(key) operations as well as consistently using the same TTL configuration for all cache operations when TTI is enabled and TTL expiration has been configured,
with the use of a TtlFunction or fixed Duration.

Closes spring-projects#2351
Original pull request: spring-projects#2643
jxblum added a commit to jxblum/spring-data-redis that referenced this pull request Jul 21, 2023
We now support time-to-idle (TTI) expiration policies for cache reads.

The TTI implementation is achieved with the use of the Redis GETEX command on Cache.get(key) operations as well as consistently using the same TTL configuration for all cache operations when TTI is enabled and TTL expiration has been configured,
with the use of a TtlFunction or fixed Duration.

Closes spring-projects#2351
Original pull request: spring-projects#2643
@jxblum
Copy link
Contributor

jxblum commented Jul 21, 2023

See PR #2649.

@jxblum jxblum changed the title Adding support to GETEX in Spring Redis Cache Add support for GETEX in Spring Redis Cache Jul 21, 2023
@mp911de mp911de added status: superseded An issue that has been superseded by another and removed type: enhancement A general enhancement labels Jul 21, 2023
@mp911de mp911de closed this Jul 21, 2023
christophstrobl pushed a commit that referenced this pull request Aug 1, 2023
We now support time-to-idle (TTI) expiration policies for cache reads.

The TTI implementation is achieved with the use of the Redis GETEX command on Cache.get(key) operations as well as consistently using the same TTL configuration for all cache operations when TTI is enabled and TTL expiration has been configured,
with the use of a TtlFunction or fixed Duration.

Closes #2351
Original pull request: #2643
christophstrobl added a commit that referenced this pull request Aug 1, 2023
Order imports to match code style. Use one line per sentence in documentation.

Original pull request: #2643
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: superseded An issue that has been superseded by another
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants