2222import java .util .List ;
2323import java .util .Map ;
2424import java .util .Map .Entry ;
25+ import java .util .Objects ;
2526import java .util .Set ;
2627import java .util .concurrent .TimeUnit ;
2728import java .util .concurrent .atomic .AtomicReference ;
103104 * @author Mark Paluch
104105 * @author Andrey Muchnik
105106 * @author John Blum
107+ * @author Kim Sumin
106108 * @since 1.7
107109 */
108110public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
@@ -126,6 +128,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
126128 private EnableKeyspaceEvents enableKeyspaceEvents = EnableKeyspaceEvents .OFF ;
127129 private @ Nullable String keyspaceNotificationsConfigParameter = null ;
128130 private ShadowCopy shadowCopy = ShadowCopy .DEFAULT ;
131+ private DeletionStrategy deletionStrategy = DeletionStrategy .DEL ;
129132
130133 /**
131134 * Lifecycle state of this factory.
@@ -134,6 +137,43 @@ enum State {
134137 CREATED , STARTING , STARTED , STOPPING , STOPPED , DESTROYED ;
135138 }
136139
140+ /**
141+ * Strategy for deleting Redis keys in Repository operations.
142+ * <p>
143+ * Allows configuration of whether to use synchronous {@literal DEL} or asynchronous {@literal UNLINK} commands for
144+ * key deletion operations.
145+ *
146+ * @author [Your Name]
147+ * @since 3.6
148+ * @see <a href="https://redis.io/commands/del">Redis DEL</a>
149+ * @see <a href="https://redis.io/commands/unlink">Redis UNLINK</a>
150+ */
151+ public enum DeletionStrategy {
152+
153+ /**
154+ * Use Redis {@literal DEL} command for key deletion.
155+ * <p>
156+ * 기key from memory. The command blocks until the key is completely removed, which can cause performance issues when
157+ * deleting large data structures under high load.
158+ * <p>
159+ * This is the default strategy for backward compatibility.
160+ */
161+ DEL ,
162+
163+ /**
164+ * Use Redis {@literal UNLINK} command for key deletion.
165+ * <p>
166+ * This is a non-blocking operation that asynchronously removes the key. The key is immediately removed from the
167+ * keyspace, but the actual memory reclamation happens in the background, providing better performance for
168+ * applications with frequent updates on existing keys.
169+ * <p>
170+ * Requires Redis 4.0 or later.
171+ *
172+ * @since Redis 4.0
173+ */
174+ UNLINK
175+ }
176+
137177 /**
138178 * Creates new {@link RedisKeyValueAdapter} with default {@link RedisMappingContext} and default
139179 * {@link RedisCustomConversions}.
@@ -228,7 +268,9 @@ public Object put(Object id, Object item, String keyspace) {
228268 byte [] key = toBytes (rdo .getId ());
229269 byte [] objectKey = createKey (rdo .getKeyspace (), rdo .getId ());
230270
231- boolean isNew = connection .del (objectKey ) == 0 ;
271+ // 제거
272+ // boolean isNew = connection.del(objectKey) == 0;
273+ boolean isNew = applyDeletionStrategy (connection , objectKey ) == 0 ;
232274
233275 connection .hMSet (objectKey , rdo .getBucket ().rawMap ());
234276
@@ -245,11 +287,11 @@ public Object put(Object id, Object item, String keyspace) {
245287 byte [] phantomKey = ByteUtils .concat (objectKey , BinaryKeyspaceIdentifier .PHANTOM_SUFFIX );
246288
247289 if (expires (rdo )) {
248- connection . del ( phantomKey );
290+ applyDeletionStrategy ( connection , phantomKey );
249291 connection .hMSet (phantomKey , rdo .getBucket ().rawMap ());
250292 connection .expire (phantomKey , rdo .getTimeToLive () + PHANTOM_KEY_TTL );
251293 } else if (!isNew ) {
252- connection . del ( phantomKey );
294+ applyDeletionStrategy ( connection , phantomKey );
253295 }
254296 }
255297
@@ -323,7 +365,7 @@ public <T> T delete(Object id, String keyspace, Class<T> type) {
323365
324366 redisOps .execute ((RedisCallback <Void >) connection -> {
325367
326- connection . del ( keyToDelete );
368+ applyDeletionStrategy ( connection , keyToDelete );
327369 connection .sRem (binKeyspace , binId );
328370 new IndexWriter (connection , converter ).removeKeyFromIndexes (keyspace , binId );
329371
@@ -335,7 +377,7 @@ public <T> T delete(Object id, String keyspace, Class<T> type) {
335377
336378 byte [] phantomKey = ByteUtils .concat (keyToDelete , BinaryKeyspaceIdentifier .PHANTOM_SUFFIX );
337379
338- connection . del ( phantomKey );
380+ applyDeletionStrategy ( connection , phantomKey );
339381 }
340382 }
341383 return null ;
@@ -485,7 +527,7 @@ public void update(PartialUpdate<?> update) {
485527 connection .persist (redisKey );
486528
487529 if (keepShadowCopy ()) {
488- connection . del ( ByteUtils .concat (redisKey , BinaryKeyspaceIdentifier .PHANTOM_SUFFIX ));
530+ applyDeletionStrategy ( connection , ByteUtils .concat (redisKey , BinaryKeyspaceIdentifier .PHANTOM_SUFFIX ));
489531 }
490532 }
491533 }
@@ -495,6 +537,18 @@ public void update(PartialUpdate<?> update) {
495537 });
496538 }
497539
540+ /**
541+ * Apply the configured deletion strategy to delete the given key.
542+ *
543+ * @param connection the Redis connection
544+ * @param key the key to delete
545+ * @return the number of keys that were removed
546+ */
547+ private Long applyDeletionStrategy (RedisConnection connection , byte [] key ) {
548+ return Objects
549+ .requireNonNull (deletionStrategy == DeletionStrategy .UNLINK ? connection .unlink (key ) : connection .del (key ));
550+ }
551+
498552 private RedisUpdateObject fetchDeletePathsFromHashAndUpdateIndex (RedisUpdateObject redisUpdateObject , String path ,
499553 RedisConnection connection ) {
500554
@@ -704,6 +758,30 @@ public boolean isRunning() {
704758 return State .STARTED .equals (this .state .get ());
705759 }
706760
761+ /**
762+ * Configure the deletion strategy for Redis keys.
763+ * <p>
764+ * {@link DeletionStrategy#DEL DEL} performs synchronous key deletion, while {@link DeletionStrategy#UNLINK UNLINK}
765+ * performs asynchronous deletion which can improve performance under high load scenarios.
766+ *
767+ * @param deletionStrategy the strategy to use for key deletion operations
768+ * @since 3.6
769+ */
770+ public void setDeletionStrategy (DeletionStrategy deletionStrategy ) {
771+ Assert .notNull (deletionStrategy , "DeletionStrategy must not be null" );
772+ this .deletionStrategy = deletionStrategy ;
773+ }
774+
775+ /**
776+ * Get the current deletion strategy.
777+ *
778+ * @return the current deletion strategy
779+ * @since 3.6
780+ */
781+ public DeletionStrategy getDeletionStrategy () {
782+ return this .deletionStrategy ;
783+ }
784+
707785 /**
708786 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
709787 * @since 1.8
@@ -792,7 +870,7 @@ private void initKeyExpirationListener(RedisMessageListenerContainer messageList
792870
793871 if (this .expirationListener .get () == null ) {
794872 MappingExpirationListener listener = new MappingExpirationListener (messageListenerContainer , this .redisOps ,
795- this .converter , this .shadowCopy );
873+ this .converter , this .shadowCopy , this . deletionStrategy );
796874
797875 listener .setKeyspaceNotificationsConfigParameter (keyspaceNotificationsConfigParameter );
798876
@@ -819,17 +897,19 @@ static class MappingExpirationListener extends KeyExpirationEventMessageListener
819897 private final RedisOperations <?, ?> ops ;
820898 private final RedisConverter converter ;
821899 private final ShadowCopy shadowCopy ;
900+ private final DeletionStrategy deletionStrategy ;
822901
823902 /**
824903 * Creates new {@link MappingExpirationListener}.
825904 */
826905 MappingExpirationListener (RedisMessageListenerContainer listenerContainer , RedisOperations <?, ?> ops ,
827- RedisConverter converter , ShadowCopy shadowCopy ) {
906+ RedisConverter converter , ShadowCopy shadowCopy , DeletionStrategy deletionStrategy ) {
828907
829908 super (listenerContainer );
830909 this .ops = ops ;
831910 this .converter = converter ;
832911 this .shadowCopy = shadowCopy ;
912+ this .deletionStrategy = deletionStrategy ;
833913 }
834914
835915 @ Override
@@ -883,7 +963,11 @@ private Object readShadowCopy(byte[] key) {
883963 Map <byte [], byte []> phantomValue = connection .hGetAll (phantomKey );
884964
885965 if (!CollectionUtils .isEmpty (phantomValue )) {
886- connection .del (phantomKey );
966+ if (deletionStrategy == DeletionStrategy .UNLINK ) {
967+ connection .unlink (phantomKey );
968+ } else {
969+ connection .del (phantomKey );
970+ }
887971 }
888972
889973 return phantomValue ;
0 commit comments