Skip to content

Commit 82d1efc

Browse files
DATAREDIS-803 - Work around Redis parameter limitation.
Redis has a [limitation of 1024 * 1024 parameters](https://github.com/antirez/redis/blob/4.0.9/src/networking.c#L1200) for bulk operations. To insert more than 1024 * 1024 / 2 - 1 entries with putAll(), they need to be split up in multiple HMSET commands. To reveive more than 1024 * 1024 - 1 entries with entrySet(), we can directly use the HGETALL command instead of first fetching the keys with HKEYS and then fetching the values with HMGET.
1 parent e0b73c7 commit 82d1efc

File tree

4 files changed

+39
-21
lines changed

4 files changed

+39
-21
lines changed

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.Collection;
1919
import java.util.Collections;
20+
import java.util.Iterator;
2021
import java.util.LinkedHashMap;
2122
import java.util.List;
2223
import java.util.Map;
@@ -32,6 +33,7 @@
3233
* @author Costin Leau
3334
* @author Christoph Strobl
3435
* @author Ninad Divadkar
36+
* @author Christian Bühler
3537
*/
3638
class DefaultHashOperations<K, HK, HV> extends AbstractOperations<K, Object> implements HashOperations<K, HK, HV> {
3739

@@ -141,16 +143,22 @@ public void putAll(K key, Map<? extends HK, ? extends HV> m) {
141143

142144
byte[] rawKey = rawKey(key);
143145

144-
Map<byte[], byte[]> hashes = new LinkedHashMap<>(m.size());
146+
int size = Math.min(RedisTemplate.REDIS_MAX_ARGS / 2 - 1, m.size());
147+
Map<byte[], byte[]> hashes = new LinkedHashMap<>(size);
145148

146-
for (Map.Entry<? extends HK, ? extends HV> entry : m.entrySet()) {
149+
Iterator<? extends Entry<? extends HK, ? extends HV>> entries = m.entrySet().iterator();
150+
while (entries.hasNext()) {
151+
Entry<? extends HK, ? extends HV> entry = entries.next();
147152
hashes.put(rawHashKey(entry.getKey()), rawHashValue(entry.getValue()));
148-
}
149153

150-
execute(connection -> {
151-
connection.hMSet(rawKey, hashes);
152-
return null;
153-
}, true);
154+
if (!entries.hasNext() || hashes.size() == size) {
155+
execute(connection -> {
156+
connection.hMSet(rawKey, hashes);
157+
return null;
158+
}, true);
159+
hashes.clear();
160+
}
161+
}
154162
}
155163

156164
/*

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,15 @@
7979
* @author Ninad Divadkar
8080
* @author Anqing Shao
8181
* @author Mark Paluch
82+
* @author Christian Bühler
8283
* @param <K> the Redis key type against which the template works (usually a String)
8384
* @param <V> the Redis value type against which the template works
8485
* @see StringRedisTemplate
8586
*/
8687
public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
8788

89+
public static final int REDIS_MAX_ARGS = 1024 * 1024;
90+
8891
private boolean enableTransactionSupport = false;
8992
private boolean exposeConnection = false;
9093
private boolean initialized = false;

src/main/java/org/springframework/data/redis/support/collections/DefaultRedisMap.java

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
*
3939
* @author Costin Leau
4040
* @author Christoph Strobl
41+
* @author Christian Bühler
4142
*/
4243
public class DefaultRedisMap<K, V> implements RedisMap<K, V> {
4344

@@ -155,20 +156,7 @@ public boolean containsValue(Object value) {
155156
*/
156157
@Override
157158
public Set<java.util.Map.Entry<K, V>> entrySet() {
158-
159-
Set<K> keySet = keySet();
160-
checkResult(keySet);
161-
Collection<V> multiGet = hashOps.multiGet(keySet);
162-
163-
Iterator<K> keys = keySet.iterator();
164-
Iterator<V> values = multiGet.iterator();
165-
166-
Set<Map.Entry<K, V>> entries = new LinkedHashSet<>();
167-
while (keys.hasNext()) {
168-
entries.add(new DefaultRedisMapEntry(keys.next(), values.next()));
169-
}
170-
171-
return entries;
159+
return hashOps.entries().entrySet();
172160
}
173161

174162
/*

src/test/java/org/springframework/data/redis/support/collections/AbstractRedisMapTests.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.ArrayList;
2626
import java.util.Collection;
2727
import java.util.Collections;
28+
import java.util.HashMap;
2829
import java.util.LinkedHashMap;
2930
import java.util.LinkedHashSet;
3031
import java.util.Map;
@@ -62,6 +63,7 @@
6263
* @author Jennifer Hickey
6364
* @author Christoph Strobl
6465
* @author Thomas Darimont
66+
* @author Christian Bühler
6567
*/
6668
@RunWith(Parameterized.class)
6769
public abstract class AbstractRedisMapTests<K, V> {
@@ -396,6 +398,23 @@ public void testEntrySet() {
396398
assertThat(values, not(hasItem(v2)));
397399
}
398400

401+
@Test // DATAREDIS-803
402+
@IfProfileValue(name = "runLongTests", value = "true")
403+
public void testBigEntrySet() {
404+
Set<Entry<K, V>> entries = map.entrySet();
405+
assertTrue(entries.isEmpty());
406+
407+
Map<K, V> m = new HashMap<>();
408+
for (int i = 0; i < RedisTemplate.REDIS_MAX_ARGS - 1; i++) {
409+
m.put(getKey(), getValue());
410+
}
411+
map.putAll(m);
412+
413+
entries = map.entrySet();
414+
415+
assertEquals(RedisTemplate.REDIS_MAX_ARGS - 1, entries.size());
416+
}
417+
399418
@Test
400419
public void testPutIfAbsent() {
401420

0 commit comments

Comments
 (0)