panic() するか non-nil error が返ったらロールバック用の処理を実行するユーティリティ構造体 Guard を作った
こんな感じに使う。
package main import ( "errors" "fmt" ) func main() { fmt.Println("start") if err := f(); err != nil { fmt.Println(err.Error()) } fmt.Println("end") } func f() (err error) { fmt.Println("f() start") g := &Guard{} defer func() { err = g.Rollback(recover()) }() g.Add(func() { fmt.Println("rollback 1") }) // if true { // panic("panic!") // } g.Add(func() { fmt.Println("rollback 2") }) // oops, invoke all rollback functions in reverse order... e := errors.New("something went wrong") if e != nil { err = g.Rollback(e) err = g.Rollback(e) // this won't call rollback functions twice! return } fmt.Println("f() end") return } // Guard invokes "rollback functions" if Rollback method received non-nil value // (e.g. recover(), non-nil error). // // TODO: use mutex or something if the methods may be invoked in parallel! type Guard struct { // rollback functions rbFuncs []func() } // Rollback rolls back if v is non-nil. // // defer func() { err = g.Rollback(recover()) }() // // // or // // if e != nil { // err = g.Rollback(e) // err = g.Rollback(e) // this won't call rollback functions twice! // return // } func (g *Guard) Rollback(v interface{}) error { var err error if e, ok := v.(error); ok { err = e } else if v != nil { err = fmt.Errorf("%s", v) } if err != nil { g.RollbackForcefully() } return err } // RollbackForcefully calls rollback functions in reverse order. func (g *Guard) RollbackForcefully() { // Call rollback functions in reverse order for i := len(g.rbFuncs) - 1; i >= 0; i-- { g.rbFuncs[i]() } // Do not rollback twice g.rbFuncs = nil } // Add adds given rollback functions. func (g *Guard) Add(f func()) { g.rbFuncs = append(g.rbFuncs, f) }
そんなに行数無いので管理面倒になるしリポジトリは作ってない。 とりあえず Volt でちょうどこんな interface を実装した struct が沢山あって (インタプリタ的な DSL を実装中で、Value は全てのベースとなる型)
type Op interface { // ... Execute(ctx context.Context, args []Value) (ret Value, rollback func(), err error) }
いちいちロールバック用の関数のスライス作って error や panic() の処理して… ってするのはかなり面倒になりそうだったので、構造体にまとめた。 本来の処理に注力できて良い感じに関心の分離ができてる感じ。
ちなみに Volt ではちょっと変更したバージョンを使ってて、
Rollback()
で error を返す所を pkg/errors の Wrap()
使うように変えてる。
エラーメッセージが分かりやすくなっていい感じになった (はず…確認できてないけど)。
Go の hashicorp/go-multierror 使ったら訳わからん挙動に遭遇してしまった
基本的な Go の挙動が理解できてないのかもしれないですが、なぜ以下のコードで (2) err != nil: true
となるのかが分かりません。
誰か教えて… (g()
がメソッドなのはなるべく元のコードと似せたせいです)
追記
KoRoN さんとりんだんさんに教えてもらいました。
型なしの nil はほぼ存在しないんです。で、型が異なる同士のnilは比較しちゃいけないではなくて、比較しても常に異なってしまうから実質役に立ちません。
— MURAOKA Taro (@kaoriya) 2018年4月1日
— ドッグ (@Linda_pp) 2018年4月1日
というわけで教えてもらった記事の最初に「Goのinterfaceの実体は参照と型情報のペア」と書いてありました。 Go 公式の FAQ にも書いてあります。 こっちのが私のコードより問題と解決策が一目で分かりやすかったので皆さんもハマる前にぜひ。
MongoDB で190万件あるコレクションに $regex で LIKE 検索する時にパフォーマンスの観点で気を付けること
150万以上ある Mongo のレコードに LIKE 検索したい時こうすると雲泥の差になった (1分以上かかってたのが 50ms 以下になった)。
— tyru (@_tyru_) 2018年3月22日
①インデックスを使う ($hint 指定しないと $regex の場合は使われないっぽい)
②パターンの頭に ^ を付ける
③パターンに .* を使わない
④パターンに () を使わない
控えめに150万件以上と言ったけど実際は190万件ぐらいだった。
以下の全てのクエリは 0 件を返す。
というか今見たら $and
使う必要はなかった。
// 89790ms db.getCollection('colName').find({$and: [{name: /(あああ)/}, {status: 1}]}) // 1361ms db.getCollection('colName').find({$and: [{name: /^(あああ)/}, {status: 1}]}).hint({name: 1}) // 4ms db.getCollection('colName').find({$and: [{name: /^あああ/}, {status: 1}]}).hint({name: 1}) // 107386ms db.getCollection('colName').find({$and: [{name: /^(あああ)/}, {status: 1}]}) // 86117ms db.getCollection('colName').find({$and: [{name: /^あああ/}, {status: 1}]})
/(あああ)/
よりヒントなしの /^(あああ)/
が遅いのが謎。
ちなみに文字列検索した時は普通にインデックスが効くっぽいのですぐ返ってくる。
// 15ms db.getCollection('colName').find({$and: [{name: "あああ"}, {status: 1}]})
公式ドキュメント
- case insensitive なパターンを使うこと
^
や\A
で始めること- 先頭からのマッチの長さをなるべく短くする
- 例えば
/^a/
,/^a.*/
,/^a.*$/
だったら/^a/
が一番速い - [私見] 安易に
.*
とか書かずにどうしても必要なら non-greedy match にするといいかも (.*?
)
- 例えば
あとドキュメントに書いてないけど冒頭の結果から分かるのは
()
を使わない
もですかね。
TODO
$explain した結果も貼る。