Skip to content

Commit e462467

Browse files
muchnikchristophstrobl
authored andcommitted
Remove phantom copy of expiring data when source gets persisted.
This commit makes sure to clean up resources when a previously expiring entity is persisted by setting the time to live to zero or negative. In case a phantom copy exists for the changed entity, it is removed to free space on the server and prevent expiration events from being sent. Closes #1955 Original Pull Request: #1961
1 parent febdb82 commit e462467

File tree

2 files changed

+43
-1
lines changed

2 files changed

+43
-1
lines changed

src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
*
100100
* @author Christoph Strobl
101101
* @author Mark Paluch
102+
* @author Andrey Muchnik
102103
* @since 1.7
103104
*/
104105
public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
@@ -248,6 +249,11 @@ public Object put(Object id, Object item, String keyspace) {
248249
}
249250
}
250251

252+
boolean isNoExpire = rdo.getTimeToLive() == null || rdo.getTimeToLive() != null && rdo.getTimeToLive() < 0;
253+
if (isNoExpire && !isNew && keepShadowCopy()){
254+
connection.del(ByteUtils.concat(objectKey, BinaryKeyspaceIdentifier.PHANTOM_SUFFIX));
255+
}
256+
251257
connection.sAdd(toBytes(rdo.getKeyspace()), key);
252258

253259
IndexWriter indexWriter = new IndexWriter(connection, converter);
@@ -492,7 +498,7 @@ public void update(PartialUpdate<?> update) {
492498
} else {
493499

494500
connection.persist(redisKey);
495-
connection.persist(ByteUtils.concat(redisKey, BinaryKeyspaceIdentifier.PHANTOM_SUFFIX));
501+
connection.del(ByteUtils.concat(redisKey, BinaryKeyspaceIdentifier.PHANTOM_SUFFIX));
496502
}
497503
}
498504

src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterTests.java

+36
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
*
5555
* @author Christoph Strobl
5656
* @author Mark Paluch
57+
* @author Andrey Muchnik
5758
*/
5859
@ExtendWith(LettuceConnectionFactoryExtension.class)
5960
public class RedisKeyValueAdapterTests {
@@ -705,6 +706,41 @@ void phantomKeyInsertedOnPutWhenShadowCopyIsInDefaultAndKeyspaceNotificationEnab
705706
assertThat(template.hasKey("persons:1:phantom")).isTrue();
706707
}
707708

709+
@Test // DATAREDIS-1955
710+
void phantomKeyIsDeletedWhenPutWithNegativeTimeToLiveAndOldEntryTimeToLiveWasPositiveAndWhenShadowCopyIsTurnedOn() {
711+
ExpiringPerson rand = new ExpiringPerson();
712+
rand.id = "1";
713+
rand.ttl = 3000L;
714+
715+
adapter.put("1", rand, "persons");
716+
717+
assertThat(template.getExpire("persons:1:phantom")).isPositive();
718+
719+
rand.ttl = -1L;
720+
721+
adapter.put("1", rand, "persons");
722+
723+
assertThat(template.hasKey("persons:1:phantom")).isFalse();
724+
}
725+
726+
@Test // DATAREDIS-1955
727+
void updateWithRefreshTtlAndWithoutPositiveTtlShouldDeletePhantomKey() {
728+
ExpiringPerson person = new ExpiringPerson();
729+
person.id = "1";
730+
person.ttl = 100L;
731+
732+
adapter.put("1", person, "persons");
733+
734+
assertThat(template.getExpire("persons:1:phantom")).isPositive();
735+
736+
PartialUpdate<ExpiringPerson> update = new PartialUpdate<>("1", ExpiringPerson.class) //
737+
.refreshTtl(true);
738+
739+
adapter.update(update);
740+
741+
assertThat(template.hasKey("persons:1:phantom")).isFalse();
742+
}
743+
708744
/**
709745
* Wait up to 5 seconds until {@code key} is no longer available in Redis.
710746
*

0 commit comments

Comments
 (0)