I've been trying to implement a "related content" section on our new website. This is done with a nested related content asset listing, with the root node set to the root of the site, and the related node replaced with the current asset. Unfortunately, this was exceptionally slow, taking up to 3 minutes to render any page it was applied to!
Having had a look through the code, the procedure that appears to be happening is:
- Find all pages under the root node
- Ditch any that the user doesn't have permission to access (incl via roles)
- See what's left that's related to the original page
Steps 1 and 2 were being undertaken by a rather large database query from generateGetLinksQuery(). This was doing a LEFT JOIN between the permissions table and the roles view. This join is what was taking the time, as it was creating a table join with millions of tuples.
After a bit of experimentation, I have come up with what may be a better way of doing this query, which basically moves the roles part of the WHERE clause from the joined tables into a sub query pre-join. This has brought the related content generation down to 6 seconds, but also made a massive improvement to front-end performance system-wide. However, whilst this hasn't caused any noticable side-effects on out setup, I'm not yet convinced that it proves correct for all instances. Is there anybody with greater knowledge of the matrix core, and database integrity, who could have a look at this?
The patch is as follows:
--- asset_manager.inc.3-16-0 2007-09-13 14:08:06.000000000 +0100 +++ asset_manager.inc 2007-09-14 10:26:56.000000000 +0100 @@ -7417,10 +7417,6 @@ if (!$GLOBALS['SQ_SYSTEM']->userRoot() && !$GLOBALS['SQ_SYSTEM']->userSystemAdmin()) { $from .= ' INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_perm p ON p.assetid = l.minorid'; - // join to roles table - $from .= ' - LEFT JOIN '.SQ_TABLE_RUNNING_PREFIX.'vw_ast_role r on p.userid = r.roleid'; - // get user and group ids $userids = array_keys($this->getParents($GLOBALS['SQ_SYSTEM']->user->id, 'user_group', FALSE)); $userids[] = (String)$GLOBALS['SQ_SYSTEM']->user->id; @@ -7431,7 +7427,12 @@ } $userids_str = implode(',', $userids); - $userid_cond = ' AND (p.userid IN ('.$userids_str.') OR r.userid IN ('.$userids_str.'))'; + $userid_cond = ' AND (p.userid IN ('.$userids_str.'))'; + + // join to roles table + $from .= ' + LEFT JOIN (SELECT * FROM '.SQ_TABLE_RUNNING_PREFIX.'vw_ast_role WHERE userid IN ('.$userids_str.')) r on p.userid = r.roleid'; + $where .= $userid_cond.' AND ( (p.permission = '.$db->quote($access).' AND (
As I say, that change has really improved the performance of our system across the board, but related content still takes 6 seconds. Does anyone think it would be better looking for related stuff first, then to see if it can be accessed? (However, that would probably mean re-writing half of the listings engine, or not having related content based on the listings engine)