Skip to main content

Redis Pipelines and Transactions with Golang

Redis is an in memory datastore mostly used as a cache. Clients can send commands to server using TCP protocol and get the response back. So, usually a request works like this.

  • Client sends a request to server and waits for the response to be sent back.
  • Server processes the command and writes the response to the socket for client to read.
Sometimes, in application flow we have to process multiple keys at once. In this case, for each request there will be a network overhead for the round trip between server and client. We can reduce this network overhead by sending commands to the Redis server in a batched manner and then process all the responses at once. This can be achieved using pipelines as well as transactions.
Pipelining in Redis is when you send all the commands to the server at once and receive all the replies in one go. It does not provide any guarantees like no other commands will be processed between your pipelined commands.
Transactions in Redis are meant to be used when you need guarantee that no other commands will be processed between your block of commands. A transaction is started by sending MULTI keyword to the server.
Let us see examples of how to execute both in Golang using Redigo as redis client.
  • Connect to Redis server first. You can change the host parameter to the IP address/DNS of your Redis server.
import (
   "github.com/gomodule/redigo/redis"
   "time")

type RedisCache struct {
   pool *redis.Pool
}

var (
   // Pool :
   pool      *redis.Pool
   RedisCacheClient *RedisCache
)

func init() {
   pool = newPool(<host>, "")
   RedisCacheClient = &RedisCache{pool: pool}
}
func newPool(server string, password string) *redis.Pool {
   return &redis.Pool{
      MaxIdle:     3,
      MaxActive:   200,
      IdleTimeout: 240 * time.Second,
      Wait:        true,
      Dial: func() (redis.Conn, error) {
         c, err := redis.Dial("tcp", server, redis.DialPassword(password),
            redis.DialReadTimeout(5000*time.Millisecond), redis.DialWriteTimeout(5000*time.Millisecond), redis.DialDatabase(10))
         if err != nil {
            return nil, err
         }
         return c, nil
      },

      TestOnBorrow: func(c redis.Conn, t time.Time) error {
         _, err := c.Do("PING")
         return err
      },
   }
}
  • Redigo support pipelining using the Send, Flush and Receive methods. Send writes the command to the connection’s output buffer. Flush flushes the connection’s output buffer to the server. Receive reads a single reply from the server. Here is a simple pipeline using these constructs:
conn := RedisCacheClient.pool.Get()
defer conn.Close()
//write all commands to connection's output buffer
conn.Send("SET", "foo1","bar1")
conn.Send("SET", "foo2", "bar2")
conn.Send("GET", "foo3")
//flush output buffer to server
conn.Flush()
//receive replies one by one 
reply1, err1 := conn.Receive()
reply2, err2 := conn.Receive()
reply3, err3 := conn.Receive()
  • Transactions can be implemented using SEND and DO methods. Do will write the command it receives as argument to connection buffer and flush the entire buffer to server. All the replies will be received as response to DO command. For example:
conn := RedisCacheClient.pool.Get()
defer conn.Close()
//start transaction
conn.Send("MULTI")
//add commands to the output buffer
conn.Send("INCR" , "foo1")
conn.Send("INCR", "foo2")
conn.Send("GET", "foo1")
//Flush all the commands to output buffer and read replies
reply, err := conn.Do("EXEC")
Pipelines without transactions give you better performance then transactions due to the fact that other commands can be executed between the pipelined block where as transactions ensure that this will not happen. So, use transactions if you need atomicity guarantees else go with pipelines for enhanced performance.

Comments

Popular posts from this blog

Monitoring Spring Boot API response times with Prometheus

Prometheus is a very useful tool for monitoring web applications. In this blog post, we will see how to use it to monitor Spring Boot API response times. You have to include following dependencies in your build.gradle file: compile group: 'io.prometheus', name: 'simpleclient_hotspot', version: '0.0.26' compile group: 'io.prometheus', name: 'simpleclient_servlet', version: '0.0.26' compile group: 'io.prometheus', name: 'simpleclient', version: '0.0.26' Now you will have to expose a Rest Endpoint, so that Prometheus can collect the metrics by scraping it at regular intervals. To do that, you would have to include these Java configuration classes: @Configuration @ConditionalOnClass(CollectorRegistry.class) public class Config { private static final CollectorRegistry metricRegistry = CollectorRegistry. defaultRegistry ; @Bean ServletRegistrationBean registerPrometheusExporterServlet() { retu...

Monitoring Go micro services using Prometheus

In this age of web scale architecture, Golang has become the language of choice for many developers to implement high throughput micro services. One of the key components of running and maintaining these services is to be able to measure the performance. Prometheus is a time series based database which has become very popular for monitoring micro services. In this blog, we will see how to implement monitoring for your Go micro service using prometheus. We will be using the official Prometheus library github.com/prometheus/client_golang/prometheus/promhttp to expose the go metrics. You can use Promhttp library’s HTTP handler as the handler function to expose the metrics. package main import ( "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus/promhttp" "net/http" ) func main() { router := mux.NewRouter() router.Handle( "/metrics" , promhttp.Handler()) http.ListenAndServe( ":8080" , router ) }...