13

私は、古典的な「フォロー」メカニズム (Twitter や Web 上の他の多くのアプリで使用されているメカニズム) を使用するアプリを作成しています。私はMongoDBを使用しています。ただし、私のシステムには違いがあります。ユーザーはユーザーのグループをフォローできます。つまり、グループをフォローすると、そのグループのメンバーであるすべてのユーザーを自動的にフォローすることになります。もちろん、ユーザーは複数のグループに所属できます。

これは私が思いついたものです:

  • ユーザー Aがユーザー B をフォローすると、ユーザー Bfollowingの ID がユーザー A のドキュメントの埋め込み配列 ( と呼ばれる) に追加されます。
  • フォローを解除するために、フォローしているユーザーのIDをfollowing配列から削除します
  • グループも同じように機能します。ユーザー Aがグループ X をフォローすると、グループ Xfollowingの ID が配列に追加されます。(実際に追加するDBRefので、接続先がユーザーかグループかがわかります。)

  • ユーザー Aがグループ Xをフォローしているかどうかを確認する必要がある場合、ユーザー Aの次の配列でグループの ID を検索するだけです。

  • ユーザー Aがユーザー Bをフォローしているかどうかを確認する必要がある場合、状況は少し複雑になります。各ユーザーのドキュメントには、ユーザーが属するすべてのグループをリストする埋め込み配列があります。そこで、$or条件を使用して、ユーザー A がユーザー B を直接フォローしているか、グループ経由でフォローしているかを確認します。このような:

    db.users.find({'$or':{'following.ref.$id':$user_id,'following.ref.$ref','users'},{'following.ref.$id':{'$in':$group_ids},'following.ref.$ref':'groups'}}})

これは問題なく機能しますが、いくつか問題があると思います。たとえば、ページネーションを含む特定のユーザーのフォロワーのリストを表示するにはどうすればよいですか? 埋め込みドキュメントで skip() と limit() を使用できません。

デザインを変更してuserfollowコレクションを使用することで、埋め込みfollowingドキュメントと同じことを行うことができます。私が試したこのアプローチの問題点は、$or以前に使用した条件では、同じユーザーを含む 2 つのグループをフォローしているユーザーが 2 回リストされることです。これを回避するには、実際に行ったグループまたは MapReduce を使用できますが、これは機能しますが、物事を単純にするためにこれを避けたいと思います。たぶん、私は箱から出して考える必要があるだけです。または、両方の試みで間違ったアプローチをとったのかもしれません。誰かがすでに同様のことをしなければならず、より良い解決策を思いつきましたか?

(これは実際には、私のこの古い質問のフォローアップです。新しい状況をよりよく説明するために、新しい質問を投稿することにしました。問題にならないことを願っています。)

4

1 に答える 1

15

ユーザーが別のユーザーをフォローするには、2 つの方法があります。直接、またはグループを介して間接的に、ユーザーはグループを直接フォローします。ユーザーとグループ間のこれらの直接的な関係を保存することから始めましょう。

{
  _id: "userA",
  followingUsers: [ "userB", "userC" ],
  followingGroups: [ "groupX", "groupY" ]
}

ここで、ユーザー A が直接的または間接的にフォローしているユーザーをすばやく見つけられるようにする必要があります。これを実現するために、ユーザー A がフォローしているグループを非正規化できます。グループ X と Y が次のように定義されているとします。

{
  _id: "groupX",
  members: [ "userC", "userD" ]
},
{
  _id: "groupY",
  members: [ "userD", "userE" ]
}

これらのグループと、ユーザー A が持つ直接的な関係に基づいて、ユーザー間のサブスクリプションを生成できます。サブスクリプションのオリジンは、各サブスクリプションと共に保存されます。サンプル データの場合、サブスクリプションは次のようになります。

// abusing exclamation mark to indicate a direct relation
{ ownerId: "userA", userId: "userB", origins: [ "!" ] },
{ ownerId: "userA", userId: "userC", origins: [ "!", "groupX" ] },
{ ownerId: "userA", userId: "userD", origins: [ "groupX", "groupY" ] },
{ ownerId: "userA", userId: "userE", origins: [ "groupY" ] }

これらのサブスクリプションは、個々のユーザーに対して map-reduce-finalize 呼び出しを使用して、非常に簡単に生成できます。グループが更新された場合、そのグループをフォローしているすべてのユーザーに対して map-reduce を再実行するだけで、サブスクリプションが再び最新になります。

マップリデュース

次の map-reduce 関数は、1 人のユーザーのサブスクリプションを生成します。

map = function () {
  ownerId = this._id;

  this.followingUsers.forEach(function (userId) {
    emit({ ownerId: ownerId, userId: userId } , { origins: [ "!" ] });
  });

  this.followingGroups.forEach(function (groupId) {
    group = db.groups.findOne({ _id: groupId });

    group.members.forEach(function (userId) {
      emit({ ownerId: ownerId, userId: userId } , { origins: [ group._id ] });
    });
  });
}

reduce = function (key, values) {
  origins = [];

  values.forEach(function (value) {
    origins = origins.concat(value.origins);
  });

  return { origins: origins };
}

finalize = function (key, value) {
  db.subscriptions.update(key, { $set: { origins: value.origins }}, true);
}

次に、クエリ (この場合はuserA.

db.users.mapReduce(map, reduce, { finalize: finalize, query: { _id: "userA" }})

いくつかのメモ:

  • そのユーザーに対して map-reduce を実行する前に、そのユーザーの以前のサブスクリプションを削除する必要があります。
  • グループを更新する場合は、そのグループをフォローしているすべてのユーザーに対して map-reduce を実行する必要があります。

MongoDB は reduce 関数の戻り値として配列をサポートしていないため、これらの map-reduce 関数は私が考えていたよりも複雑であることが判明したことに注意してください。理論的には、関数もっと単純になる可能性がありますが、MongoDB との互換性はありません。ただし、必要に応じて、このより複雑なソリューションを使用して、usersコレクション全体を 1 回の呼び出しでマップ縮小することができます。

于 2010-10-28T14:25:34.467 に答える