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

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

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