Go FoundationDB Atomic Counter


(Ravilution) #1

I want to increment and decrement int counter value using atomic operations. I am confused on how to use the add method in GO Lang. Can someone share an example? I am looking for something similar to Cassandra counter.


(Alex Miller) #2

This was asked before, but the answer unfortunately ended up on on a github issue instead of on the forums, so let me reproduce it here for search ability:

But to answer your question, the short answer is to use “encoding/binary” to encode a -1 (in little endian two’s compliment). Adding a negative 1 is then the same as a 1. In two’s complement, -1 is entirely 0xff bytes, so you can hard code that in, or you could do something like this:

func incrKey(tor fdb.Transactor, k fdb.Key) error {
    _, e := tor.Transact(func (tr fdb.Transaction) (interface{}, error) {
        buf := new(bytes.Buffer)
        err := binary.Write(buf, binary.LittleEndian, int64(1))
        if err != nil {
            return nil, err
        }
        one := buf.Bytes()
        tr.Add(k, one)
        return nil, nil
    })
    return e
}

func decrKey(tor fdb.Transactor, k fdb.Key) error {
    _, e := tor.Transact(func (tr fdb.Transaction) (interface{}, error) {
        buf := new(bytes.Buffer)
        err := binary.Write(buf, binary.LittleEndian, int64(-1))
        if err != nil {
            return nil, err
        }
        negativeOne := buf.Bytes()
        tr.Add(k, negativeOne)
        return nil, nil
    })
    return e
}

Those two samples are very similar, but in the first, we increment the key by 1, and in the second we decrement by 1 (i.e., increment by -1). If we looked at the bytes, we would see that the first is a 1 byte followed by 7 0 bytes, and then the second is 8 0xff bytes.

Here’s a sample that reads the given key, as well as a little tester than tries to read the key and do some atomic updates:

func getKey(tor fdb.Transactor, k fdb.Key) (int64, error) {
    val, e := tor.Transact(func (tr fdb.Transaction) (interface{}, error) {
        return tr.Get(k).Get()
    })
    if e != nil {
        return 0, e
    }
    if val == nil {
        return 0, nil
    }
    byteVal := val.([]byte)
    var numVal int64
    readE := binary.Read(bytes.NewReader(byteVal), binary.LittleEndian, &numVal)
    if readE != nil {
        return 0, readE
    } else {
        return numVal, nil
    }
}

func run(db fdb.Database) {
    var t tuple.Tuple
    t = []tuple.TupleElement{"foo"}
    var key fdb.Key
    key = t.Pack()
    db.Transact(func (tr fdb.Transaction) (interface{}, error) {
        tr.Clear(key)
        return nil, nil
    })
    v1, _ := getKey(db, key)
    fmt.Printf("v1 = %d\n", v1)
    incrKey(db, key)
    v2, _ := getKey(db, key)
    fmt.Printf("v2 = %d\n", v2)
    decrKey(db, key)
    v3, _ := getKey(db, key)
    fmt.Printf("v3 = %d\n", v3)
    incrKey(db, key)
    v4, _ := getKey(db, key)
    fmt.Printf("v1 = %d\n", v4)
    incrKey(db, key)
    v5, _ := getKey(db, key)
    fmt.Printf("v2 = %d\n", v5)
    decrKey(db, key)
}

If I run this (passing a database for my local instance), I get:

v1 = 0
v2 = 1
v3 = 0
v1 = 1
v2 = 2

which is the desired behavior.


(Ravilution) #3

Thank you. I highly appreciate. Wow. For someone (me) new to Go and FoundationDB this looks super complicated. In Cassandra I have to call update with +1 or -1. :cry:


(Ravilution) #4

@alexmiller The more I learn about Go, more respect and appreciation I have for your answer. Thank you _/\_