candy icon indicating copy to clipboard operation
candy copied to clipboard

performance issue when joining a big room

Open zh99998 opened this issue 12 years ago • 14 comments

if a room has huge amount of users in it (e.g. 500), when parsing presence it could be so slow, and choke up the whole browser.

zh99998 avatar Jan 26 '13 23:01 zh99998

You might want to disable animations - this helps speeding up a bit.

mweibel avatar Jan 27 '13 08:01 mweibel

We might should add some logic there to use a short timeout and disable animations if the joined users stack up to 10+ or something.

pstadler avatar Jan 27 '13 18:01 pstadler

i tried to disable animation, set Candy.View.Pane.Roster.joinAnimation to $('#' + elementId).show(); it's much faster, but still a bit slow

joining a ~500 room, with animation it will choke up 9 seconds, without it's 4 seconds.

zh99998 avatar Jan 29 '13 08:01 zh99998

You could also disable all animations using jquery: jQuery.fx.off = false;

Am 29.01.2013 um 09:25 schrieb zh99998 [email protected]:

i tried to disable animation, set Candy.View.Pane.Roster.joinAnimation to $('#' + elementId).show(); it's much faster, but still a bit slow

joining a ~500 room, with animation it will choke up 9 seconds, without it's 4 seconds.

— Reply to this email directly or view it on GitHub.

mweibel avatar Jan 29 '13 08:01 mweibel

One possible tweak would be to implement a short timeout to do batch processing with detaching the element from the DOM.

On Tue, Jan 29, 2013 at 9:26 AM, Michael Weibel [email protected]:

You could also disable all animations using jquery: jQuery.fx.off = false;

Am 29.01.2013 um 09:25 schrieb zh99998 [email protected]:

i tried to disable animation, set Candy.View.Pane.Roster.joinAnimation to $('#' + elementId).show(); it's much faster, but still a bit slow

joining a ~500 room, with animation it will choke up 9 seconds, without it's 4 seconds.

— Reply to this email directly or view it on GitHub.

— Reply to this email directly or view it on GitHubhttps://github.com/candy-chat/candy/issues/137#issuecomment-12824737.

pstadler avatar Jan 29 '13 08:01 pstadler

I'm going to try to implement some of these ideas for large volume rooms (which I have too) - do they make sense in the core or as a plugin? Looking through the event hooks for plugins, it doesn't seem to me like it's something I could add in there.

The items I want to address are:

  • cutting off animation when a room hits a certain size
  • disabling join/leave notifications, which can overwhelm chat in a room when there's frequent turnover
  • potentially hide the user list by default, or batch updates to it
  • look into whether it's possible to convince the jabber server not to send presence updates, which seem a lot of unnecessary load in a muc context (this may have nothing to do with candy, we'll see)

drewww avatar Feb 17 '13 17:02 drewww

Hi dreww,

those seem to be reasonable to implement into the core. We'd appreciate a pull request and gladly help if possible.

The functions you want to implement would be nice if everyone is configurable to a certain limit.

About the presence updates: I think you use ejabberd, right? This patch might help to solve the issue: http://www.ejabberd.im/node/5055

  • Michael

mweibel avatar Feb 18 '13 07:02 mweibel

Closing this issue in favor of #148 which we should merge.

mweibel avatar Dec 16 '13 21:12 mweibel

@mweibel only disable animation is not ok. another expensive calculate is sorting.

for me, because my room is always big (500~1000), i just removed the sorting code without an opinion, here is my fix.

