Ristretto - the Most Performant Concurrent Cache Library for Go
2 Mar 2021

Ristretto - the Most Performant Concurrent Cache Library for Go

Recently, I discovered a surprisingly reliable memory caching solution, which I’m planning to use in all my further applications to increase performance. In this blog post, I will share some code examples of how you can integrate Ristretto caching library into your application.

Ristretto is a fast, concurrent cache library built with a focus on performance and correctness.

This library was created by the Dgraph team as a contention-free cache for the Dgraph database.

Let’s dive into the practical example. We are going to build a simple application that gets a list of users from the database. In the first iteration, there will be no caching layer at all. In the second iteration, we will add a Ristretto caching and compare execution time.

Below, you can see that I defined a repository package with the Repository interface and with InMemoryRepository implementation:

package repository

import "fmt"

// Repository interface to handle users data.
type Repository interface {
	GetUsers() map[int]string
}

type InMemoryRepository struct{}

// NewInMemoryRepository constructs and returns InMemoryRepository.
func NewInMemoryRepository() *InMemoryRepository {
	return &InMemoryRepository{}
}

// GetUsers returns 50000 dummy users from the in-memory repository.
func (r *InMemoryRepository) GetUsers() map[int]string {
	users := make(map[int]string)
	for i:=1; i <= 50000; i++ {
		users[i] = fmt.Sprintf("User %d", i)
	}

	return users
}

Next, we are going to call a GetUsers() method 100 times to simulate calling of the same function from several places in the real-world applications:

package main

import (
	"fmt"
	"github.com/alexsergivan/blog-examples/ristretto/repository"
)

func main() {
	for i:=0; i<100; i ++ {
		UsersGetter(repository.NewInMemoryRepository())
	}
}

func UsersGetter(repository repository.Repository) {
	repository.GetUsers()
}

Let’s measure how much time it takes to execute it with time go run main.go:

1.46s user
0.34s system
106% cpu
1.686 total

Next, we are going to add a caching layer to our application.

Don’t forget to get the Ristretto library:

  go get github.com/dgraph-io/ristretto

Inside repository package we inject Ristretto cache:

package repository

import (
	"fmt"
	"github.com/dgraph-io/ristretto"
	"time"
)

type InMemoryRepository struct{
	cache        *ristretto.Cache
}

// NewInMemoryRepository constructs and returns InMemoryRepository.
func NewInMemoryRepository(ristrettoCache *ristretto.Cache) *InMemoryRepository {
	return &InMemoryRepository{
		cache: ristrettoCache,
	}
}

// GetUsers returns 50000 dummy users from the in-memory repository.
func (r *InMemoryRepository) GetUsers() map[int]string {
	key := "users"
	value, found := r.cache.Get(key)
    // If the users data not cached yet, get it from the repository.
	if !found {
		users := make(map[int]string)
		for i:=1; i <= 50000; i++ {
			users[i] = fmt.Sprintf("User %d", i)
		}
        // Adds data to the cache for 1h.
		r.cache.SetWithTTL(key, users, 1, 1*time.Hour)
		time.Sleep(10 * time.Millisecond)
		return users
	}
	return value.(map[int]string)
}

Next, inside the main() function we initiate a new Ristretto cache and pass it to the InMemoryRepository:

  package main

import (
	"github.com/alexsergivan/blog-examples/ristretto/repository"
	"github.com/dgraph-io/ristretto"
)

func main() {
	ristrettoCache, _ := ristretto.NewCache(&ristretto.Config{
		NumCounters: 1e7,     // Num keys to track frequency of (10M).
		MaxCost:     1 << 30, // Maximum cost of cache (1GB).
		BufferItems: 64,      // Number of keys per Get buffer.
	})

	for i:=0; i<100; i ++ {
		UsersGetter(repository.NewInMemoryRepository(ristrettoCache))
	}
}

func UsersGetter(repository repository.Repository) {
	repository.GetUsers()
}

Let’s check how much time it takes to perform the same action:

0.29s user
0.26s system
147% cpu
0.377 total

As you can notice, the total time is 4 times less than in the example without caching layer.

Despite a silly example, I hope you got an idea of how to integrate the Ristretto caching into your application and how it could improve overall performance.

The complete source code you can find here.

Related posts

How to Show Flash Messages in Go web applications (with Echo framework)
4 Feb 2021

How to Show Flash Messages in Go web applications (with Echo framework)

When we create a web application, usually, there a need to communicate with the users to inform them about the results of their actions. The easiest way to communicate - is to send messages. These messages might be warnings, errors, or just informational text. In this article, we will improve the UX of our user authentication application from the previous article by adding an error flash message when the user entered a wrong password and a success message after user authorisation.

go echo flash messages
User Authentication in Go Echo with JWT
28 Jan 2021

User Authentication in Go Echo with JWT

In this article, we will build a simple user authentication functionality using JWT (JSON Web Token). In the examples, I’m going to use a Go Echo framework. This will allow us to avoid writing some boilerplate code.

go authentication JWT Echo
A Simple Queue Implementation in Golang with channels
12 Jan 2021

A Simple Queue Implementation in Golang with channels

In this post, I’m going to show the way how we can implement a simple queue in Golang, using channels.

go queue channel goroutine
How to Sort Strings With Go Alphabetically in Any Language
4 Jan 2021

How to Sort Strings With Go Alphabetically in Any Language

In this article I’m going to show how easy we can sort strings alphabetically in different languages, using Go. It seems like an easy task if we want to sort English words, however, it’s not so trivial if we want to sort correctly strings with special characters or in other languages, i.e Cyrillic based.

go sorting alphabetical sort
How to Control Router Access Permissions in Go Web Apps
23 Dec 2020

How to Control Router Access Permissions in Go Web Apps

In this post I’m going to describe how can we limit user access to the specific url in golang web application. I will use chi router - a lightweight, idiomatic and composable router for building Go HTTP services.

go router chi