Jump to:

3447 Posts in 1032 Topics by 872 members

Template Questions

SilverStripe Forums » Template Questions » Menu Slowing Load Times - SOLVED WITH PARTIAL CACHING

Moderators: martimiz, Sean, biapar, Willr, Ingo, swaiba, simon_w

Page: 1
Go to End
Author Topic: 1133 Views
  • davepolyester
    Avatar
    Community Member
    47 Posts

    Menu Slowing Load Times - SOLVED WITH PARTIAL CACHING Link to this post

    Hi all,

    I've built a site (in SS 2.4.7) for a client to use as a knowledgebase and, as such, it contains a vast number of pages with much more hierarchical depth than your average site.

    We've been having speed issues which may be a result of hosting on Dreamhost, but I've noticed that the menu system seems to affect the page load speed greatly.
    A page with the menu switched on takes about 6-8 seconds to load, but the same page with the menu switched off takes about 2 seconds.
    I'm concerned this means each page load is having to dig the whole content of every page out of the database so it can populate the menu with all the Titles of every sub page!

    The menu is a fairly standard 'Suckerfish' style flyout, with about six levels of depth, and I've attached my template code for you to check out. Please let me know if you spot anything dodgy in there that could help.

    I wonder if there might be a way to delay the populating of deeper levels of the menu until someone actually hovers over the menu?

    Any advice on dealing with this kind of site in SS will be much appreciated.

    BTW, am getting YSlow scores of about 83-94 so the site overall should be pretty speedy I think.

    Cheers!

    Attached Files
  • kaanuni
    Avatar
    Community Member
    22 Posts

    Re: Menu Slowing Load Times - SOLVED WITH PARTIAL CACHING Link to this post

    First of all if there is a will there is a way. So yes you can 'delay the populating deeper levels of the menu until someone actually hovers over the menu'. It would however be complicated. You would need to use ajax/json. It appears you are using css for the menu except for old browsers. So what you would do is load the first level of the menu and add a onmouseover="loading_function($li_id)" property to the <li>'s or use jQuery to the same effect (note whatever way you do it you will have to add id properties to the menu items). Next you will need to define loading_function in a javascript file so that it reads a url on your server to get the sub menu items. And finally you will have to create a new template and page type for this url, possibly with a custom controller so that it returns one level of menus in json format.

    However, besides not being the easiest solution this is not the best solution either, since it won't work for browsers that have disabled javascript. There are other options:

    1- Overload the getMenu method of your controller and use custom SQL to fetch only the columns you need. Or write a new function like getSimpleMenu (probably a better idea).
    2- Write a getEntireMenu method in your controller that uses custom SQL to fetch the relevant columns for site tree for the entire menu and output it. This will allow you to optimize the code for your menu depth and will result in less function calls. However you will either have to include HTML in your controller (which is icky) or output javascript (which is not only equally icky, but also sucks for SEO).
    3- Use partial or total caching, read: http://doc.silverstripe.org/framework/en/reference/partial-caching http://doc.silverstripe.org/framework/en/2.4/reference/partial-caching

    Method #1 is not terrible and you can do it along side #3 for even better performance. Method #2 is a bad idea in that it requires a lot of effort and any gains come at the expense of breaching the mvc model. #3 is quicker to implement and will likely give the largest performance boost on average.

    In this instance you would wrap your menu with this:

    <% cached 'navigation', List(Page).max(LastEdited) %> ***
    <% cached 'navigation', Aggregate(Page).Max(LastEdited) %>
    <% end_cached %>

    *** The struck out line is for 3.0, my bad.

    Now I haven't tried this, but according to the page at the link I posted, the section wrapped in this will be cached until any page on the site is edited. So if after you are done making edits you refresh a page on your site with the navigation, your visitors will always get a cached copy of the menu.

    Alternatively you can cache entire pages. You can read up on that here: http://doc.silverstripe.org/framework/en/reference/staticpublisher
    http://doc.silverstripe.org/framework/en/2.4/reference/staticpublisher

  • davepolyester
    Avatar
    Community Member
    47 Posts

    Re: Menu Slowing Load Times - SOLVED WITH PARTIAL CACHING Link to this post

    Thanks Kaanumi for your fantastic response. Great to get such excellent detail around possible solutions, their pros and cons, and how they'll work rather than just a simple "Do this..." with no explanation.

    I agree with you that the caching option should be the best method. I certainly don't want to introduce any ajax or other javascript to my menus, I much prefer lean, mean CSS styled versions.

    I'll take a look at the links in your posts and come back with any questions and the results from my implementation.

    Thanks once again!

  • davepolyester
    Avatar
    Community Member
    47 Posts

    Re: Menu Slowing Load Times - SOLVED WITH PARTIAL CACHING Link to this post

    Success! Partial-caching is now my best friend! By caching my menus I've reduced page load times from 8-10 seconds down to 2-5 seconds.
    Ran into a few gotchas though:

    1) If a <% require %> tag is inside the <% cache %> tag, it ceases to be called once the content is cached. I had a CSS file in this situation and my menu would completely restyle itself once it was cached, defaulting to another CSS file further up the chain.

    2) I can't use the same menu for the Secure and Public sections of the site. If I do, the public end up seeing the Secure pages listed in their menu, though they can't access them of course.

    3) I've had to tweak my CSS as, for some reason, the a.Section and a.Current tags seem to cache sometimes too, resulting in the menu continuing to display a previously visited page as the Current page. This seems a bit intermittent so I'm confused, but I've simply disabled my styling for Current pages so it won't affect end users.

    Thanks once again Kaanumi for pointing me towards the partial-caching feature - it has saved Silverstripe from being dumped by the client on this project!

  • Willr
    Avatar
    Forum Moderator
    5511 Posts

    Re: Menu Slowing Load Times - SOLVED WITH PARTIAL CACHING Link to this post

    2) Use the CurrentMember flag as part of your cache key.

    3) Add ID as part of the cache key

    <% cached 'Menu', Aggregate(SiteTree).Max(LastEdited), ID, CurrentMember.ID %>

  • davepolyester
    Avatar
    Community Member
    47 Posts

    Re: Menu Slowing Load Times - SOLVED WITH PARTIAL CACHING Link to this post

    Thanks for the CurrentMember.ID tip Will, that should fix the anomalies for me. Unfortnately I'm still getting some issues:

    If I log in with one user, then log out and log back in with a second user who has different page view permissions, the second user still sees the menu as it appeared to the first user, including items that should be restricted. (If they click the items they are told they don't have access.)

    Is there something else I need to do to force the menu to render according the current user's page access rights?

    Could it be caused by the fact that the cached menus are all in Includes, not directly in any page template?

    Thanks again.

  • lx
    Avatar
    Community Member
    83 Posts

    Re: Menu Slowing Load Times - SOLVED WITH PARTIAL CACHING Link to this post

    I would like to address this topic once gain.
    For me, SS generates too many queries to build the navigation.

    Lets take this example template

    <nav class="primary">
       <ul>
    <% loop $Menu(1) %>
             <li class="$LinkingMode"><a href="$Link" title="$Title.XML">$MenuTitle.XML</a>
    <% if $Children %>
    <ul>
    <% loop $Children %>
    <li class="$LinkingMode"><a href="$Link" title="$Title.XML">$MenuTitle.XML</a>
    <% end_loop %>
    </ul>
    <% end_if %>
    </li>
    <% end_loop %>
       </ul>
    </nav>

    I didn't do any SS for at least one year, but i think this is correct
    It creates only a 2-level-navigation.

    • Home
    • Products
      • Product 1
      • Product 2

    • About Us
      • Our dog
      • Our Cat

    • Contact Us
      • Contact Me
      • Contact my dog

    This little sitetree of 10 pages already needs 12 database queries to be created. (use ?showqueries=1 to see all queries)
    So with big websites the number of needed queries can become really high.

    Somehow, adding partial caching feels like a patch for the code that has to generate the navigation.

    I am wondering if it could be possible to write a function that just makes a single query to get all pages and then build a tree of them in php.

    Any other thoughts on this ?

  • HARVS1789UK
    Avatar
    Community Member
    21 Posts

    Re: Menu Slowing Load Times - SOLVED WITH PARTIAL CACHING Link to this post

    @Willr can you provide any information on @DavePolyester's 1st point:

    1) If a <% require %> tag is inside the <% cache %> tag, it ceases to be called once the content is cached.

    I have, what I assume to be, a similar issue where I am trying to cache 2 x WidgetAreas which (optionally) appear on every page of my site. I am using the below cache key:

    <% cached 'WidgetAreas', ID, List(Page).max(LastEdited) %>

    I understand this should create one new cache file for the enclosed markup for each page on my site due to the use of 'ID' in my cache key. Note that you may expect me to just use 'LastEdited' for the second part but I have implemented an 'Inherit Widget Area From Parent' feature meaning widgets changing on any of this pages parents could potentially mean my cache needs invalidating.

    The problem I am having is that my widgets CSS files are no longer included when my cached version is used, I don't have any <% require %> tags within my <% cache %> tag but my widgets CSS is included via the widget controllers init() method() i.e.:

    public function init() {
    parent::init();
    Requirements::css('widgets_CallToActionWidget/css/CallToActionWidget.css');
    }

    I imagine that this is the same issue as raised above, and that moving the require logic into the widget template, rather than its controller class, would have the exact same result.

    Can you offer any insight as to why content pulled in via the Requirements class is excluded from cached content? and more importantly for me, how I can fix it in order to cache my WidgetAreas?

    Thanks in advance,

    HARVS1789UK

    1133 Views
Page: 1
Go to Top

Want to know more about the company that brought you SilverStripe? Then check out SilverStripe.com

Comments on this website? Please give feedback.