You can use the Redis pipelining mechanism to perform batch operations or improve command execution performance. Pipelining allows you to simultaneously send multiple commands to the server, reducing network latency and improving performance. Tair (Redis OSS-compatible) supports the pipelining technique of open source Redis.
Overview of pipelining
Typically, the ping-pong mode is used in the communication between clients and Redis servers. In this mode, the client does not issue a command until the client receives a response to the last command from the server.
Additionally, Redis provides the pipelining mode in which the client batch issues commands without waiting for responses. After the client receives the responses, it matches them to the commands in order and returns the results to the frontend.
The following figure shows how the ping-pong mode and the pipelining mode work.
Pipelining improves the system efficiency and performance by reducing the network latency associated with round trip time (RTT) and the number of read()
and write()
system calls.
Pipelining is useful in scenarios where multiple commands need to be quickly submitted to the server and the responses are not required immediately. As such, pipelining can be used as a batch processing tool to optimize the system performance.
In pipelining mode, pipelines exclusively use the connection between the client and the server, and non-pipeline operations cannot be performed until the pipelines are closed. To perform other operations at the same time, you can establish a dedicated connection for pipelines to separate them from other operations.
For more information, see Redis pipelining.
Precautions
Pipelining does not ensure atomicity.
In pipelining mode, the client batch issues commands, whereas the server resolves individual commands and runs them in order. As a result, the server may also run commands from other clients. To ensure atomicity, you can use transaction scripts or Lua scripts.
Pipelining does not support rollback in the case of errors.
If commands to be run are dependent on each other, do not use pipelining.
NoteSpecific clients, such as redis-py, use transaction commands such as MULTI and EXEC to simulate pipelines. It is important to distinguish between pipelines and transactions and use them appropriately based on your specific requirements. Otherwise, errors may occur. For more information about the limits on transactions, see Transactions.
If a large number of commands are run, pipelining may not perform well due to buffer limits of servers and specific clients.
Pipelining is fundamentally a client-server interaction pattern that is independent of the server architecture. Cluster instances in proxy mode, cluster instances in direct connection mode, and read/write splitting instances all support pipelining.
NoteThe cluster architecture has inherent limits. For example, the cluster architecture does not support cross-slot key access in a command. If a client attempts to access a key that does not belong to the current node to which the client is connected, a
-MOVED
error occurs. When you use pipelining in the cluster architecture, make sure that the commands within pipelines meet the requirements of the cluster architecture. For more information, see Limits on commands supported by cluster instances and read/write splitting instances.
Sample code
Performance comparison
The following code provides the performance comparison between operations with and without pipelines:
package pipeline.kvstore.aliyun.com;
import java.util.Date;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
public class RedisPipelinePerformanceTest {
static final String host = "xxxxxx.m.cnhza.kvstore.aliyuncs.com";
static final int port = 6379;
static final String password = "password";
public static void main(String[] args) {
Jedis jedis = new Jedis(host, port);
// Specify the password used to connect to the instance.
String authString = jedis.auth(password);// password
if (!authString.equals("OK")) {
System.err.println("AUTH Failed: " + authString);
jedis.close();
return;
}
//Run several commands consecutively.
final int COUNT=5000;
String key = "KVStore-Tanghan";
//1 ---Without pipelines---
jedis.del(key);//Initialize the key.
Date ts1 = new Date();
for (int i = 0; i < COUNT; i++) {
//Send a request and receive a response.
jedis.incr(key);
}
Date ts2 = new Date();
System.out.println("Without pipelines > value is: "+jedis.get(key)+" > Time elapsed: " + (ts2.getTime() - ts1.getTime())+ "ms");
//2 ----With pipelines---
jedis.del(key);//Initialize the key.
Pipeline p1 = jedis.pipelined();
Date ts3 = new Date();
for (int i = 0; i < COUNT; i++) {
//Send the request.
p1.incr(key);
}
//Receive the response.
p1.sync();
Date ts4 = new Date();
System.out.println("With pipelines > value is:"+jedis.get(key)+" > Time elapsed:" + (ts4.getTime() - ts3.getTime())+ "ms");
jedis.close();
}
}
After you enter the correct endpoint and password to connect to the Tair (Redis OSS-compatible) instance and run the preceding Java code, the following output is displayed. The output shows that the performance is enhanced with pipelines.
Without pipelines > value: 5,000 > Time elapsed: 5,844 ms
With pipelines > value: 5000 > Time elapsed: 78 ms
Methods to handle responses
When you use pipelining in Jedis, you can use two methods to handle responses. For more information, see the following sample code:
package pipeline.kvstore.aliyun.com;
import java.util.List;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;
public class PipelineClientTest {
static final String host = "xxxxxxxx.m.cnhza.kvstore.aliyuncs.com";
static final int port = 6379;
static final String password = "password";
public static void main(String[] args) {
Jedis jedis = new Jedis(host, port);
// Specify the password used to connect to the instance.
String authString = jedis.auth(password);// password
if (!authString.equals("OK")) {
System.err.println("AUTH Failed: " + authString);
jedis.close();
return;
}
String key = "KVStore-Test1";
jedis.del(key);// Initialize the key.
//-------- Method 1
Pipeline p1 = jedis.pipelined();
System.out.println("-----Method 1-----");
for (int i = 0; i < 5; i++) {
p1.incr(key);
System.out.println("Pipeline sends requests");
}
// After the pipeline sends all requests, the client starts to receive responses.
System.out.println("Requests sent. Starting to receive responses");
List<Object> responses = p1.syncAndReturnAll();
if (responses == null || responses.isEmpty()) {
jedis.close();
throw new RuntimeException("Pipeline error: no responses received");
}
for (Object resp : responses) {
System.out.println("Pipeline receives responses: " + resp.toString());
}
System.out.println();
//-------- Method 2
System.out.println("-----Method 2-----");
jedis.del(key);// Initialize the key.
Pipeline p2 = jedis.pipelined();
// Declare the responses first.
Response<Long> r1 = p2.incr(key);
System.out.println("Pipeline sends requests");
Response<Long> r2 = p2.incr(key);
System.out.println("Pipeline sends requests");
Response<Long> r3 = p2.incr(key);
System.out.println("Pipeline sends requests");
Response<Long> r4 = p2.incr(key);
System.out.println("Pipeline sends requests");
Response<Long> r5 = p2.incr(key);
System.out.println("Pipeline sends requests");
try{
r1.get(); // Errors occur because the client has not started to receive responses.
}catch(Exception e){
System.out.println(" <<< Pipeline error: The client has not started to receive responses. >>> ");
}
// After the pipeline sends all requests, the client starts to receive responses.
System.out.println("Requests sent. Starting to receive responses");
p2.sync();
System.out.println("Pipeline receives responses: " + r1.get());
System. out. println ("Pipeline receives responses: " + r2.get ());
System. out. println ("Pipeline receives responses: " + r3.get ());
System. out. println ("Pipeline receives responses: " + r4.get ());
System. out. println ("Pipeline receives responses: " + r5.get ());
jedis.close();
}
}
After you enter the correct endpoint and password to connect to the Tair (Redis OSS-compatible) instance and run the preceding Java code, the following output is displayed:
----- Method 1 -----
Pipeline sends requests
Pipeline sends requests
Pipeline sends requests
Pipeline sends requests
Pipeline sends requests
After the pipeline sends all requests, the client starts to receive responses.
Pipeline receives responses: 1
Pipeline receives responses: 2
Pipeline receives responses: 3
Pipeline receives responses: 4
Pipeline receives responses: 5
----- Method 2 -----
Pipeline sends requests
Pipeline sends requests
Pipeline sends requests
Pipeline sends requests
Pipeline sends requests
<Pipeline error: The client has not started to receive responses>
After the pipeline sends all requests, the client starts to receive responses.
Pipeline receives responses: 1
Pipeline receives responses: 2
Pipeline receives responses: 3
Pipeline receives responses: 4
Pipeline receives responses: 5