🍃
実測MongoDB
はじめに
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で生成する試みをしています。
大規模なデータベースで作業をしたことがなく、インデックスは自分にとってある種の信仰だったのですが、実験主義的なアプローチをとったことで理性のものとなった気がします。
ありがとうございました。
X (Twitter): @koyo_k0
Discussion