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

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

インデックスは順序と範囲で設計する

TT Labで続きを見る

一言でいうと

インデックスは「どのフィールドに張るか」ではなく、どの順序で、何を含めるかという問題です。同じ3つのフィールドでも、順序によって 走査するキーが数千倍変わり、含める範囲(部分・TTL)によってサイズと寿命が変わります。判断は実行計画の数字で行います。

なぜ必要だったのか — インデックスがあるのに遅い

「インデックスを張ったのになぜ遅いのか」という報告の大半は、インデックスがない場合ではなく、合っていない場合です。一覧画面の よくあるクエリを見てみましょう。

db.orders.find({status: "shipped", amount: {$gte: 150000}}).sort({createdAt: -1}).limit(20)

クエリに出てくる順序どおりに{status, amount, createdAt}インデックスを作ると、statusで絞り込んだ後、amountの範囲を走査します。ところが、範囲を 走査した結果はcreatedAtの順序ではありません。そのため計画にSORTステージが生まれ、条件に合う数千件をすべてメモリに載せて ソートしてから20件を切り出します。limit 20は何の助けにもなりません。

どう動くのか — ESR

複合インデックスは、先頭のフィールドから値でソートされた1つのリストです。そこから順序のルールが導かれます。

順序 フィールド 理由
E 等価(status: "shipped") 1つの区間に絞り込みます。その区間の中は次のフィールドの順序でソートされています
S ソート(createdAt: -1) 等価の区間の中ですでにソートされた順序で進めば、ソートは無料です
R 範囲(amount >= …) 進みながらキーですぐに絞り込みます。20件が埋まったら止まります

{status, createdAt, amount}で作ると、計画はLIMIT>FETCH>IXSCANになり、走査するキーは20件を埋めるのに必要な分まで減ります。 範囲フィールドの後ろに置いたフィールドは、探索ではなく絞り込みにだけ使われます — これがERSとESRの違いのすべてです。

さらに3つあります。

計画を読む順序

実行計画は木構造です。外側(winningPlan)が最後に行う処理で、inputStageをたどって下りると最初に行う処理が出てきます。 LIMIT>FETCH>IXSCANは「インデックスを走査して → ドキュメントを取得して → 20件で切る」で、SORT>COLLSCANは「すべて走査して → メモリで ソートする」です。読むときは3つの数字を一緒に見ます。

比較 意味
totalKeysExamined ≫ nReturned インデックスを使ったが区間が広い — 順序か正規表現を疑います
totalDocsExamined ≫ nReturned ドキュメントを取得してから絞り込んでいる — 絞り込むフィールドがインデックスにありません
totalDocsExamined = 0 インデックスだけで答えた(カバード)

プランナーは候補の計画を少しずつ一緒に実行してみて、先に多くの結果を出した方を選び、その選択をクエリの形ごとにキャッシュします。負けた計画は rejectedPlansに残ります。そのため、インデックスを新しく作るとキャッシュがクリアされ、同じクエリの計画が変わることがあります — 直した後にもう一度 測る理由です。特定のインデックスの成績を個別に測りたいときはhintでそのインデックスを強制し、インデックスなしで測るには{$natural: 1}を指定します。

現場で出会う姿

次のラボですること

注文60,000件の上で、同じ一覧クエリをインデックスなし・ERS・ESRで測って数字を比較し、カバードクエリ・部分インデックス・TTL・正規表現の 接頭辞を順に測ります。最後に、3つの画面が遅くなったインシデントを受け取り、同じ症状の裏にある異なる原因を切り分けます。