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 した結果も貼る。