📙
buntdbを使ってみた
buntdb とは
tidwall/buntdbは Pure Go で書かれた KVS ライブラリ。
インメモリで処理を行うが、ディスクに永続化することもできる。またトランザクションや Index がある。
Go からは mattn/go-sqlite3を使えば SQLite が使えるが、SQLite の利用には cgo が必要なので、環境によってはややビルド方法などで気を使う必要がある。
そのため Pure Go で使える、軽量に組み込める DB ライブラリがほしかった。
buntdb は KVS なので SQLite ほど柔軟にクエリや集計ができるわけではないが、用途によっては使えるかと思った。
シンプルな使い方
package main
import (
"fmt"
"log"
"github.com/tidwall/buntdb"
)
func main() {
db, err := buntdb.Open(":memory:")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 読み書きができるトランザクションを生成
db.Update(func(tx *buntdb.Tx) error {
// レコードの登録
tx.Set("key3", "value3", nil)
tx.Set("key1", "value1", nil)
tx.Set("key2", "value2", nil)
return nil // エラーが返らない場合コミットされる
})
// 読み込みのみのトランザクションを生成
db.View(func(tx *buntdb.Tx) error {
// 全レコードを昇順でイテレート
tx.Ascend("", func(key, value string) bool {
fmt.Println(key, value)
return true
})
return nil
})
}
結果
key1 value1
key2 value2
key3 value3
- トランザクションは、読み書き, 読み込みのみ, どちらの場合も関数として表現する
- Key も Value も string 型
Index を使う
package main
import (
"fmt"
"log"
"github.com/tidwall/buntdb"
)
func main() {
db, err := buntdb.Open(":memory:")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// keyに`key:`というprefixがつくレコードを対象に、valueを文字列としてソートする`indexstr`という名前のインデックスを生成
db.CreateIndex("indexstr", "key:*", buntdb.IndexString)
// keyに`key:`というprefixがつくレコードを対象に、valueを数値としてソートする`indexint`という名前のインデックスを生成
db.CreateIndex("indexint", "key:*", buntdb.IndexInt)
db.Update(func(tx *buntdb.Tx) error {
tx.Set("key:1", "13", nil)
tx.Set("key:2", "2", nil)
tx.Set("key:3", "9", nil)
return nil // エラーが起きていなければ自動でコミットされる
})
db.View(func(tx *buntdb.Tx) error {
fmt.Println("iterate over indexstr")
tx.Ascend("indexstr", func(key, value string) bool {
fmt.Println(key, value)
return true
})
fmt.Println("iterate over indexint")
tx.Ascend("indexint", func(key, value string) bool {
fmt.Println(key, value)
return true
})
return nil
})
}
結果
iterate over indexstr
key:1 13
key:2 2
key:3 9
iterate over indexint
key:2 2
key:3 9
key:1 13
- Index の対象とするレコードは、Key のパターンで指定する
- パターン文字列には*(任意の文字列)と?(任意の 1 文字)が使える https://pkg.go.dev/github.com/tidwall/match#Match
Multi Value Index を使う
package main
import (
"fmt"
"log"
"github.com/tidwall/buntdb"
)
func main() {
db, err := buntdb.Open(":memory:")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 全レコードを対象に、valueをJSONとみなしてi1, i2の値の順にソートする`index`という名前のインデックスを生成
db.CreateIndex("index", "*", buntdb.IndexJSON("i1"), buntdb.IndexJSON("i2"))
db.Update(func(tx *buntdb.Tx) error {
var err error
records := []struct {
Key string
Value string
}{
// 値が文字列
{Key: "s12", Value: `{"i1":"1", "i2":"2"}`},
{Key: "s21", Value: `{"i1":"2", "i2":"1"}`},
{Key: "s22", Value: `{"i1":"2", "i2":"2"}`},
{Key: "s322", Value: `{"i1":"3", "i2":"22"}`},
{Key: "s32", Value: `{"i1":"3", "i2":"2"}`},
{Key: "s11", Value: `{"i1":"1", "i2":"1"}`},
{Key: "s31", Value: `{"i1":"3", "i2":"1"}`},
{Key: "s33", Value: `{"i1":"3", "i2":"3"}`},
// 値が数値型
{Key: "n12", Value: `{"i1":1, "i2":2}`},
{Key: "n21", Value: `{"i1":2, "i2":1}`},
{Key: "n22", Value: `{"i1":2, "i2":2}`},
{Key: "n322", Value: `{"i1":3, "i2":22}`},
{Key: "n32", Value: `{"i1":3, "i2":2}`},
{Key: "n11", Value: `{"i1":1, "i2":1}`},
{Key: "n31", Value: `{"i1":3, "i2":1}`},
{Key: "n33", Value: `{"i1":3, "i2":3}`},
}
for _, record := range records {
_, _, err = tx.Set(record.Key, record.Value, nil)
}
return err
})
db.View(func(tx *buntdb.Tx) error {
// indexを昇順にイテレート
fmt.Println("iterate over index")
tx.Ascend("index", func(key, value string) bool {
fmt.Println(key, value)
return true
})
// indexの値が `{"i1":3, "i2":1}` と同じレコードをイテレート
fmt.Println("iterate over index equal to n31")
tx.AscendEqual("index", `{"i1":3, "i2":1}`, func(key, value string) bool {
fmt.Println(key, value)
return true
})
// indexの値が `{"i1":3, "i2":1}` より大きいレコードをイテレート
fmt.Println("iterate over index greater or equal to n31")
tx.AscendGreaterOrEqual("index", `{"i1":3, "i2":1}`, func(key, value string) bool {
fmt.Println(key, value)
return true
})
// i1 = 3のレコードをi2の値で昇順にイテレート
fmt.Println("iterate over index where i1 = 3")
tx.AscendRange("index", `{"i1":3, "i2":0}`, `{"i1":4, "i2":0}`, func(key, value string) bool {
fmt.Println(key, value)
return true
})
return nil
})
}
結果
iterate over index
n11 {"i1":1, "i2":1}
n12 {"i1":1, "i2":2}
n21 {"i1":2, "i2":1}
n22 {"i1":2, "i2":2}
n31 {"i1":3, "i2":1}
n32 {"i1":3, "i2":2}
n33 {"i1":3, "i2":3}
n322 {"i1":3, "i2":22}
s11 {"i1":"1", "i2":"1"}
s12 {"i1":"1", "i2":"2"}
s21 {"i1":"2", "i2":"1"}
s22 {"i1":"2", "i2":"2"}
s31 {"i1":"3", "i2":"1"}
s32 {"i1":"3", "i2":"2"}
s322 {"i1":"3", "i2":"22"}
s33 {"i1":"3", "i2":"3"}
iterate over index equal to n31
n31 {"i1":3, "i2":1}
iterate over index greater or equal to n31
n31 {"i1":3, "i2":1}
n32 {"i1":3, "i2":2}
n33 {"i1":3, "i2":3}
n322 {"i1":3, "i2":22}
s11 {"i1":"1", "i2":"1"}
s12 {"i1":"1", "i2":"2"}
s21 {"i1":"2", "i2":"1"}
s22 {"i1":"2", "i2":"2"}
s31 {"i1":"3", "i2":"1"}
s32 {"i1":"3", "i2":"2"}
s322 {"i1":"3", "i2":"22"}
s33 {"i1":"3", "i2":"3"}
iterate over index where i1 = 3
n31 {"i1":3, "i2":1}
n32 {"i1":3, "i2":2}
n33 {"i1":3, "i2":3}
n322 {"i1":3, "i2":22}
- buntdb.IndexJSON()を使うと JSON の特定のフィールドに対しての Index を作れる
- buntdb.CreateIndex()の引数に複数の Index 関数を設定できる
- buntdb.IndexJSON()の順は JSON 中の型による。文字列型であれば文字列として、数値型であれば数値として並ぶ
Discussion