(function (self, $) {
    Candy.View.Pane.Roster.update = function (roomJid, user, action, currentUser) {

        var roomId = self.Chat.rooms[roomJid].id,
            userId = Candy.Util.jidToId(user.getJid()),
            usercountDiff = -1,
            userElem = $('#user-' + roomId + '-' + userId);

        var evtData = {'roomJid': roomJid, type: null, 'user': user};

        /** Event: candy:view.roster.before-update
         * Before updating the roster of a room
         *
         * Parameters:
         *   (String) roomJid - Room JID
         *   (Candy.Core.ChatUser) user - User
         *   (String) action - [join, leave, kick, ban]
         *   (jQuery.Element) element - User element
         */
        $(self).triggerHandler('candy:view.roster.before-update', {
            'roomJid': roomJid,
            'user': user,
            'action': action,
            'element': userElem
        });

        // a user joined the room
        if (action === 'join') {
            usercountDiff = 1;
            var html = Mustache.to_html(Candy.View.Template.Roster.user, {
                roomId: roomId,
                userId: userId,
                userJid: user.getJid(),
                nick: user.getNick(),
                displayNick: Candy.Util.crop(user.getNick(), Candy.View.getOptions().crop.roster.nickname),
                role: user.getRole(),
                affiliation: user.getAffiliation(),
                me: currentUser !== undefined && user.getNick() === currentUser.getNick(),
                tooltipRole: $.i18n._('tooltipRole'),
                tooltipIgnored: $.i18n._('tooltipIgnored')
            });

            if (userElem.length < 1) {
                var userInserted = false,
                    rosterPane = self.Room.getPane(roomJid, '.roster-pane');

                // first user in roster
                if (!userInserted) {
                    rosterPane.append(html);
                }

                self.Roster.joinAnimation('user-' + roomId + '-' + userId);
                // only show other users joining & don't show if there's no message in the room.
                if (currentUser !== undefined && user.getNick() !== currentUser.getNick() && self.Room.getUser(roomJid)) {
                    // always show join message in private room, even if status messages have been disabled
                    if (self.Chat.rooms[roomJid].type === 'chat') {
                        self.Chat.onInfoMessage(roomJid, $.i18n._('userJoinedRoom', [user.getNick()]));
                    } else {
                        self.Chat.infoMessage(roomJid, $.i18n._('userJoinedRoom', [user.getNick()]));
                    }
                }
                // user is in room but maybe the affiliation/role has changed
            } else {
                usercountDiff = 0;
                userElem.replaceWith(html);
                $('#user-' + roomId + '-' + userId).css({opacity: 1}).show();
                // it's me, update the toolbar
                if (currentUser !== undefined && user.getNick() === currentUser.getNick() && self.Room.getUser(roomJid)) {
                    self.Chat.Toolbar.update(roomJid);
                }
            }

            // Presence of client
            if (currentUser !== undefined && currentUser.getNick() === user.getNick()) {
                self.Room.setUser(roomJid, user);
                // add click handler for private chat
            } else {
                $('#user-' + roomId + '-' + userId).click(self.Roster.userClick);
            }

            $('#user-' + roomId + '-' + userId + ' .context').click(function (e) {
                self.Chat.Context.show(e.currentTarget, roomJid, user);
                e.stopPropagation();
            });

            // check if current user is ignoring the user who has joined.
            if (currentUser !== undefined && currentUser.isInPrivacyList('ignore', user.getJid())) {
                Candy.View.Pane.Room.addIgnoreIcon(roomJid, user.getJid());
            }

            // a user left the room
        } else if (action === 'leave') {
            self.Roster.leaveAnimation('user-' + roomId + '-' + userId);
            // always show leave message in private room, even if status messages have been disabled
            if (self.Chat.rooms[roomJid].type === 'chat') {
                self.Chat.onInfoMessage(roomJid, $.i18n._('userLeftRoom', [user.getNick()]));
            } else {
                self.Chat.infoMessage(roomJid, $.i18n._('userLeftRoom', [user.getNick()]));
            }
            // user has been kicked
        } else if (action === 'kick') {
            self.Roster.leaveAnimation('user-' + roomId + '-' + userId);
            self.Chat.onInfoMessage(roomJid, $.i18n._('userHasBeenKickedFromRoom', [user.getNick()]));
            // user has been banned
        } else if (action === 'ban') {
            self.Roster.leaveAnimation('user-' + roomId + '-' + userId);
            self.Chat.onInfoMessage(roomJid, $.i18n._('userHasBeenBannedFromRoom', [user.getNick()]));
        }

        // Update user count
        Candy.View.Pane.Chat.rooms[roomJid].usercount += usercountDiff;

        if (roomJid === Candy.View.getCurrent().roomJid) {
            Candy.View.Pane.Chat.Toolbar.updateUsercount(Candy.View.Pane.Chat.rooms[roomJid].usercount);
        }

        var evtData = {
            'roomJid': roomJid,
            'user': user,
            'action': action,
            'element': $('#user-' + roomId + '-' + userId)
        };

        // deprecated
        Candy.View.Event.Roster.onUpdate(evtData);

        /** Event: candy:view.roster.after-update
         * After updating a room's roster
         *
         * Parameters:
         *   (String) roomJid - Room JID
         *   (Candy.Core.ChatUser) user - User
         *   (String) action - [join, leave, kick, ban]
         *   (jQuery.Element) element - User element
         */
        $(self).triggerHandler('candy:view.roster.after-update', evtData);
    }

    /** Function: joinAnimation
     * Animates specified elementId on join
     *
     * Parameters:
     *   (String) elementId - Specific element to do the animation on
     */
    self.Roster.joinAnimation = function (elementId) {
        $('#' + elementId).show().css({opacity: 1})
    }
})(Candy.View.Pane, jQuery);

maybe it's better to append multi item to DOM rather than only once a time (and then we can reopen the sorting because it runs less). but i don't know how to do it.

zh99998 avatar Dec 17 '13 00:12 zh99998

Sounds like a good idea. We could implement all three options (animation disabled, sorting disabled, multi item DOM insertion (with a detached DOM)) and for each of those options we could specify a configurable threshold. What do you think?

mweibel avatar Dec 17 '13 07:12 mweibel

@zh99998 I implemented some measurements in the branch feature/148-performance-improvements-big-rooms. Would you like to test them out?

There are now new options to be set for the view config part:

view: {
    bigroom: {
        disableAnimationThreshold: 200, // disable animations after 200 users
        disableSortingThreshold: 1000, // disable sorting entirely after 1000 users
        batchRosterUpdate: {
            threshold: 500, // after 500 users, update roster in batches
            interval: 200 // every 200ms, the roster gets updated with the list of new users
        }
    }
}

Currently it does only check the batchRosterUpdate settings on joins of users. If needed, it might be possible to add it also if users are leaving.

Please note, this is highly experimental code and I couldn't yet test it thoroughly. Especially I also testet it only with one room..Will need to test how it behaves with multiple rooms..

mweibel avatar Dec 23 '13 18:12 mweibel

@zh99998 already had some time to check it out?

mweibel avatar Jan 09 '14 17:01 mweibel

ohh... sorry, just saw. testing


room member count seems not work. after join, it's 0, and when a member leave, it will -1

zh99998 avatar Mar 31 '14 03:03 zh99998

and my big room is public: [email protected]

since xmpp cross-server muc is slow. better register a @my-card.in account for test. just use pidgin or other xmpp client to register.

it will be full 1000 user in weekend / evening (UTC+8). and 300+ other time.

zh99998 avatar Mar 31 '14 03:03 zh99998