Prasanth Janardhanan

A Simple Wrapper to BadgerDB Key-Value store in Go

BadgerDB is an embeddable key-value store written in Go. It is a persistent store.

In this article, we build a wrapper around badgerDB. The purpose of this wrapper is to make it simple to save simple values to the DB in “virtual tables”. The concept is an adaptation from the Sett project. Much of the code -especially the unit tests - are changed though.

Usage

import(
    "github.com/prasanthmj/sett"
)

s := sett.Open(sett.DefaultOptions("./data/mydb"))

To set a string key-value pair, call s.SetStr(k, v) and to get it back, call s.GetStr(k).

Virtual Tables

BadgerDB has no tables. However, we can simulate a table using simple key prefixes.

Here is how you set value:

s.Table("cache").SetStr(k, v)

In order to get the value back, call

v, err := s.Table("cache").GetStr(k)

Having these virtual tables helps organize the keys by purpose (cache, session for example), and avoids collisions. You can drop the entire table together

s.Table("cache").Drop()

Expiring tables (TTL)

You can set the values in a table to expire after some time. For example, the “cache” table may hold values for 10 minutes whereas the “session” table expires values after one hour.

cache := s.Table("cache").WithTTL(10 * time.Minute)

session := s.Table("session").WithTTL(1 * time.Hour)

Saving structures

Sett provides functions to save structures as well. Sett uses gob package for dynamically creating the structure instances. You have to call gob.Register() to register the structures that you use with Sett.

This is how you would keep user details in a session, for example:

type UserSession struct {
	ID    string
	Email string
}
//register the structure first
gob.Register(&UserSession{})

user := &UserSession{ID:user.ID, Email:user.Email}

s.Table("session").Set(session_id, user)

Insert

Sometimes we want to save a record and just need a key to refer to that record. In the case of saving to sessions, we need to save User information and then get a session ID - a short string that should be random. We can use Insert() in this case.

sessionID, err := sessionTable.Insert(user)

Then we can send the session ID as part of the response.

Cut

‘Cut’ is an operation where we get the value and remove the item from the database in a single atomic operation. Imagine you have several concurrent task handlers running. You want to be sure that no two task handlers get the same task. You can use Cut() operation to pick tasks.


key,err := table.Insert(t)
if err == nil {
    access_key <- key    
}

On the other end, the go routine picks the key and processes the task

for {
        select {
        case akey := <-access_key:
            iobj, err := store.Table(tab).Cut(akey)
            if err == nil{
                ProcessTask(iobj)    
            }
        case <-closed:
					return
		}
            
    }

Updating

Update is done in a callback like this:

s.Table("tasks").Update(key, func(iv interface{}) error {
		tobj := iv.(*TaskObj)
		tobj.Access += 1
		tobj.Status = "inprogress"
		return nil
}, false)