This Drupal cache case study began on a pleasant January afternoon in beautiful Goa. The entire company flew in for our annual retreat, enjoying the scenic beauty of the sandy beaches and the cool breeze. Between the fun times, we were ducking into our remote workspace, poring over a cachegrind report sent over by our client.

This report was generated for ajax responses coming from a product page on an e-commerce website. We could clearly see that the page was extremely slow to render and that this was true for all ajax responses.

The Cachegrind report as sent by the client - Drupal Cache: A Case Study

The Cachegrind report as sent by the client – Drupal Cache: A Case Study

Free Open Source Staffing Guide

Progressive Doping

Digging deeper to figure out what was causing the performance bottleneck, we use XDebug to generate cachegrind files, which we could then analyze to determine what functions were costing us the most, or where there were functional paths that could be truncated via caching. Performing this process iteratively we came up with a list of major pain areas. This list was very specific to our project.

  1. Function “get nid from product_id” was being frequently called with a resulting resource consuming data query.
  2. Function “hook_commerce_checkout_pane_info_alter” was being executing on each request.
  3. Product filtering for a particular type of user was a resource consuming activity
  4. Function “get product_id matching attributes” was being frequently called, with a resulting resource consuming query.
  5. The theme function “theme_extra_bundles_radio” was processing some resource consuming business logic.
  6. A lot of calls were made to the “entity_load” function.

Drupal Cache

It is a widely accepted fact that as great as the Drupal 7 core can be, it doesn’t scale well for web sites with a lot of content and a lot of users. To extract the best performance under such scenarios, it’s necessary to make use of several strategies, tools, and techniques. Keeping this in mind, we settled down with the following strategies:

  • Basic Drupal caching:

Drupal offers basic performance settings at:

Administration > Configuration > Performance > Development > Performance (admin/config/development/performance)

Here we enabled block and page caching which cached the dynamic complex SQL queries of Drupal for quick retrieval of content. We also enabled the Compress cached pages, Aggregate and compress CSS files and Aggregate JavaScript files which further helped in bandwidth optimization by reducing the number of HTTP calls.

  • Panel Caching:

    We developed a custom ctools plugin to provide two extra options for panel cache methods.

    • Complex cache
    • Lineup/Dealership cache
Lineup/Dealership cache - Drupal Cache: A Case Study

Lineup/Dealership cache – Drupal Cache: A Case Study

You can also define your panel cache method for your particular use case and cache the panel content.  First, define a ctool plugin like:

<?php // Plugin definition. $plugin = array( 'title' => t("Example cache"),
  'description' => t('Provides a custom option to cache the panel content.'),
  'cache get' => 'my_module_cache_get_cache',
  'cache set' => 'my_module_cache_set_cache',
);

/**
 * Get cached content.
 */
function my_module_cache_get_cache($conf, $display, $args, $contexts, $pane = NULL) {
   $cache = cache_get(‘my_panel_data', 'cache_panels');
  if (!$cache) {
	return FALSE;
  }

  if ((time() - $cache->created) > $conf['lifetime']) {
	return FALSE;
  }

  return $cache->data;
}

/**
 * Set cached content.
 */
function my_module_cache_set_cache($conf, $content, $display, $args, $contexts, $pane = NULL)    {

      	cache_set('my_panel_data', $content, 'cache_panels');
}

?>
  • Custom caching:

After looking at few more cachegrind reports, we identified some custom functions that were causing a performance problem and thrashing the database to perform complex queries and expensive calculations every time a user viewed specific pages. This prompted  us to use Drupal’s built-in caching API’s

The rule is never to do something time-consuming twice if we can hold onto the result and reuse them. This can be depicted in a simple example:

<?php function my_module_function() { $data = &drupal_static(__FUNCTION__); if (!isset($data)) { // Here goes the complex and expensive calculations and populate $data // with the output of the calculations. } return $data; } ?>

The drupal_static function provides a temporary storage to functions for data that sticks around even after they are done executing. drupal_static adds the magic to this function. It returns an empty value on the first call and preserves the data on next call in the same request. That means our function now can determine if the variable is already populated or not. If it has been populated, it will return the value and skip the expensive calculations.

The static variable technique stores data for only a single page load. To overcome this situation Drupal cache API persists the cached data in a database. The Following code snippet illustrates the use of Drupal cache API

<!--?php function my_module_function() 	{   
$data 	= 	&drupal_static(__FUNCTION__);   	
if (!isset($data)) {
  $cache = cache_get('my_module_data', 'cache_my_module')
  if ($cache && time() < $cache->expire) { 
    $data = $cache--->data;
  }
  else {
    // Here goes the complex and expensive calculations and populate
   // $data with the output of the calculations.
   cache_set('my_module_data', $data, 'cache_my_module', time() + 360);
  }
}
return $data;
}
?>

This version of the function uses database caching. Drupal caching API provides three key functions –  cache_get(), cache_set() and cache_clear_all(). The initial check is made to the static variable, and then this function checks Drupal’s cache for data stored with a particular key. If data is found the $data is set to $cache->data.

If no cached version is found, the function performs the actual work to generate the data and saves it to the static variable at the same time which means that in the next call in the same request, it does not even need cache_get.

Finally, we get a slick little function that saves time whenever it can—first checking for an in-memory copy of the data, then checking the cache, and finally calculating it from scratch.

Conclusion:

After putting all these together, we were able to reduce the total number of functions call to 741 and the execution time reduced to 665ms. Hence we get the 1500% of performance boost.

Cachegrind Report Generated After Implementing Drupal Cache. - Drupal Cache: A Case Study

Cachegrind Report Generated After Implementing Drupal Cache. – Drupal Cache: A Case Study

Want to learn how to set up RESTful Drupal Caching? Check this out.

References:

This article was originally published in April 2015.