19

For a big institution website, with heavy caches, I'd like to generate caches as soon as possible, so that no user may arrive on cache generation...

I have a cron set all minutes that does it, by executing a few functions and requesting critical pages, but what I'm looking for is a way to know when the cache has just been cleared, preferably a hook, so I can launch this generating-functions.

Any idea ?

1
  • Depending on what you're trying to accomplish, phayes's answer down the page is a nice solution to fire off code after caches have been cleared. Commented Aug 2, 2016 at 15:42

9 Answers 9

8

There isn't in Drupal 7.x but this was added as a core hook, hook_rebuild in Drupal 8.x after enough people requested it. There may be a better way to solve your problem in 7.x though -- you're trying to initiate some kind of cache warming functionality right after cron clears the cache, correct? Another way to approach this would be to use Elysia cron which has a number of significant improvements to how cron operates but two that might be relevant to your use case are:

Elysia Cron extends Drupal standard cron, allowing a fine grain control over each task and several ways to add custom cron jobs to your site.

  • Set the timings and frequencies of each cron task (you can run some jobs every day at a specified hour, other only monthly and so on...). For each task you can simply choose between some frequently used options ("once a day", "once a month" ...), or use a powerful "linux crontab"-like syntax to set the accurate timings. You can even define your frequently used options to speed up site configuration. ...
  • Change the priority/order of task execution. ...

You can use this module to have finer control over how your cron runs to help resolve the stale cache issue. Specifically, you could add a hook to your rebuild functions to cron and then using Elysia cron, set these operations to run immediately after the cache clearing operation.

It also sounds like you might be having issues with cron running to frequently leading to the cache getting recreated too often. If that is the case, you can set the specific cache clearing operation in Elysia cron to run at a different rate than the rest of your cron operations, so for example search indexing would update every 5 minutes but the full cache clear would only run every 6 hours, etc.

Fine tune cron cache management : drupal cron will invalidate variable cache every cron run, and this is a great performance problem if you have a frequently called task. Elysia cron optimize cache management, and doesn't need to invalidate cache.

1
  • Well this is a real bummer. Hurry D8 ! Actually, I alreadry have, as I told you, a cron with elysia_cron, running every minute, warming what I need. But as my site is going to have > 10.000 / visits / hour, I'm pretty sure sbdy will fall on empty caches... Thx anyway, I know now that it's a D7 limitation ! Commented Sep 2, 2013 at 14:55
14

The way to do this is to use hook_flush_caches in combination with register_shutdown_function. Example code:

/**
 * Implements hook_flush_caches().
 */
function mymodule_flush_caches() {
   // After caches are cleared we will run mymodule_cache_rebuild()
   register_shutdown_function('mymodule_cache_rebuild');

   // We don't want to add any custom cache-tables, so just return an empty array
   return array();
}

/**
 * Rebuild expensive cache items that need to be rebuilt immediately.
 */
function mymodule_cache_rebuild() {
  // Do the cache rebuild work here
}

Using register_shutdown_function means that our cache-rebuilding function will be called after the caches have been cleared. We are abusing hook_flush_caches in a way that it was never intended to be used, but this should do exactly what you need.

2
  • I really like this solution. Before using it myself, I searched for any known issues/conflicts using register_shutdown_function() in Drupal, and came across Drupal core's drupal_register_shutdown_function(): "Wrapper for register_shutdown_function() that catches thrown exceptions to avoid "Exception thrown without a stack frame in Unknown". I know it makes me feel better about abusing hook_flush_caches if I am using only Drupal core functions to do it. Commented Jan 14, 2020 at 4:55
  • Now in Drupal 10, the name of the hook is hook_cache_flush
    – Andreas
    Commented Apr 26 at 10:03
11

No, there isn't. Not really. At least not in 6 or 7. Assuming 7:

If you will look at drupal_flush_all_caches() you will see it invokes hook_flush_caches(). This hook is meant to:

"add cache table names to the list of cache tables that will be cleared by the Clear button on the Performance page or whenever drupal_flush_all_caches is invoked."

It would be tempting to simply make your module's hook go last and write code there. But let's look again at drupal_flush_all_caches(). Actual deletion happens like this:

  $cache_tables = array_merge(module_invoke_all('flush_caches'), $core);
  foreach ($cache_tables as $table) {
    cache_clear_all('*', $table, TRUE);
  }

It means that all hooks are fired before anything gets really cleared. There is only one function called after actual deletion, _system_update_bootstrap_status(), but it only calls hook_boot, hook_exit, hook_watchdog and hook_language_init - hooks you don't want to implement only to provide cache-clear-dependant functionality.

3
  • Dammit, it took me to long to add all these links ;) Will leave it for now as I can't force myself to delete it, after so much time spent on explaining why it can't be done.
    – Mołot
    Commented Sep 2, 2013 at 14:27
  • 3
    Leave it, it is a good answer.
    – mpdonadio
    Commented Sep 2, 2013 at 14:59
  • Yes leave it, I can't check all good answers but I upped it :) Commented Sep 2, 2013 at 22:41
8

Broad strokes here:

