ApsaraDB for Redis and Tair support the pipelining feature of native 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 these two modes work. pipeline

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.
Important 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 the commands and runs them in order. As such, 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.

  • If a large number of commands are run, pipelining may not perform well due to buffer limits of servers and specific clients.
  • Cross-slot key access incurs errors in pipelining mode because the cluster architecture does not support cross-slot key access in a command.

    For example, if the client requests a key on a different data node of a cluster instance in direct connection mode, the -MOVE error is reported. In this case, your business may be affected because clients in pipelining mode cannot immediately handle errors. Therefore, make sure that requested keys reside on the same data node when you use pipelining for cluster instances.

Sample code

Performance testing

The following code provides the performance comparison between operations using pipelining and those without using pipelining:

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);
                //The password of the ApsaraDB for Redis instance.
                String authString = jedis.auth(password);// password
                if (! authString.equals("OK")) {
                    System.err.println("AUTH Failed: " + authString);
                    jedis.close();
                    return;
                }
                //Runs several commands consecutively.
                final int COUNT=5000;
                String key = "KVStore-Tanghan";
                //1 ---Without using pipeline operations---
                jedis.del(key);//Initializes the key.
                Date ts1 = new Date();
                for (int i = 0; i < COUNT; i++) {
                    //Sends a request and receives a response.
                    jedis.incr(key);
                }
                Date ts2 = new Date();
                System.out.println("Without Pipeline > value is: "+jedis.get(key)+" > Time elapsed: " + (ts2.getTime() - ts1.getTime())+ "ms");
                //2 ----Using pipeline operations---
                jedis.del(key);//Initializes the key.
                Pipeline p1 = jedis.pipelined();
                Date ts3 = new Date();
                for (int i = 0; i < COUNT; i++) {
                    //Sends the request. 
                    p1.incr(key);
                }
                //Receives the response.
                p1.sync();
                Date ts4 = new Date();
                System.out.println("Using Pipeline > value is:"+jedis.get(key)+" > Time elapsed:" + (ts4.getTime() - ts3.getTime())+ "ms");
                jedis.close();
        }
    }

After you access the ApsaraDB for Redis instance with the correct address and password 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

With pipelines defined in Jedis, responses are processed in two methods, as shown in 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);
                // The password of the ApsaraDB for Redis 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 pipeline sends all requests, the client starts to receive responses.
                System.out.println("Sending requests completed. Start 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 response: " + 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 pipeline sends all requests, the client starts to receive responses.
                System.out.println("Sending requests completed. Start to receive responses");
                p2.sync();  
                System.out.println("Pipeline receives response: " + r1.get());  
                System. out. println ("Pipeline receives response: " + r2.get ());  
                System. out. println ("Pipeline receives response: " + r3.get ());
                System. out. println ("Pipeline receives response: " + r4.get ());
                System. out. println ("Pipeline receives response: " + r5.get ());
                jedis.close();
            }
    }

After you access the ApsaraDB for Redis instance with the correct address and password and run the 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 pipeline sends all requests, the client starts to receive responses.
Pipeline receives response: 1
Pipeline receives response: 2
Pipeline receives response: 3
Pipeline receives response: 4
Pipeline receives response: 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 pipeline sends all requests, the client starts to receive responses.
Pipeline receives response: 1
Pipeline receives response: 2
Pipeline receives response: 3
Pipeline receives response: 4
Pipeline receives response: 5