【问题标题】:Aggregation Query Optimization Mongodb聚合查询优化MongoDB
【发布时间】:2021-09-03 06:33:02
【问题描述】:

用户架构

我一直在构建一个社交媒体应用程序,我必须编写一个返回用户用户的查询。用户的架构如下所示。

const userSchema = Schema(
  {
    email: {
      type: String,
      unique: true,
      required: [true, "Email is required"],
      index: true,
    },
    active: {
      type: Boolean,
      default: true,
    },
    phone: {
      type: String,
      unique: true,
      required: [true, "Phone is required"],
      index: true,
    },
    name: {
      required: true,
      type: String,
      required: [true, "Name is required"],
    },
    bio: {
      type: String,
    },
    is_admin: {
      type: Boolean,
      index: true,
      default: false,
    },
    is_merchant: {
      type: Boolean,
      index: true,
      default: false,
    },
    password: {
      type: String,
      required: [true, "Password is required"],
    },
    profile_picture: {
      type: String,
    },
    followers: [
      // meaning who has followed me
      {
        type: Types.ObjectId,
        ref: "user",
        required: false,
      },
    ],
    followings: [
      // meaning all of them who I followed
      {
        type: Types.ObjectId,
        ref: "user",
        required: false,
      },
    ],
  },
  {
    timestamps: { createdAt: "created_at", updatedAt: "updated_at" },
    toObject: {
      transform: function (doc, user) {
        delete user.password;
      },
    },
    toJSON: {
      transform: function (doc, user) {
        delete user.password;
      },
    },
  }
);

关注/关注实施

我已经使用如下所示的逻辑实现了跟随/跟随。每次用户关注另一个用户。它将执行 2 个查询。一个是使用findOneAndUpdate({push:followee._id}) 更新关注者关注者部分,第二个查询是更新关注者用户部分。

查询响应模式

我编写了一个查询,该查询应该返回响应,并在每个用户上附加以下响应

{
  doesViewerFollowsUser: boolean // implying if person we are viewing profile of follows us 
  doesUserFollowsViewer: boolean // implying if person we are viewing profile of follows us
}

实际查询

查询必须如下所示


userModel
    .aggregate([
      {
        $match: {
          _id: {
            $in: [new Types.ObjectId(userId), new Types.ObjectId(viewerId)],
          },
        },
      },

      {
        $addFields: {
          order: {
            $cond: [
              {
                $eq: ["$_id", new Types.ObjectId(viewerId)], // testing for viewer
              },
              2,
              1,
            ],
          },
        },
      },
      {
        $group: {
          _id: 0,
          subjectFollowings: {
            $first: "$followings",
          },
          viewerFollowings: {
            $last: "$followings",
          },
          viewerFollowers: {
            $last: "$followers",
          },
        },
      },
      {
        $lookup: {
          from: "users",
          localField: "subjectFollowings",
          foreignField: "_id",
          as: "subjectFollowings",
        },
      },
      {
        $project: {
          subjectFollowings: {
            $map: {
              input: "$subjectFollowings",
              as: "user",
              in: {
                $mergeObjects: [
                  "$$user",
                  {
                    doesViewerFollowsUser: {
                      $cond: [
                        {
                          $in: ["$$user._id", "$viewerFollowers"],
                        },
                        true,
                        false,
                      ],
                    },
                  },
                  {
                    doesUserFollowsViewer: {
                      $cond: [
                        {
                          $in: ["$$user._id", "$viewerFollowings"],
                        },
                        true,
                        false,
                      ],
                    },
                  },
                ],
              },
            },
          },
        },
      },
      {
        $project: {
          "subjectFollowings.followings": 0,
          "subjectFollowings.followers": 0,
          "subjectFollowings.bio": 0,
          "subjectFollowings.password": 0,
          "subjectFollowings.is_admin": 0,
          "subjectFollowings.is_merchant": 0,
          "subjectFollowings.email": 0,
          "subjectFollowings.phone": 0,
          "subjectFollowings.created_at": 0,
          "subjectFollowings.updated_at": 0,
          "subjectFollowings.__v": 0,
        },
      },
    ])

问题

我认为当前的查询没有那么大。此查询的最坏情况复杂度达到 0(n^2)(大约)。所以,请帮我优化这个查询。

【问题讨论】:

    标签: javascript node.js mongodb social-networking mern


    【解决方案1】:

    问题在于您的数据建模。您不应该将关注者/关注者存储在数组中,因为:

    1. Mongodb 对每个文档都有 16mb 的硬限制,这意味着您可以将有限的数据存储在单个文档中
    2. 数组查找将花费线性时间;数组越大,查询它所需的时间就越长。

    你可以做的是有一个这样的用户关系集合:

    follower: user id
    followee: user id
    

    然后,您可以在 follower-followee 上创建复合索引并有效地查询以检查谁关注了谁。您还可以在此处启用时间戳。 为了获取用户的所有关注者,只需在关注者键上创建一个索引,这也将很快解决

    【讨论】:

    • 我最近想到了,但你能告诉我更多关于如何在模型中以最少的数据库调用实现{ doesViewerFollowsUser: boolean // implying if person we are viewing profile of follows us doesUserFollowsViewer: boolean // implying if person we are viewing profile of follows us }
    • 从技术上讲,您不需要在新集合文档中添加这些字段。您可以在集合中查找这两个用户的文档。如果您决定添加这些字段那么当有人关注/取消关注某人时,您必须确保相应地更新这些字段..您只需要执行额外的更新操作而不是读取操作..但我认为使用复合索引它应该足够快,您应该不需要这些字段..如果您认为用户很少。互相关注/取消关注您的方法可能会更好.. 取决于场景
    • 我不是要添加这些字段,而是要为某个端点计算它。那么你有什么建议吗?我应该使用单个聚合 $map 还是应该在许多数据库调用中这样做
    • 哦,好吧..我以为你试图添加这些字段..为了计算这些,这很简单..假设你想检查用户 a 跟随用户 b,那么你可以检查此集合中是否存在 { follower: a, followee: b }。我希望这是有道理的。您也可以将其添加为聚合阶段,以便在单个查询中获取所有内容
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-18
    • 2010-12-18
    • 2021-05-04
    • 2018-02-18
    • 2010-10-23
    • 2020-08-06
    相关资源
    最近更新 更多