Power of bolt-DB in Go!

Mahedi Hasan Jisan
5 min readOct 25, 2021

“Bolt is a pure Go key/value store inspired by Howard Chu’s LMDB project. The goal of the project is to provide a simple, fast, and reliable database for projects that don’t require a full database server such as Postgres or MySQL.

Since Bolt is meant to be used as such a low-level piece of functionality, simplicity is key. The API will be small and only focus on getting values and setting values. That’s it.”

As the definition mentions that simplicity is the key. This tutorial will walk through a basic example of how to use bolt-DB using Go.

The goal of the project is to provide a simple, fast, and reliable database for projects that don’t require a full database server such as Postgres or MySQL.

How it is simple?

“Bolt uses a single memory-mapped file, implementing a copy-on-write B+ tree, which makes reads very fast. Bolt’s load time is better, especially during recovery from crash, since it doesn’t need to read the log to find the last succeeded transaction: it just reads IDs for two B+tree roots and uses the one with the greater ID.”

Bolt-DB Installation (Linux)

go install github.com/boltdb/bolt/...

Dependencies: Go Installation (Linux)

Installing Go from https://golang.org/dl/
After installation the Go, follow the below steps:
1. Note for installation: tar -xzvf go1.17.1.linux-amd64.tar.gz
2. ./go/bin/go
3. sudo mv go /usr/local/
4. vi ~/.bashrc (hit ENTER and insert at the end (SHIFT + G): # Add go to path → export PATH=$PATH:/usr/local/go/bin → CTRL + C → wq → ENTER
5. source ~/.bashrc
6. go → shows you ouput that it is installed indeed!
7. go version

If you want to be familiar with the Go language, then visit this link: Familiar with Go!

Opening a database:

// Openinig a database name: "test.db"
// if it doesn't exists, then the database will be created
db, err := bolt.Open("test.db", 0644, &bolt.Options{Timeout: 1 * time.Second})
if err != nil {
log.Fatal(err)
}
defer db.Close()

→ bolt.open: It will create a database even if there isn’t any! For example, in this case, the database name is “test.db”

&bolt.Options{Timeout: 1 * time.Second}: If some other service opens the same database and you want to access that database, then it will create a deadlock. To void that situation, this code is used.

defer db.close(): Always remember to close the db connection.

Create bucket and store key:value pairs:

// setting key/value pair
key := []byte("Rogers")
value := []byte("Avengers, Assemble!")
// store some data to a bucket AKA table in the database
err = db.Update(func(tx *bolt.Tx) error {
bucket, err := tx.CreateBucketIfNotExists(movie)
if err != nil {
return err
}
return bucket.Put(key, value)
})
if err != nil {
log.Fatal(err)
}

If you want to clear the concept of key:value pairs or B+ tree works then check out this article: B-Trees

db.Update: Bolt only allows one read-write transaction at a time but allows as many as you want read-only transactions. “Individual transactions and all objects created from them (e.g. buckets, keys) are not thread-safe. To work with data in multiple goroutines you must start a transaction for each one or use locking to ensure only one goroutine accesses a transaction at a time. Creating transactions from the DB is thread-safe.” db.Update opens up a read-write transaction.

bucket, err:= tx.CreateBucketIfNotExists(movie): It will create the bucket for example: movie = “marvel”!

bucket.Put(key, value): It will store the key-value pair in the table/bucket.

Retrieve data from bucket/table:

// retrieve the data from the database.table: test.marvel
err = db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(movie)
if bucket == nil {
return fmt.Errorf("bucket %q not found", movie)
}
val := bucket.Get(key)
fmt.Println(string(key), ": ")
fmt.Println(string(val))
return nil
})

db.View: This will allow us to open a transaction and view the table.

bucket:= tx.Bucket(movie): Creating an instance of the bucket.

val := bucket.Get(key): Storing the value from the bucket using the key.

Looks pretty easy right? Do you know what we did so far equivalent to PostgreSQL and SQLite?

  1. Creating a database: “test.db”
  2. Creating a table or bucket: “marvel”
  3. Storing values inside the table or bucket: “key:value”

We did that with 50 lines of code. You want to see the full code, there you go:

package mainimport (
"fmt"
"log"
"time"
"github.com/boltdb/bolt"
)
var movie = []byte("marvel")func main() {
// Openinig a database name: "test.db"
// if it doesn't exists, then the database will be created
db, err := bolt.Open("test.db", 0644, &bolt.Options{Timeout: 1 * time.Second})
if err != nil {
log.Fatal(err)
}
defer db.Close()
// setting key/value pair
key := []byte("Rogers")
value := []byte("Avengers, Assemble!")
// store some data to a bucket AKA table in the database
err = db.Update(func(tx *bolt.Tx) error {
bucket, err := tx.CreateBucketIfNotExists(movie)
if err != nil {
return err
}
return bucket.Put(key, value)
})
if err != nil {
log.Fatal(err)
}
// retrieve the data from the database.table: test.marvel
err = db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(movie)
if bucket == nil {
return fmt.Errorf("bucket %q not found", movie)
}
val := bucket.Get(key)
fmt.Println(string(key), ": ")
fmt.Println(string(val))
return nil
})
if err != nil {
log.Fatal(err)
}
}

****Think how to use the key:value pair to make a persistence database for your application?

Compile and Run:

Step #1: go mod init main.go

Step #2: go mod tidy

Step #3: go run main.go

Output:

Bucket Output!

That’s it! It’s that simple and I guess you understand by the looks of it, how powerful and simple it can be!

Nested Bucket Example:

package mainimport (
"fmt"
"log"
"time"
"github.com/boltdb/bolt"
)
var movie = []byte("movies")
var marvel = []byte("MCU")
func main() {
// Openinig a database name: "test.db"
// if it doesn't exists, then the database will be created
db, err := bolt.Open("test.db", 0644, &bolt.Options{Timeout: 1 * time.Second})
if err != nil {
log.Fatal(err)
}
defer db.Close()
// setting key/value pair
key := []byte("Rogers")
value := []byte("Avengers, Assemble!")
// store some data to a bucket AKA table in the database
err = db.Update(func(tx *bolt.Tx) error {
root, err := tx.CreateBucketIfNotExists(movie)
if err != nil {
return err
}
_, err = root.CreateBucketIfNotExists(marvel)
if err != nil {
return err
}
return nil
})
if err != nil {
log.Fatal(err)
}
fmt.Println("DB setup done!")
err = db.Update(func(tx *bolt.Tx) error {
err := tx.Bucket(movie).Bucket(marvel).Put(key, value)
if err != nil {
return err
}
return nil
})
fmt.Println("first entry in the inner bucket!")
if err != nil {
log.Fatal(err)
}
// retrieve the data from the database.table: test.marvel
err = db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(movie).Bucket(marvel)
if bucket == nil {
return fmt.Errorf("bucket %q not found", movie)
}
val := bucket.Get(key)
fmt.Println(string(key), ": ")
fmt.Println(string(val))
return nil
})
if err != nil {
log.Fatal(err)
}
}

Go Documentation: https://golang.org/doc/

Bolt-DB Documentation: https://github.com/boltdb/bolt

Cheers! 🙌

--

--