Simple fragment cache and some javascript ninja code for the win!

Written by Walter on 21/7/2013

< >

Today I decided to optimize one of our most complex pages. It was long overdue. The view rendering was now taking a whopping 9 seconds and the javascript which dynamically added tri-state selects another +7 seconds or more on a slow pc.

1. Solving the server side view render time :
Basically we have a dynamic permission checker that iterates all controllers and actions and shows an overview for each role. Turns out after some profiling, fetching the actions from routes are not expensive it's rendering +500 checkboxes that screeches our rails app to a halt. Easy 5 min. fix was using a clever fragment cache like so:


<h1>Manage permissions</h1>

<% app_controller_actions, app_controller_paths = Permission.routes_hashes %>
<% permission_cache_key = Digest::MD5.hexdigest( app_controller_actions.to_s+app_controller_paths.to_s ) %>

<%= cache "permissions_#{permission_cache_key}" do %>

... complex view code iterating all controllers and showing the action checkboxes for each login role...

<%end%>

What's nice about this is that it will auto invalidate the cache if the md5 of the set we use to iterate over changes. So if any new controller or action is defined we immediately create a new cached copy. Ow yes and view render time now dropped to 120ms vs the original +9000ms ;)

2. Solving the client side (jquery javascript). We also iterated each row of checkboxes and wanted to minimize/collapse it all (this turns out to not be so slow) what did take time was determining to show a custom label in case some checkboxes we're checked. This was harder to make faster but we could solve it in another way. By (ab)using setInterval we can simulate an asynchronous process. In more modern browsers the issue will solve itself easily with javascript workers. But for now you can use the following ninja scripting:

CODE BEFORE:


  $(".select_all").updateTriSelect(); // nice and short alas this hogs the browser 
                                      // and locks it untill all elements are processed


Fixing it:


function batchUpdateTriSelect( inputList ){
  var i=0, limit = inputList.size(), busy=false;
  var processor = setInterval( function(){
    if( !busy ){
      busy = true;
    
      //update a single tri-select
      if( i < limit ){  
        element = $(inputList[i]);
        updateSelectAll( element.data('controller'), element.data('role') ); //updateTriSelect of single element but unrolled to shave a few milli seconds off...
      }   

      if( ++i == limit ){
        clearInterval( processor ); //stop processing, our list is updated.
      }   
      busy = false;
    }   
  
  }, 1 );  
}

The net effect is the browser is responsive straight after the pageload and is processing in the background without disturbing the user experience. woohooo!

Back to archive