ソースコードから理解する技術-UnderSourceCode

手を動かす(プログラムを組む)ことで技術を理解するブログ

Golangの組み込みDBとして使えるBoltについて

はじめに

前回にも書きましたが、Golangの組み込みDBとして使うことができるBoltを見つけたので、使ってみました。
GitHub - boltdb/bolt: An embedded key/value database for Go.

以下、このDBについて紹介していきたいと思います。

Boltとは

BoltはKeyValue型のデータベースで、これ自体がGolangで実装されています。
シンプルで早いDBを目指しており、またAPIやファイルフォーマットは既に安定した(stable)な状態のようです。
Golangの組み込みDBとして使うには良い選択肢ではないでしょうか。。。

Boltの特徴

Boltは以下の特徴を持っています。

1. Golangで実装されていて組み込みDBとして使える
2. データ自体が別ファイルとして保存される
3. データの集合をBucketという単位で扱う

Boltはデータの集合を「Bucket」という単位で扱います。
このBucketの中にKeyValue形式でデータを登録します。
データの集合という意味では、BucketRDBなどのテーブルに近いですが、Bucketはネストさせることができます。
これらについては、サンプルプログラムにてより詳しく見てみます。

4. トランザクションはスレッドセーフではない

これは注意点だと思います。
READMEには以下のようにあります。

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.

GitHub - boltdb/bolt: An embedded key/value database for Go.

goroutineなどから使うときには同時にアクセスが行われないよう注意する必要があります。

サンプルプログラム

Boltを使ってサンプルプログラムを書いてみました。
一つのBucketに登録・参照・削除するプログラムと、Bucketをネストさせるプログラムです。

一つのBucketに登録・参照・削除するプログラム

まずは一つのBucketに登録・参照・削除するプログラムです。
以下のようになります。

登録・更新
db, err := bolt.Open(dbFile, 0600, nil)
(中略)

return db.Update(func(tx *bolt.Tx) error {
    b, err := tx.CreateBucketIfNotExists([]byte(bucket))
    (中略)
    err = b.Put(keybytes(id), []byte(data))
参照
db, err := bolt.Open(dbFile, 0600, nil)
(中略)

db.View(func(tx *bolt.Tx) error {
    b := tx.Bucket([]byte(bucket))
    c := b.Cursor()
    for k, v := c.First(); k != nil; k, v = c.Next() {
        (何らかの処理)
    }
削除
db, err := bolt.Open(dbFile, 0600, nil)
(中略)

db.Update(func(tx *bolt.Tx) error {
  b := tx.Bucket([]byte(bucket))
  err := b.Delete(key)
  return err
})

先にも書いたようにBoltは「Bucket」の中にデータを格納します。
なので処理の流れは

1. DBを開く(このときデータファイルが存在しなければ作成される)
2. Bucketを作る or 参照する
3. Bucketに対して登録・参照・削除する

という形となります。

Bucketをネストさせるプログラム

次にBucketをネストさせるプログラムです。
こちらも登録・参照を行います。

登録

db, err := bolt.Open(dbFile, 0600, nil)
(中略)

err = insert(db)
(中略)

func insert(db *bolt.DB) error {
	return db.Update(func(tx *bolt.Tx) error {
		// create root bucket.
		users, err := tx.CreateBucketIfNotExists([]byte(rootBucket))
		if err != nil {
			return err
		}

		// create nested bucket.
		tom, err := users.CreateBucketIfNotExists([]byte(tomBucket))
		if err != nil {
			return err
		}

		// insert.
		err = tom.Put([]byte("key1"), []byte("tom's todo"))
		if err != nil {
			return err
		}

		// create nested bucket.
		ken, err := users.CreateBucketIfNotExists([]byte(kenBucket))
		if err != nil {
			return err
		}

		// insert.
		err = ken.Put([]byte("key1"), []byte("ken's todo1"))
		if err != nil {
			return err
		}
		err = ken.Put([]byte("key2"), []byte("ken's todo2"))
		if err != nil {
			return err
		}

		return nil
	})
}

参照

db, err := bolt.Open(dbFile, 0600, nil)
(中略)

err = reference(db)
(中略)

func reference(db *bolt.DB) error {
	return db.View(func(tx *bolt.Tx) error {
		users := tx.Bucket([]byte(rootBucket))

		// select from nested bucket.
		tom := users.Bucket([]byte(tomBucket))
		c := tom.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			fmt.Printf("key:%v, value:%s\n", k, string(v))
		}

		// select from nested bucket.
		ken := users.Bucket([]byte(kenBucket))
		c = ken.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			fmt.Printf("key:%v, value:%s\n", k, string(v))
		}

		return nil
	})
}

処理順としては

1. DBを開く
2. RootとなるBucketを作る or 参照する
3. ネストしたBucketを作る or 参照する
4. ネストしたBucketに登録 or 削除を行う

となります。

まとめ

このような感じでBoltを使ってデータを登録・削除・参照することができました。
公式のREADMEも充実しているので、使うときには一読することをお勧めします。

今回私が作ったサンプルプログラムは以下となります。
GitHub - SrcHndWng/go-learning-boltdb-todo
GitHub - SrcHndWng/go-learning-boltdb-bucket-nest