Configuring Memcached in Go

Memcached is a distributed memory-caching system. Caching returns faster results and also reduces the number of database queries required.

As a memcached client we have used the gomemcache client, it has a variety of caching functions and it is simple to use.

First let's install memcached client package by:

go get github.com/bradfitz/gomemcache/memcache

We will initialise and establish connection with local memcached service,

import "github.com/bradfitz/gomemcache/memcache"

var memcachedInstance *memcache.Client

func initializeMemcachedClients() {
    connectionString := “127.0.0.1:11211”

    memcachedInstance = memcache.New(connectionString)
    memcachedInstance.MaxIdleConns = 3
    memcachedInstance.Timeout = 100 * time.Millisecond
}

MaxIdleConns specifies the maximum number of idle connections that will be maintained per address. If it is less than one, DefaultMaxIdleConns will be used. DefaultMaxIdleConns is the default maximum number of idle connections kept for any single address. Default value is 2.

Timeout specifies the socket read/write timeout. If zero, DefaultTimeout is used.

The most used functions of memcached are - Get and Set.

Set - to set a key-value pair in cache

Set() requires the instance of Item struct provided by gomemcache, which consists of Key, Value and Expiry. You will need to convert your value of any data type to a byte array first before invoking the Set method.

func SetCache(key string, value []byte, expiry int) error {
	// key is userId = user1
	// value is user details 
	// expiry is cache expiration time in seconds 
	cacheErr := memcachedInstance.Set(&memcache.Item{
		Key:        key,
		Value:      value,
		Expiration: int32(expiry),
	})
    
	if cacheErr != nil {
		return cacheErr
	}

	return nil
}

Get - to fetch data based on a key (ex: userId)

Get() accepts `key` as argument and returns an instance of Item struct with Key, Value.

// key against which cache was previously Set
func GetCache(key string) (interface{}, error) {
	value, cacheErr := memcachedInstance.Get(key)

	if cacheErr != nil {
		return nil, cacheErr
	}

	return value.Value, nil
}

Output of GetCache would look like following:

map[user1:{1 abc abc@def.com password1}]

Multi Get - to fetch data for multiple keys

GetMulti() can be used as a batch version of Get, it takes an array of keys and returns a map which may have less keys than input because of cache misses.

// keys is a string array of userIds
func GetMultiCache(keys []string) (map[string]interface{}, error) {
	cacheResp, cacheErr := memcachedInstance.GetMulti(keys)
	if cacheErr != nil {
		return nil, cacheErr
	}

	response := make(map[string]interface{})
	for key, item := range cacheResp {
		response[key] = item.Value
	}

	return response, nil
}

Output of GetMutliCache would look like following :

map[
    user1:{1 abc abc@def.com password1} 
    user2:{2 xyz xyz@def.com password2} 
    user3:{3 pqr pqr@def.com password3}
]

Let's see an example to prepare final code, where we set the details of a user in cache and get it by userId, using the methods of memcached client mentioned above.

import "github.com/bradfitz/gomemcache/memcache"

type userDetails struct {
	Id       string `json:"id"`
	Name     string `json:"name"`
	Email    string `json:"email"`
	Password string `json:"password"`
}


func main(){
    initializeMemcachedClients()
	
    userId := 1001
    userDetails := GetUserById(userId)
}

func GetUserById(userId string) interface{} {

	cacheResponse, cacheErr := GetCache(userId)
	if cacheErr != nil {
		// data not found in cache
		// Get user details and set in cache as well.
		dbData := userDetails{1 sam sam@def.com password1}
        
		// you will need to convert dbData to []byte
		convertedData, _ := json.Marshal(dbData)
		SetCache(userId, convertedData, 100)
		
		return dbData
	}
	return cacheResponse

}

So, by using caching data, you can significantly improve the performance of your application.

Isha Bansod

Isha Bansod