問題
以前に書かれた
問題1:BSONのサイズ制限
この記事の執筆時点では、BSONドキュメントは16MBに制限されています 。その制限に達すると、MongoDBは例外をスローし、コメントを追加できなくなります。最悪の場合、変更によってドキュメントのサイズが大きくなる場合は、(ユーザー)名や画像を変更することもできません。
>問題2:クエリの制限とパフォーマンス
特定の条件下では、コメント配列をクエリまたは並べ替えることは簡単にはできません。かなりコストのかかる集計が必要なものもあれば、かなり複雑なステートメントが必要なものもあります。
クエリが実行されたら、これはそれほど問題ではないと主張することもできますが、私は違うように頼みます。まず、クエリが複雑になるほど、開発者とその後のMongoDBクエリオプティマイザーの両方にとって最適化が難しくなります。データモデルとクエリを単純化し、1つのインスタンスで応答を100倍高速化することで、最高の結果が得られました。
スケーリングする場合、複雑なクエリやコストのかかるクエリに必要なリソースは、より単純なデータモデルやそれに応じたクエリと比較すると、合計するとマシン全体になる可能性があります。
問題3:保守性
最後になりましたが、コードの保守で問題が発生する可能性があります。簡単な経験則として
この文脈では、「高価」とは、お金(プロのプロジェクトの場合)と時間(趣味のプロジェクトの場合)の両方を指します。
(My!)ソリューション
非常に簡単です。データモデルを単純化します。その結果、クエリの複雑さが軽減され、(うまくいけば)高速になります。
ステップ1:ユースケースを特定する
それは私にとっては大げさな推測ですが、ここで重要なことは、一般的な方法を示すことです。ユースケースを次のように定義します:
- 特定の投稿について、ユーザーはコメントできる必要があります
- 特定の投稿について、投稿者とコメント、コメント投稿者と作成者のユーザー名と写真を表示します
- 特定のユーザーについては、名前、ユーザー名、写真を簡単に変更できる必要があります
ステップ2:それに応じてデータをモデル化する
ユーザー
まず、単純なユーザーモデルがあります
{
_id: new ObjectId(),
name: "Joe Average",
username: "HotGrrrl96",
picture: "some_link"
}
ここでは新しいことは何もありません。完全を期すために追加されました。
投稿
{
_id: new ObjectId()
title: "A post",
content: " Interesting stuff",
picture: "some_link",
created: new ISODate(),
author: {
username: "HotGrrrl96",
picture: "some_link"
}
}
投稿は以上です。ここで注意すべき点が2つあります。1つは、投稿を表示するときにすぐに必要な作成者データを保存することです。これにより、ユビキタスではないにしても、非常に一般的なユースケースのクエリが節約されます。コメントとコメント投稿者のデータをそれに応じて保存しませんか? 16MBのサイズ制限 のため 、単一のドキュメントに参照が保存されないようにしています。むしろ、参照をコメントドキュメントに保存します:
コメント
{
_id: new ObjectId(),
post: someObjectId,
created: new ISODate(),
commenter: {
username: "FooBar",
picture: "some_link"
},
comment: "Awesome!"
}
投稿と同じように、投稿を表示するために必要なすべてのデータがあります。
クエリ
私たちが今達成したことは、BSONのサイズ制限を回避し、投稿やコメントを表示できるようにするためにユーザーデータを参照する必要がないことです。これにより、多くのクエリを節約できます。しかし、ユースケースといくつかのクエリに戻りましょう
コメントの追加
今ではそれは完全に簡単です。
特定の投稿に対するすべてまたは一部のコメントを取得する
すべてのコメントについて
db.comments.find({post:objectIdOfPost})
最後の3つのコメントについて
db.comments.find({post:objectIdOfPost}).sort({created:-1}).limit(3)
したがって、投稿と、ユーザー名や写真を含むそのコメントのすべて(または一部)を表示するために、2つのクエリを実行します。以前に必要だった以上のものですが、サイズ制限を回避し、基本的にすべての投稿に対して無数のコメントを付けることができます。しかし、実際の何かに取り掛かりましょう
最新の5つの投稿と最新の3つのコメントを取得する
これは2段階のプロセスです。ただし、適切なインデックス作成(後で戻ってきます)を使用すると、これは依然として高速である必要があります(したがって、リソースを節約できます):
var posts = db.posts.find().sort({created:-1}).limit(5)
posts.forEach(
function(post) {
doSomethingWith(post);
var comments = db.comments.find({"post":post._id}).sort("created":-1).limit(3);
doSomethingElseWith(comments);
}
)
特定のユーザーのすべての投稿を最新から古い順に並べ替えて、コメントを取得します
var posts = db.posts.find({"author.username": "HotGrrrl96"},{_id:1}).sort({"created":-1});
var postIds = [];
posts.forEach(
function(post){
postIds.push(post._id);
}
)
var comments = db.comments.find({post: {$in: postIds}}).sort({post:1, created:-1});
ここではクエリが2つしかないことに注意してください。投稿とそれぞれのコメントを「手動で」関連付ける必要がありますが、それは非常に簡単なはずです。
ユーザー名を変更する
これはおそらく、実行されるまれなユースケースです。ただし、上記のデータモデルではそれほど複雑ではありません
まず、ユーザードキュメントを変更します
db.users.update(
{ username: "HotGrrrl96"},
{
$set: { username: "Joe Cool"},
$push: {oldUsernames: "HotGrrrl96" }
},
{
writeConcern: {w: "majority"}
}
);
古いユーザー名を対応する配列にプッシュします。これは、以下の操作で問題が発生した場合のセキュリティ対策です。さらに、データの耐久性を確保するために、書き込みの懸念をかなり高いレベルに設定しました。
db.posts.update(
{ "author.username": "HotGrrrl96"},
{ $set:{ "author.username": "Joe Cool"} },
{
multi:true,
writeConcern: {w:"majority"}
}
)
ここでは特別なことは何もありません。コメントの更新ステートメントはほとんど同じように見えます。これらのクエリには時間がかかりますが、実行されることはめったにありません。
インデックス
経験則として、MongoDBはクエリごとに1つのインデックスしか使用できないと言えます。インデックスの交差があるため、これは完全には当てはまりませんが、対処は簡単です。もう1つは、複合インデックスの個々のフィールドを個別に使用できることです。したがって、インデックスを最適化する簡単な方法は、インデックスを使用する操作で使用されるフィールドが最も多いクエリを見つけて、それらの複合インデックスを作成することです。クエリでの発生順序が重要であることに注意してください。それでは、先に進みましょう。
投稿
db.posts.createIndex({"author.username":1,"created":-1})
コメント
db.comments.createIndex({"post":1, "created":-1})
結論
確かに、投稿ごとに完全に埋め込まれたドキュメントは、それをロードする最も速い方法であり、コメントです。ただし、拡張性は低く、処理に必要な複雑なクエリの性質上、このパフォーマンス上の利点を活用または排除することもできます。
上記のソリューションを使用すると、基本的に無制限のスケーラビリティと、データを処理するためのはるかに簡単な方法に対して、ある程度の速度(もし!)と引き換えになります。
Hth。