TT Lab
はじめる
学ぶ 学習パス コース

MongoDB — ドキュメントDBの判断

遅い理由はたいていインデックスだ

TT Labで続きを見る

一言でいうと

インデックスがなければ、ドキュメントDBもすべてを走査します。COLLSCANと表示されます。RDBのSeq Scanとまったく同じもので、対策も同じです。

なぜ必要なのか: 遅い原因はたいてい1つ

「MongoDBが遅い」という報告の大半は、データベースではなくインデックスのないクエリが原因です。ドキュメントが1万件のときは誰も気づきません。100万件になると同じコードが急に遅くなり、そのとき人はデータベースを疑います。

確認する方法はRDBと同じです。実行計画を尋ねます。

db.items.find({ name: "빨간 배낭" }).explain()

実行計画の中で見る語は2つだけです。COLLSCANなら全部を走査したということで、IXSCANならインデックスを使ったということです。

数字も一緒に見る

ステージ名だけでは半分です。explain('executionStats')を使うと、実際に何件を走査したかがわかります。

値 意味
nReturned 実際に返したドキュメント数
totalDocsExamined それを探すために開いたドキュメント数

この2つの比率が、インデックスの成績表です。1件を返すために100万件を開いたなら、インデックスがないか、間違った作り方になっています。逆に2つが近ければ、インデックスがうまく効いています。

複合インデックスは順序がすべて

インデックスを作ると決めたら、次の問いはどの順序で組み合わせるかです。ドキュメントDBの複合インデックスも値でソートされた構造なので、先頭のフィールドから使うことができ、そのルールが何を使えて何を使えないかを決めます。

{ status: 1, createdAt: -1 }で作ったインデックスは、次のように使われます。

statusの昇順、次にcreatedAtの降順でソートされた複合インデックスを、3通りのクエリで走査する図。statusで検索すると、一致する行が連続しているため連続した区間を1つだけ走査し、ソートを付けてもすでに降順なので無料になります。createdAtだけで検索すると、一致する行が散らばっていて連続した区間がありません

そこから、実務での順序のルールが導かれます。等価で絞り込むフィールドを前に、範囲で絞り込むかソートに使うフィールドを後ろに置きます。範囲条件が掛かったフィールドより後ろのフィールドは、探索には使えず、絞り込みにだけ使われます。

ソートがインデックスを使えないと、静かにコストが高くなります。ドキュメントDBはメモリ内でソートしますが、その量に上限があり、超えるとエラーで失敗するか一時ファイルを使います。結果が少なくても、ソート前の候補が多いと引っかかる点が、特に人を驚かせます。実行計画にソートのステージが別に見えたら、その順序をインデックスに移せるかを確認します。

配列フィールドに張るインデックスは性質が違います。配列の要素ごとにエントリが1つずつ作られるため、ドキュメント1つがインデックスに何度も入ります。要素が数百個ある配列にインデックスを張ると、その分だけ大きくなり、書き込みも遅くなります。そして配列フィールドを2つ以上、1つのインデックスにまとめることはできません。組み合わせの数が掛け算で爆発するからです。

最後に、必要な値がすべてインデックスの中にあれば、ドキュメントをまったく開きません。先ほど見たtotalDocsExaminedが0になるのがこの場合で、一覧画面のように一部のフィールドだけを表示するクエリでは、特に価値が大きくなります。

現場では

インデックスを増やすことが常に答えではありません。インデックスは書き込みを遅くし、容量を消費します。ドキュメントを1件入れるたびに、すべてのインデックスが更新されます。

だから順序があります。まず実行計画を読んで何が遅いのかを確認し、そのうえで、そのクエリ1つのためのインデックスを作ります。「念のため」に作ったインデックスは、書き込みコストだけを残して、どのクエリも速くしない場合が多いのです。

この順序を守っているかどうかが、SQLの最適化経験を問う面接で分かれるところです。