← 戻る

buntdb について

2021-03-18

buntdb とは

tidwall/buntdbは Pure Go で書かれた KVS ライブラリ。 インメモリで処理を行うが、ディスクに永続化することもできる。またトランザクションや Index がある。 Go からは mattn/go-sqlite3を使えば SQLite が使えるが、 SQLite は cgo が必要なので、環境によってはややビルド方法などで気を使う必要がある。 そのため Pure Go で使える DB ライブラリがほしかった。 buntdb は KVS なので SQLite ほど柔軟にクエリや集計できるわけではないが、用途によっては使えるかと思った。

シンプルな使い方

https://play.golang.org/p/wSHTfPSmmHp

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

Index を使う

https://play.golang.org/p/tchBZ89nFwy

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

Multi Value Index を使う

https://play.golang.org/p/eEMPdWVoy6X

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}