Skip to content

A Last-Writer-Wins (LWW) CRDT Go implementation with Redis support.

Notifications You must be signed in to change notification settings

cedricblondeau/go-lww-element-set

Repository files navigation

Last-Writer-Wins (LWW) Element Set GoDoc Build Status

A LWW CRDT implementation with Redis support in Go.

forthebadge

A CR-What?!

A conflict-free replicated data type (CRDT) is a type of data structure that is used to achieve strong eventual consistency (SEC) and monotonicity (ie, there are no rollbacks) across a set of nodes in a distributed system.

It can be particularly interesting in available/partition-tolerant (AP) environment because it makes conflicts mathematically impossible. In other words, there is no need for conflict resolution.

Package

This package focuses on Last-Writer-Wins (LWW) Element Set data structure that uses timestamped adds and removes. It provides 2 implementations (Go maps and Redis).

Unlike Roshi, this package does not provide replication, sharding, garbage collection or REST-ish HTTP interface.

Public API

This package (lww) exposes 3 different constructors. Each constructor returns an ElementSet. Public methods return an error if something like a network failure error happens. Add() and Remove() methods do not guarantee that the operation took effect, just that it was acknowledged if no error. Exists() and Get() methods provides liveness guarantees only.

See GoDoc reference for details.

Usage

Go maps backed LWW element set

import (
	"time"

	"github.com/cedricblondeau/go-lww-element-set"
)

ms := lww.NewMapElementSet()
ms.Add("Hello", time.Now())
ms.Add("Hi!", time.Now())
ms.Remove("Hello", time.Now())
elements, err := ms.Get() 
elements // ["Hi!"]

Redis backed LWW element set

import (
	"time"

	"github.com/cedricblondeau/go-lww-element-set"
	redis "gopkg.in/redis.v4"
)

rc := redis.NewClient(&redis.Options{
	Addr:     "localhost:6379",
	Password: "", // no password set
	DB:       0,  // use default DB
})
rs := lww.NewRedisElementSet("shopping_cart", rc)
addErr1 := s.Add("Product #1", time.Now()) // Check addErr1 for eventual redis error
addErr2 := rs.Add("Product #2", time.Now()) // Check addErr2 for eventual redis error
rmErr := rs.Remove("Product #1", time.Now()) // Check rmErr for eventual redis error
elements, err := rs.Get() // Check err for eventual redis error
elements //  ["Product #2"]

You can also use NewRedisElementSetWithCustomMarshalling() construtor to specify custom marshalling/unmarshalling functions.

Implementation details

ElementSet defines the LWW Element Set logic. An ElementSet is backed by two underlying timestamped sets (timedSet).

timedSet is an interface that defines a set where added element are associated with timestamps. This package provides two implementations of this interface:

  • mapTimedSet (using Go maps)
  • redisTimedSet (using Redis)

Go maps implementation

Go maps implementation uses two mapTimedSet. A mapTimedSet is a timedSet backed with a Go map. Because Go maps are not safe for concurrent use, mutual exclusion is used.

Redis implementation

Redis implementation uses two redisTimedSet. A redisTimedSet is a timedSet backed with a Redis sorted set. This implementation uses go-redis client.

To make the add() action atomic, a Redis script (which is transactional by definition and by extension atomic) is used.

Redis uses IEEE 754 64-bit numbers to rank elements in a sorted set (score). As a result, the given timestamps will be converted to float64 and it will limit the precision to one millisecond.

Run tests

To run tests with race detector enabled:

go test -race

References

About

A Last-Writer-Wins (LWW) CRDT Go implementation with Redis support.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages