V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐工具
RoboMongo
推荐书目
50 Tips and Tricks for MongoDB Developers
Related Blogs
Snail in a Turtleneck
JCZ2MkKb5S8ZX9pq
V2EX  ›  MongoDB

Mongodb 小白问题,首页显示关注的人的帖子,一般怎么实现?

  •  
  •   JCZ2MkKb5S8ZX9pq · 2019-02-26 00:11:36 +08:00 · 8116 次点击
    这是一个创建于 2095 天前的主题,其中的信息可能已经有所发展或是发生改变。

    现在有一个 db.follow

    {uid:111, follows:[001, 002, 003]}
    {uid:222, follows:[005, 006, 007, 008]}
    ...
    

    然后有一个 db.post

    {id:00001, uid:001, text:'...'}
    {id:00002, uid:001, text:'...'}
    {id:00003, uid:005, text:'...'}
    

    目的

    • 取出某个用户 /关注的对象 /所发出的帖子。
    • 帖子以 _id 或发帖时间倒序排列。
    • 包含翻页功能。

    方法

    follows = db.follow.find_one({uid:111}).follows
    db.post.aggregate([ 
        {$match: {uid: {$in:follows}}}, 
        {$sort: {_id:-1}},
        {$limit: 10}
    ])
    

    大致是这么个思路。


    请问

    • 有什么更高效简洁的写法吗?
    • 有办法把第一步取数组放到 aggregate 内吗?
    • 爬虫经常看到翻页是给出下一页第一条 post 的 id,然后把这个作为起始 id 再去取下一页,这个怎么实现的?
    14 条回复    2019-02-26 23:14:40 +08:00
    fakeshadow
        1
    fakeshadow  
       2019-02-26 00:37:40 +08:00   ❤️ 1
    复杂的查询不是很懂。
    用$lookup 把 follow 的 uid 拿过来再 match 不知道可以不。
    用$gt 来查询应该就可以实现翻页吧。
    JCZ2MkKb5S8ZX9pq
        2
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-02-26 00:59:08 +08:00
    @fakeshadow 我也想拿到 aggregate 里面来,但不知道用哪个命令。
    gt 只有在 id 升序或降序才可以吧。可现在 id 是 16 位英数混排的,很可能不行。
    fortunezhang
        3
    fortunezhang  
       2019-02-26 08:56:19 +08:00
    不建议你这么做,流量稍微一大就压死数据库。 推荐你去看下 QQ 空间的算法。
    brickyang
        4
    brickyang  
       2019-02-26 09:37:03 +08:00
    1. ObjectID 是有序的,可以用来翻页,自定义乱序 id 基本没办法了
    2. https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/
    luw2007
        5
    luw2007  
       2019-02-26 09:37:57 +08:00
    homeline timeline
    很少会用数据库直接生成
    大部分都是在发|删帖的时候用逻辑改变 homeline 和 timeline
    whusnoopy
        6
    whusnoopy  
       2019-02-26 10:17:41 +08:00   ❤️ 1
    以前在人人做过,这种 SNS 的首页或信息流,都是有额外的缓存的,当年写过一篇分析,见 https://www.yewen.us/blog/2013/03/push-or-pull-on-sns/

    关于翻页用 post_id,最大的好处是可以利用主 key 来做查询加速,查询语句里写 {post_id: {$gt: post_id}}, limit=10 就好,如果是 {}, limit=10, skip=20 的写法,DB 还是要先把前面 20 条取出来扫过去再拿后面的,翻页到后面时性能开销极大,特别是如果还加了其他的条件,可能就是从 100 条记录里选 10 条,和从 10000 条记录里选 10 条的差别
    JCZ2MkKb5S8ZX9pq
        7
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-02-26 11:42:46 +08:00
    @luw2007 那如果某个用户有很多 follower,他每发一帖,都要去更新所有 follower 的 timeline 吗?
    另外讨论下:
    follower 的 timeline 条数有上限嘛?还是超了那个上限再去数据库里抓?
    如果有非活跃 follower,会不会做太多额外的开销?
    JCZ2MkKb5S8ZX9pq
        8
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-02-26 11:47:32 +08:00
    @whusnoopy 请教一下
    现在尝试下来,直接 aggregate 查询,好像是返回 cursor,是个 generator。
    这时候加上 limit,速度其实是挺快的。去掉 limit,数据量大就很慢了。
    有没有什么办法可以利用这个 cursor 做翻页?
    感觉可能会比重搜一次 post_id 会快一点
    JCZ2MkKb5S8ZX9pq
        9
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-02-26 12:11:11 +08:00
    @whusnoopy

    > 新浪那个前端加载速度, 绝对不比后台做一次拉操作快, 所以用户几乎感觉不到……

    这个吐槽,笑死。
    luw2007
        10
    luw2007  
       2019-02-26 12:26:38 +08:00
    @JCZ2MkKb5S8ZX9pq 用户量少就不要考虑拆分活跃用户和非活跃用户。只会增加代码的复杂度。
    完备的做法就是再造一个微博。

    owner 发布之后,直接更新 follower 的 timeline
    whusnoopy
        11
    whusnoopy  
       2019-02-26 15:19:14 +08:00
    @JCZ2MkKb5S8ZX9pq 这个取决于你的最终实现

    就这个问题来说,我们先关注 aggregate 到底做了什么。裸的实现应该是从每个 followee 那拿 sort 好的 limit 条数据,然后合并到一起,再 sort 取 limit。如果你需要自己优化这个事情,那就是先从每个 followee 那拿从 post_id 往前的 limit 条数据,然后把所有 followee 的 limit 条数据合并成 n*limit 条,再排序取 limit (这里有个假设是 post_id 是全局单增的)

    回到 cursor 的问题,这个 cursor 很可能只有 limit 条,再往后 MongoDB 压根就没给你准备数据,或者还是要按前面我说的那样去重复计算

    我觉得你先不用考虑活跃用户和非活跃用户,每个人的 timeline 默认都是有限的,翻前一两页和翻很多页的逻辑都是要区分,前面是缓存好的数据,后面是实时计算。如果你的用户量真的大到这种规模,自然会有人来帮你架构优化的(比如微博)
    JCZ2MkKb5S8ZX9pq
        12
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-02-26 16:48:15 +08:00
    @whusnoopy 嗯,明白。
    再请教个问题,假设先用纯拉取的方式。
    我现在是分两步,先从一个记录关系的 collection 获取关注列表,然后到 post 的 collection 获取指定关注列表发的帖子,mongo 有没有什么命令能直接写到一个 aggregate 里嘛?还是一定要分两步来?
    whusnoopy
        13
    whusnoopy  
       2019-02-26 22:28:31 +08:00 via Android
    @JCZ2MkKb5S8ZX9pq 这个不清楚,我都自己裸写的……
    JCZ2MkKb5S8ZX9pq
        14
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-02-26 23:14:40 +08:00
    @whusnoopy 搞定了,直接写里面就完了,直接把查询写在$in 后面,不需要赋值再传递。
    实测查询的话,的确用单增 id 比较 lt 快一点。
    skip 的话,skip100 大概 0.1s ,skip10000 大概 0.2s ,估计量大了还是差蛮多的。
    而且再次查询的话 skip 位置可能有偏移。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3485 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 11:14 · PVG 19:14 · LAX 03:14 · JFK 06:14
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.