0

TL;博士:

聊天是一个集合。ChatMess 另一个包含引用 Chat _id 的消息的消息。如何以尽可能少的计算从聊天列表中获取最后一条消息?在这里,循环中的查找/获取周期太重且太长。

我有这个出版物,用于向用户返回一组光标:

  • 他参加的聊天会话(来自聊天收藏)
  • 第一个光标中引用的每个聊天会话的最后一条消息(来自 ChatMess 集合)

目前,逻辑是:

  • 从用户个人资料中获取聊天会话列表
  • 找到聊天会话并循环播放
  • 在循环中,我找到来自此聊天会话的最后一条消息,并将其 _id 存储在一个数组中。此外,我存储所有其他用户_id。
  • 然后,我找到 _id 与我的数组中的消息匹配的消息。

这是我的主要问题:

难道没有一种更快的方法来从我的每个聊天会话中获取最后一条消息吗?使用该算法,我很容易达到 8000 毫秒的响应时间,这是一个过于繁重的计算时间,因为大部分时间都用于查找/获取聊天消息的 _id(参见 Kadira 的链接屏幕)。

    Meteor.publish("publishNewChat", function() {
    this.unblock();

    // we get a list of chat _id
    let chatIdList = _get_all_the_user_chats_ids(this.userId);

    if (!chatList)
        return ;

    // get the chat sessions objects
    let chats_cursor = Modules.both.queryGet({
                    type        : 'chat',
                    method      : 'find',
                    query       : { _id: { $in: chatIdList } },
                    projection  : { sort: { _id: 1 }, limit : 1000 }
                });

    let array_of_fetched_chats = chats_cursor.fetch();
    let chat_ids = [];

    // and here we loop through the chat documents in order to get the last message that's been attached to each of them
    array_of_fetched_chats.forEach(function(e) {
        let lastMess = Modules.both.queryGet({
                            type        : 'chatMess',
                            method      : 'findOne',
                            query       : { chatId: e._id },
                            projection  : { sort: { date: -1 } }
                        });

        if (lastMess)
            chat_ids.push(lastMess._id);
    });

    return ([
        chats_cursor,
        Modules.both.queryGet({
            type        : 'chatMess',
            method      : 'find',
            query       : { _id: { $in: chat_ids } },
            projection  : { sort: { date: -1 }, limit: 1000 }
        })
    ]);
    });

最后,它还为我随后的所有 DDP 请求添加了延迟。我目前使用 this.unblock() 来避免这种情况,但我不想在这里使用它。

仅供参考,我有另一个发布,每次客户端更改他当前的活动聊天会话时都会更新:在客户端上,路由到一个新的聊天将其 _id 添加到一个反应数组中,以更新我的 getChatMess 订阅,以便在客户端上获取消息从用户连接后访问的每个聊天中。目标显然是让服务器不必从用户一生中访问过的每个聊天会话中发送每条消息。

不幸的是,我缺乏在不破坏我所有聊天逻辑的情况下改进算法的想法:S。你有什么想法吗?你会怎么做?

谢谢。

编辑:这是来自 kadira 的屏幕,清楚地显示了问题: 在此处输入图像描述

4

2 回答 2

0

您是否考虑过使用reywood/publishComposite 包?使用此包,您可以以相同的方法发布相关数据,而无需执行一堆逻辑来获取发布的正确数据。

下面的代码应该让你开始:

Meteor.publishComposite("publishNewChat", function() {
return [{
    find:function(){
        return Users.find({ _id: this.userId },{fields:{"profile.chat":1}});
    },
    children:[{
        find:function(user){ //this function is passed each user returned from the cursor above.
            return UserChats.find({userId:user._id},{fields:{blah:1,blah:1}}); //find the user chats using whatever query 
        },
        children:[
            //if there are any children of user chats that you need to publish, do so here...
            {
                find:function(userchat){
                    return Chats.find({_id:userchat.chatId})
                },
                children:[
                    {
                        find:function(chat){
                            return ChatMess.find({chatId:chat._id},{ sort: { date: -1 } });
                        },
                        children:[
                            {
                                find:function(chatMess){
                                    var uids = _.without(chatMess.participants, this.userId);
                                    return Users.find({_id:{$in:uids}});
                                }
                            }
                        ]
                    }
                ]
            }
        ]
    },
    ]
}]

这将发布与每个父文档相关的所有文档的光标。它非常快,我在生产平台高流量和大数据集上使用这个包没有问题。然后,您可以在客户端上正常查询文档以获取需要显示的文档。

就像是:

Users.findOne({_id:Meteor.userId()});
UserChats.find({userId:Meteor.userId()});
etc...
于 2016-02-25T16:19:15.797 回答
0

这是我开发的解决方案:

Meteor.publish("publishNewChat", function() {
this.unblock();

let user = Modules.both.queryGet({
                type        : 'users',
                method      : 'findOne',
                query       : { _id: this.userId },
                projection  : { fields: { "profile.chat": true } }
            });

let thisUserschats = tryReach(user, "profile", "chat").value;

if (!thisUserschats)
    return ;

thisUserschats = thisUserschats.map(function(e) { return (e.chatId); });

let chats = Modules.both.queryGet({
                type        : 'chat',
                method      : 'find',
                query       : { _id: { $in: thisUserschats } },
                projection  : { sort    : { _id: 1 },
                                limit   : 1000
                              }
            });

let chatArray = chats.fetch(),
    uids = cmid = [];

let messages_id_list = [],
    i = chatArray.length;

let _parallelQuery = index => {
    Meteor.setTimeout(function () {
        let tmp = Modules.both.queryGet({
                      type      : 'chatMess',
                      method    : 'find',
                      query     : { chatId: chatArray[index]._id },
                      projection: { limit: 1, sort: { date: -1 } }
                  });

        tmp.forEach(doc => {
            messages_id_list.push((doc && doc._id) ? doc._id : null);
        });
    }, 1);
}

while (--i >= 0)
    _parallelQuery(i);

let cursors = {
    chats           : chats,
    chatMessages    : null
}

let interval = Meteor.setInterval(function () {
    if (messages_id_list.length === chatArray.length)
    {
        Meteor.clearInterval(interval);

        cursors.chatMessages = Modules.both.queryGet({
                                    type        : 'chatMess',
                                    method      : 'find',
                                    query       : { _id: { $in: messages_id_list } },
                                    projection  : { sort: { date: -1 }, limit: 1000 }
                               });

        cursors.chats.observeChanges({
            // ...
        });

        cursors.chatMessages.observeChanges({
            // ...
        });

        self.ready();

      self.onStop(() => subHandle.stop(); );
    }
}, 10);

});

我使用带有 Meteor.setTimeout 的异步函数来并行化查询并保存引用聊天 _id 的索引以查找。然后,当查询完成时,我将最后一条消息添加到数组中。使用 Meteor.setInterval,我检查数组长度以了解所有查询何时完成。然后,由于我不能再返回游标,我使用 Meteor 发布低级 API 来处理文档的发布。

仅供参考:在第一次尝试中,我在我的 _parallelQueries 中使用了“findOne”,它将我的计算时间除以 2/3。但是后来,多亏了一位朋友,我尝试了 cursor.foreach() 函数,它允许我再次将计算时间除以 2 !

在生产环境中,基准测试让我的响应时间从 7/8 秒变为平均 1.6 秒 :)

希望这对你们有用!:)

于 2016-02-25T21:32:26.767 回答