これは境界線であるという他の回答の一般的な概念に同意します 関係の問題。
MongoDBデータモデルの鍵は書き込みの重さですが、これはこのユースケースでは扱いにくい場合があります。これは主に、ユーザーをアイテムに直接リンクする場合に必要となる簿記のためです(グループへの変更の後にロットが続きます)。のユーザーには膨大な数の書き込みが発生するため、これを行うにはワーカーが必要です。
ここでは、読み取りが多いモデルが適用できないのか、それとも時期尚早の最適化を行っているのかを調べてみましょう。
リードヘビーアプローチ
主な懸念事項は、次のユースケースです。
実際のパフォーマンスの問題は、特定のアイテムについてユーザーがフォローしているすべてのグループを取得したい場合です[...]その場合、ユーザーがフォローしているすべてのグループを検索し、そこからすべてを検索する必要があるためです。 group_id
$in
のitem_groups とアイテムID。
これを分析しましょう:
-
ユーザーがフォローしているすべてのグループを取得する
これは単純なクエリです:
db.followers.find({userId : userId})
。userId
のインデックスが必要になります これにより、この操作の実行時間はO(log n)になり、nが大きい場合でも非常に高速になります。 -
そこから、group_id
$in
を持つすべてのitem_groupsを検索します とアイテムIDさて、これは難しい部分です。アイテムが多数のグループの一部になる可能性は低いと少しの間仮定しましょう。次に、複合インデックス
{ itemId, groupId }
最初の基準で候補セットを劇的に減らすことができるため、最も効果的です-アイテムが800グループのみで共有され、ユーザーが220グループをフォローしている場合、mongodbはこれらの共通部分を見つけるだけで済みます。これは、両方が比較的簡単であるためです。セットは小さいです。
ただし、これよりも深く掘り下げる必要があります:
データの構造はおそらく 複雑ネットワークのそれ 。複雑なネットワークにはさまざまな種類がありますが、フォロワーグラフがほぼスケールフリーであると想定するのは理にかなっています。これも、ほとんど最悪のケースです。スケールフリーネットワークでは、非常に少数のノード(有名人、スーパーボウル、ウィキペディア)が多くの「注意」を引き付けます(つまり、多くの接続があります)が、はるかに多くのノードが同じ量の注意を引くのに問題があります組み合わせ 。
小さなノードは心配する必要はありません 、データベースへのラウンドトリップを含む上記のクエリは、2ミリ秒の範囲です。 数千万の接続と5GBを超えるデータを含むデータセット上の開発マシンで。データセットは巨大ではありませんが、どのテクノロジーを選択しても、インデックスはどのような場合でもRAMに存在する必要があるため(ネットワークのデータの局所性と分離性は一般に不十分です)、設定された交差サイズはRAMにバインドされます。定義上小さい。言い換えれば、この体制はハードウェアのボトルネックによって支配されています。
スーパーノードはどうですか でも?
それは当て推量であり、ネットワークモデルに非常に興味があるので、データモデルに基づいて劇的に簡略化されたネットワークツールを自由に実装して、いくつかの測定を行いました。 (申し訳ありませんが、C#ですが、適切に構造化されたネットワークを生成することは、私が最も流暢な言語では十分に困難です...)
スーパーノードにクエリを実行すると、7msトップの範囲の結果が得られます。 (これは、1.3GBデータベースの1200万エントリにあり、最大のグループには133,000アイテムが含まれています および143のグループをフォローしているユーザー。)
仮定 このコードでは、ユーザーが従うグループの数はそれほど多くありませんが、ここではそれが妥当なようです。そうでない場合は、書き込みが多いアプローチを選択します。
コードを自由に試してみてください。残念ながら、数GBを超えるデータでこれを試してみたい場合は、少し最適化する必要があります。これは、単に最適化されておらず、あちこちで非常に非効率的な計算が行われるためです(特に、ベータ加重ランダムシャッフルは改善される可能性があります)。 。
言い換えると、読み取りが多いアプローチのパフォーマンスについては心配しません 。 多くの場合、問題はユーザー数が増えるほどではありませんが、ユーザーが予期しない方法でシステムを使用していることです。
書き込みヘビーアプローチ
別のアプローチは、おそらくリンクの順序を逆にすることです:
UserItemLinker
{
userId,
itemId,
groupIds[] // for faster retrieval of the linker. It's unlikely that this grows large
}
これはおそらく最もスケーラブルなデータモデルですが、シャーディングが重要な要件である大量のデータについて話しているのでない限り、私はそれを選びません。ここでの主な違いは、シャードキーの一部としてuserIdを使用することで、データを効率的にコンパートメント化できることです。これは、クエリを並列化し、効率的にシャーディングし、マルチデータセンターシナリオでのデータの局所性を向上させるのに役立ちます。
これは、より精巧なバージョンのテストベッドでテストできますが、まだ時間がわかりませんでした。率直に言って、ほとんどのアプリケーションではやり過ぎだと思います。