🍃

実測MongoDB

2024/10/25に公開

はじめに

MongoDBのパフォーマンスをいくつかの条件で比較しながら測定していきます。
実行環境はNode.js + MongoDBです。

記事の最後にリポジトリのへリンクがあるため、実装の詳細はそちらを参照してください。

Insert

100,000件のドキュメントをinsertし、それにかかった時間を検証します。

条件

  • ループでのinsert
  • bulkでのinsert
  • インデックスが設定されているコレクションへのinsert
  • 100個のフィールドを持つドキュメントのinsert
  • 100階層にネストされたドキュメントのinsert
// ループの例
for (let i = 0; i < 100000; i++) {
    await collection.insertOne({
        name: `User${i}`,
        age: Math.floor(Math.random() * 100),
        country: `Country${i}`,
        favorites: [`Favorite${i}`],
    });
}
// 100個のフィールドを持つドキュメントの例

// 100個のランダムなキー・値をもつオブジェクトを生成する
const randomObj = generateRandomObject(100);
const arrRandom = Array.from({ length: 100000 }, () => ({ ...randomObj }));

for (let i = 0; i < INSERTION_COUNT; i++) {
    await collection.insertOne(arrRandom[i]);
}

結果

種別 実行時間(ms)
ループ 13,966
bulk 590
インデックスあり 28,916
多数のフィールド 18,019
多重ネスト 19,520

bulk以外はNode.js側のオーバーヘッドが発生するため、あくまで相対的な差として見るのがよさそうです。
フィールド数・ネストの深さにもある程度相関があることがわかります。

Find

1,000,000件のデータを挿入して、そこに対するクエリのパフォーマンスをexplain("executionStats")で検証します。

let bulk = collection.initializeUnorderedBulkOp();
for (let i = 0; i < 1000000; i++) {
    bulk.insert({
    name: `User${i}`,
    age: Math.floor(Math.random() * 100),
    country: Math.random() > 0.5 ? "Japan" : "USA",
    favorites: [`Favorite${i}`],
    });
}
await bulk.execute();

条件

  • findとindexの有無
    • find({ age: 30 })
    • createIndex({ age: 1 }) & find({ age: 30 })
  • 複合的なクエリとindexの有無
    • find({ age: 30, country: "Japan" })
    • createIndex({ age: 1 }) & find({ age: 30, country: "Japan" })
    • createIndex({ country: 1 }) & find({ age: 30, country: "Japan" })
    • createIndex({ age: 1, country: 1 }) & find({ age: 30, country: "Japan" })
  • 範囲とindexの有無
    • find({ age: { $gte: 20, $lte: 30 } })
    • createIndex({ age: 1 }) & find({ age: { $gte: 20, $lte: 30 } })
  • ソートとindexの有無
    • find({ age: { $gte: 20, $lte: 30 } }) & sort({ country: 1 })
    • createIndex({ country: 1 }) & find({ age: { $gte: 20, $lte: 30 } }) & sort({ country: 1 })
    • createIndex({ age: 1 }) & find({ age: { $gte: 20, $lte: 30 } }) & sort({ country: 1 })
    • createIndex({ age: 1, country: 1 }) & find({ age: { $gte: 20, $lte: 30 } }) & sort({ country: 1 })
// 複合インデックス・複合クエリの例
await collection.createIndex({ age: 1, country: 1 });
result = await collection
    .find({ age: 30, country: "Japan" })
    .explain("executionStats");

結果

クエリ内容 インデックス 実行時間(ms) 検査したドキュメント数
age = 30 なし 210 1,000,000
age = 30 age 8 10,021
age = 30 AND country = 'Japan' なし 177 1,000,000
age = 30 AND country = 'Japan' age 9 10,021
age = 30 AND country = 'Japan' country 219 500,149
age = 30 AND country = 'Japan' (age, country) 6 4,968
20 <= age <= 30 なし 194 1,000,000
20 <= age <= 30 age 72 110,021
20 <= age <= 30 ORDER BY country なし 227 1,000,000
20 <= age <= 30 ORDER BY country country 459 1,000,000
20 <= age <= 30 ORDER BY country age 117 110,021
20 <= age <= 30 ORDER BY country (age, country) 180 110,021

MongoDBのインデックスはB-tree構造を採用しており、インデックスの有無による計算量の差はO(N)とO(log N)となります。

ORDER BYするキーにインデックスを貼っても特に効果は無いようです。

おわりに

Docker環境で試せるリポジトリは以下です。
なお、コードの大部分はGitHub Copilot workspaceで生成する試みをしています。
https://github.com/koyo221/mongo-performance-test

大規模なデータベースで作業をしたことがなく、インデックスは自分にとってある種の信仰だったのですが、実験主義的なアプローチをとったことで理性のものとなった気がします。

ありがとうございました。
X (Twitter): @koyo_k0

Discussion