22
22
import java .util .List ;
23
23
import java .util .Map ;
24
24
import java .util .Map .Entry ;
25
+ import java .util .Objects ;
25
26
import java .util .Set ;
26
27
import java .util .concurrent .TimeUnit ;
27
28
import java .util .concurrent .atomic .AtomicReference ;
103
104
* @author Mark Paluch
104
105
* @author Andrey Muchnik
105
106
* @author John Blum
107
+ * @author Kim Sumin
106
108
* @since 1.7
107
109
*/
108
110
public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
@@ -126,6 +128,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
126
128
private EnableKeyspaceEvents enableKeyspaceEvents = EnableKeyspaceEvents .OFF ;
127
129
private @ Nullable String keyspaceNotificationsConfigParameter = null ;
128
130
private ShadowCopy shadowCopy = ShadowCopy .DEFAULT ;
131
+ private DeletionStrategy deletionStrategy = DeletionStrategy .DEL ;
129
132
130
133
/**
131
134
* Lifecycle state of this factory.
@@ -134,6 +137,43 @@ enum State {
134
137
CREATED , STARTING , STARTED , STOPPING , STOPPED , DESTROYED ;
135
138
}
136
139
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
+
137
177
/**
138
178
* Creates new {@link RedisKeyValueAdapter} with default {@link RedisMappingContext} and default
139
179
* {@link RedisCustomConversions}.
@@ -228,7 +268,9 @@ public Object put(Object id, Object item, String keyspace) {
228
268
byte [] key = toBytes (rdo .getId ());
229
269
byte [] objectKey = createKey (rdo .getKeyspace (), rdo .getId ());
230
270
231
- boolean isNew = connection .del (objectKey ) == 0 ;
271
+ // 제거
272
+ // boolean isNew = connection.del(objectKey) == 0;
273
+ boolean isNew = applyDeletionStrategy (connection , objectKey ) == 0 ;
232
274
233
275
connection .hMSet (objectKey , rdo .getBucket ().rawMap ());
234
276
@@ -245,11 +287,11 @@ public Object put(Object id, Object item, String keyspace) {
245
287
byte [] phantomKey = ByteUtils .concat (objectKey , BinaryKeyspaceIdentifier .PHANTOM_SUFFIX );
246
288
247
289
if (expires (rdo )) {
248
- connection . del ( phantomKey );
290
+ applyDeletionStrategy ( connection , phantomKey );
249
291
connection .hMSet (phantomKey , rdo .getBucket ().rawMap ());
250
292
connection .expire (phantomKey , rdo .getTimeToLive () + PHANTOM_KEY_TTL );
251
293
} else if (!isNew ) {
252
- connection . del ( phantomKey );
294
+ applyDeletionStrategy ( connection , phantomKey );
253
295
}
254
296
}
255
297
@@ -323,7 +365,7 @@ public <T> T delete(Object id, String keyspace, Class<T> type) {
323
365
324
366
redisOps .execute ((RedisCallback <Void >) connection -> {
325
367
326
- connection . del ( keyToDelete );
368
+ applyDeletionStrategy ( connection , keyToDelete );
327
369
connection .sRem (binKeyspace , binId );
328
370
new IndexWriter (connection , converter ).removeKeyFromIndexes (keyspace , binId );
329
371
@@ -335,7 +377,7 @@ public <T> T delete(Object id, String keyspace, Class<T> type) {
335
377
336
378
byte [] phantomKey = ByteUtils .concat (keyToDelete , BinaryKeyspaceIdentifier .PHANTOM_SUFFIX );
337
379
338
- connection . del ( phantomKey );
380
+ applyDeletionStrategy ( connection , phantomKey );
339
381
}
340
382
}
341
383
return null ;
@@ -485,7 +527,7 @@ public void update(PartialUpdate<?> update) {
485
527
connection .persist (redisKey );
486
528
487
529
if (keepShadowCopy ()) {
488
- connection . del ( ByteUtils .concat (redisKey , BinaryKeyspaceIdentifier .PHANTOM_SUFFIX ));
530
+ applyDeletionStrategy ( connection , ByteUtils .concat (redisKey , BinaryKeyspaceIdentifier .PHANTOM_SUFFIX ));
489
531
}
490
532
}
491
533
}
@@ -495,6 +537,18 @@ public void update(PartialUpdate<?> update) {
495
537
});
496
538
}
497
539
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
+
498
552
private RedisUpdateObject fetchDeletePathsFromHashAndUpdateIndex (RedisUpdateObject redisUpdateObject , String path ,
499
553
RedisConnection connection ) {
500
554
@@ -704,6 +758,30 @@ public boolean isRunning() {
704
758
return State .STARTED .equals (this .state .get ());
705
759
}
706
760
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
+
707
785
/**
708
786
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
709
787
* @since 1.8
@@ -792,7 +870,7 @@ private void initKeyExpirationListener(RedisMessageListenerContainer messageList
792
870
793
871
if (this .expirationListener .get () == null ) {
794
872
MappingExpirationListener listener = new MappingExpirationListener (messageListenerContainer , this .redisOps ,
795
- this .converter , this .shadowCopy );
873
+ this .converter , this .shadowCopy , this . deletionStrategy );
796
874
797
875
listener .setKeyspaceNotificationsConfigParameter (keyspaceNotificationsConfigParameter );
798
876
@@ -819,17 +897,19 @@ static class MappingExpirationListener extends KeyExpirationEventMessageListener
819
897
private final RedisOperations <?, ?> ops ;
820
898
private final RedisConverter converter ;
821
899
private final ShadowCopy shadowCopy ;
900
+ private final DeletionStrategy deletionStrategy ;
822
901
823
902
/**
824
903
* Creates new {@link MappingExpirationListener}.
825
904
*/
826
905
MappingExpirationListener (RedisMessageListenerContainer listenerContainer , RedisOperations <?, ?> ops ,
827
- RedisConverter converter , ShadowCopy shadowCopy ) {
906
+ RedisConverter converter , ShadowCopy shadowCopy , DeletionStrategy deletionStrategy ) {
828
907
829
908
super (listenerContainer );
830
909
this .ops = ops ;
831
910
this .converter = converter ;
832
911
this .shadowCopy = shadowCopy ;
912
+ this .deletionStrategy = deletionStrategy ;
833
913
}
834
914
835
915
@ Override
@@ -883,7 +963,11 @@ private Object readShadowCopy(byte[] key) {
883
963
Map <byte [], byte []> phantomValue = connection .hGetAll (phantomKey );
884
964
885
965
if (!CollectionUtils .isEmpty (phantomValue )) {
886
- connection .del (phantomKey );
966
+ if (deletionStrategy == DeletionStrategy .UNLINK ) {
967
+ connection .unlink (phantomKey );
968
+ } else {
969
+ connection .del (phantomKey );
970
+ }
887
971
}
888
972
889
973
return phantomValue ;
0 commit comments