Redis provides support for pipelining, which involves sending multiple commands to the server without waiting for the replies and then reading the replies in a single step. Pipelining can improve performance when you need to send several commands in a row, such as adding many elements to the same List.
Spring Data Redis provides several RedisTemplate methods for running commands in a pipeline.
If you do not care about the results of the pipelined operations, you can use the standard execute method with pipeline set to true:
// Write several values in a single pipeline round-trip, discarding results
redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
connection.openPipeline();
for (int i = 0; i < batchSize; i++) {
connection.stringCommands().set(("key:" + i).getBytes(), ("value:" + i).getBytes());
}
// Results are automatically discarded when the callback returns
return null;
}
}, true /* exposeConnection */, true /* pipeline */);The executePipelined methods run the provided RedisCallback or SessionCallback in a pipeline and return the deserialized results as a List<Object>, as shown in the following example:
// Pop a batch of items from a Redis list in a single pipeline round-trip
List<Object> results = stringRedisTemplate.executePipelined(
new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisConnection stringRedisConn = new DefaultStringRedisConnection(connection);
for (int i = 0; i < batchSize; i++) {
stringRedisConn.rPop("mylist");
}
return null; // (1)
}
}
);-
The return value of
doInRedismust always benull. Spring Data Redis discards it and uses the pipeline results instead.
The results List contains all the popped items in the order the commands were sent.
RedisTemplate uses its value, hash key, and hash value serializers to deserialize all results before returning, so the items in the preceding example are returned as String values.
There are additional executePipelined overloads that let you pass a custom serializer for pipelined results.
The same approach works with mixed command types. Each entry in the returned List corresponds to one command in the order it was issued:
List<Object> results = stringRedisTemplate.executePipelined(
new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisConnection conn = new DefaultStringRedisConnection(connection);
conn.set("key1", "value1"); // index 0: Boolean
conn.rPush("mylist", "item"); // index 1: Long (new list length)
conn.get("key1"); // index 2: String
conn.lRange("mylist", 0, -1); // index 3: List<String>
return null;
}
}
);
// results.get(0) → true (SET succeeded)
// results.get(1) → 1L (list length after RPUSH)
// results.get(2) → "value1"
// results.get(3) → ["item"]|
Note
|
Read commands issued inside executePipelined are queued and sent to Redis together with all write commands.
Their results are not available inside the callback — they only appear in the returned List after the pipeline is flushed.
This means you cannot use the result of a read command to conditionally issue further commands within the same callback.
Use a Lua script or a transaction (MULTI/EXEC) if you need conditional reads and writes in a single round-trip.
|
|
Tip
|
The Lettuce driver supports fine-grained flush control that allows to either flush commands as they appear, buffer or send them at connection close. LettuceConnectionFactory factory = // ...
factory.setPipeliningFlushPolicy(PipeliningFlushPolicy.buffered(3)); (1)
|
|
Note
|
Pipelining is limited to Redis Standalone.
Redis Cluster is currently only supported through the Lettuce driver except for the following commands when using cross-slot keys: rename, renameNX, sort, bLPop, bRPop, rPopLPush, bRPopLPush, info, sMove, sInter, sInterStore, sUnion, sUnionStore, sDiff, sDiffStore.
Same-slot keys are fully supported.
|