BoltでGoroutineを使って登録する場合のDB.Batch()について
はじめに
昨日に引き続きにBoltついてです。
GitHub - boltdb/bolt: An embedded key/value database for Go.
今回はGoroutineの中でBoltに登録処理を行うケースを想定し、DB.Batch()を検証してみました。
DB.Batch()
公式のドキュメントは以下となります。
https://github.com/boltdb/bolt#batch-read-write-transactions
DB.Batch()を使うと、DB.Update()が一件毎にディスクに書き込むのに対して、まとめて書き込むことでオーバーヘッドを無くすことができるようです。
ただし注意点もあり、複数あるGoroutineの中でエラーが発生しても、別のGoroutineは実行されてしまいます。
サンプルソース
DB.Batch()を使ったサンプルソースを書いてみました。
特に長くはないので全行載せます。
package main import ( "fmt" "log" "sync" "github.com/boltdb/bolt" ) const dbFile = "sample.db" const bucket = "todos" func main() { fmt.Println("main start...") db, err := bolt.Open(dbFile, 0600, nil) if err != nil { log.Fatal(err) } defer db.Close() batch(db) reference(db) fmt.Println("main end...") } func batch(db *bolt.DB) { var wg sync.WaitGroup ids := []string{"1", "2", "3", "4", "5"} for _, id := range ids { wg.Add(1) go func(itemID string) { fmt.Printf("goroutine called. itemId = %s\n", itemID) err := db.Batch(func(tx *bolt.Tx) error { b, err := tx.CreateBucketIfNotExists([]byte(bucket)) if err != nil { return err } // comment to raise error // if itemID == "3" { // return errors.New(fmt.Sprintf("Error! itemId = %s", itemID)) // } return b.Put([]byte(itemID), []byte(fmt.Sprintf("todo %s", itemID))) }) if err != nil { fmt.Printf("Batch() error : %v\n", err) } wg.Done() }(id) } wg.Wait() } func reference(db *bolt.DB) error { return 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() { fmt.Printf("key:%v, value:%s\n", k, string(v)) } return nil }) }
batch()が今回取り扱うDB.Batch()で登録処理を行なっている関数です。id 1〜5までをループし、Goroutineにて登録しています。
登録した後、reference()で参照して出力しています。
上記の状態で実行すると以下のように出力されます。
main start... goroutine called. itemId = 5 goroutine called. itemId = 2 goroutine called. itemId = 1 goroutine called. itemId = 3 goroutine called. itemId = 4 key:[49], value:todo 1 key:[50], value:todo 2 key:[51], value:todo 3 key:[52], value:todo 4 key:[53], value:todo 5 main end...
次にbatch()内のエラーを起こす以下のコメントアウトを外して実行してみます。
// comment to raise error if itemID == "3" { return errors.New(fmt.Sprintf("Error! itemId = %s", itemID)) }
idが3の場合のみエラーを起こすようにしてみました。
この状態で実行すると、以下のようになります。
main start... goroutine called. itemId = 5 goroutine called. itemId = 2 goroutine called. itemId = 3 goroutine called. itemId = 4 goroutine called. itemId = 1 Batch() error : Error! itemId = 3 key:[49], value:todo 1 key:[50], value:todo 2 key:[52], value:todo 4 key:[53], value:todo 5 main end...
idが3の場合にエラーが発生ましたが、他のGoutineは実行されて1・2・4・5は登録されたことが分かるかと思います。
今回私が作ったサンプルは以下になります。
GitHub - SrcHndWng/go-learning-boltdb-batch