While there isn't a hook out there in pre-D8, you could write your own database backend based upon the standard DrupalDatabaseCache one and then write any or all sorts of logic into your clear() function. A quick look would suggest this to be a reasonably straightforward in D7 (just copy the class to your custom name and modify it etc by throwing in a module_invoke_all() as appropriate) and with the cache_backport module would even work in D6. Then point any cache bins you want fancified on clear and you should be on your way.

4
  • 3
    This is probably the best solution, only 'problem' being that if you have multiple cache bins (memcache, redis, etc) you have to extend several cache classes. Still worth it though
    – Clive
    Commented Sep 2, 2013 at 16:26
  • Would not work with cache in memcached, apc or other non-db solution, would it?
    – Mołot
    Commented Sep 3, 2013 at 7:03
  • I use Redis, not sure that would work. Commented Sep 3, 2013 at 10:22
  • If you are using drupal.org/project/redis you should be able to just copy or otherwise modify the supplied classes etc in a custom module and then use them instead. If, though, you are using something along the lines of the Pantheon platform where they provide all the heavy lifting for redis, then yes, you'd need to coordinate with them about all of this.
    – Jimajamma
    Commented Sep 3, 2013 at 13:33
3

If you look at the source for drupal_flush_all_caches() and clear_cache_all(), you will see that no hooks are invoked post clearing, which is a pretty bug bummer.

It is very difficult to guarantee that a user will never have to wait for some cache entries to be built, so I try to avoid full cache clears as much as possible.

One method that really helps is to form alter the performance page to wire up a submit handler that just clears the forward facing caches, and doesn't touch the menus, registry, and similar core caches. I have had good results with this, as menu and registry rebuild take up about half of the time for a full cache rebuild.

The other thing that I have a drush script that does a drupal_http_request() on all of my URLs (not just the important ones) so that everything gets cached. How this is done varies by site. Sometimes I can just EFQ the published nodes, and build up URLs that way. Other times, you can query the XML sitemap tables to get your URL. I then call this from my system cron as often as I need.

2

This may not be suitable for everyone, and may not be quick enough for the OP — as it is only triggered on the next page initialisation. However, it helped me with triggering code just after a "cache clear all" that wasn't time sensitive.

Obviously HOOK needs replacing with your own module name.

/**
 * Implements hook_init().
 */
function HOOK_init(){
  // if there is no cache_not_empty defined, define it 
  // and then trigger our cache cleared code
  if ( !cache_get('HOOK_cache_not_empty') ) {
    cache_set('HOOK_cache_not_empty', TRUE);
    foreach (module_implements('cache_cleared') as $module) {
      module_invoke($module, 'cache_cleared');
    }
  }
}

/**
 * Implements hook_cache_cleared().
 */
function HOOK_cache_cleared(){
  // do what you need here, in which ever module.
}

If you have a specific bin you need to target, the above could be modified to support that, as long as the entire bin gets emptied at the point of your cache clear.

hook_init is only executed for non-cached pages. Although because a full cache clear should mean no cached pages, this shouldn't cause a problem. However, external caching systems like Varnish will get in the way of this triggering, and will mean it'll only occur when the next proper request reaches back to Drupal.

It should also be noted that depending on your caching system — on exactly when a cache_set becomes available to all concurrent users — that this hook could be triggered multiple times concurrently, especially if you have large numbers of users.

1
  • I'm reading this 8 years later, but still... genius! Commented Mar 15, 2023 at 20:39
1

Couple of options:
https://www.drupal.org/project/cache_graceful might be exactly what you want.

https://www.drupal.org/project/apdqc has 2 hooks that fire on a cache clear allowing you to alter the clear drupal_alter('apdqc_cache_clear', $cid, $wildcard, $this->bin, $caller); and after allowing you to react to the clear module_invoke_all('apdqc_cache_clear', $cid, $wildcard, $this->bin, $caller);. Get APDQC working correctly and set $conf['apdqc_call_hook_on_clear'] = TRUE; in your settings.php file & then the hooks should be called whenever a cache clear is done.

0

I had a similar need, where a client wanted to flush both Drupal and Varnish caches when they hit the "flush all caches" button. I hijacked that menu item to do so.

This won't hit any cache clears on cron or anywhere else - just on the menu link.

/**
 * Implements hook_menu_alter().
 */
function mymodule_menu_alter(&$items) {
  if (isset($items['admin_menu/flush-cache'])) {
    $items['admin_menu/flush-cache']['page callback'] =
      "_mymodule_custom_flush_cache";
  }
}

/**
 * Hijacks the "flush all caches" button in menu
 */
function _mymodule_custom_flush_cache() {
  /**
   * Clear varnish, or other logic here
   */
  admin_menu_flush_cache(); //Run the normal cache clearing stuff
}
1
  • Thx Travis, but i''m looking for a way that hooks on any clear caching, not only the one triggered volontarily by a user. Commented Feb 11, 2015 at 23:27
0

You may like to try https://www.drupal.org/project/recacher — it uses Cache Expiration module to detect expired pages and then re-cache only those pages using the excellent HTTPRL.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.