From cc51e0a889ddd73792d333a89dd3792ef3857db7 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 21 Nov 2013 12:18:29 +0100 Subject: [PATCH 1/2] Skip tags in Zend_Cache for Aggregate if not available Allows usage of one consistent Zend_Cache backend for all SilverStripe core storage, greatly simplifying its configuration. This means a call to Aggregate::flushCache() will indeed flush all caches if the backend doesn't support tags, including any custom caches defined through SS_Cache. Given caches are regarded transient that's an acceptable limitation. --- model/Aggregate.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/model/Aggregate.php b/model/Aggregate.php index 372843299..84cc105e2 100644 --- a/model/Aggregate.php +++ b/model/Aggregate.php @@ -43,19 +43,24 @@ class Aggregate extends ViewableData { protected static function cache() { return self::$cache ? self::$cache : (self::$cache = SS_Cache::factory('aggregate')); } - - /** Clear the aggregate cache for a given type, or pass nothing to clear all aggregate caches */ + + /** + * Clear the aggregate cache for a given type, or pass nothing to clear all aggregate caches. + * {@link $class} is just effective if the cache backend supports tags. + */ public static function flushCache($class=null) { $cache = self::cache(); - - if (!$class || $class == 'DataObject') { + $capabilities = $cache->getBackend()->getCapabilities(); + if($capabilities['tags'] && (!$class || $class == 'DataObject')) { $cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array('aggregate')); - } else { + } elseif($capabilities['tags']) { $tags = ClassInfo::ancestry($class); foreach($tags as &$tag) { $tag = preg_replace('/[^a-zA-Z0-9_]/', '_', $tag); } $cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, $tags); + } else { + $cache->clean(Zend_Cache::CLEANING_MODE_ALL); } } From 78f74737b6e9870d78e2387738e49ae43792c496 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 21 Nov 2013 14:13:51 +0100 Subject: [PATCH 2/2] Improved SS_Cache docs, moved to /docs We keep having trouble with extended PHPDoc formatting, and have similar topics covered in Markdown already. Moved docs, added some details about timeouts and the theory of caching backends. --- cache/Cache.php | 103 +------------------------------ docs/en/topics/caching.md | 125 +++++++++++++++++++++++++++++++++++++- 2 files changed, 125 insertions(+), 103 deletions(-) diff --git a/cache/Cache.php b/cache/Cache.php index b953523b9..acf8621a3 100644 --- a/cache/Cache.php +++ b/cache/Cache.php @@ -1,109 +1,10 @@ Using a cache - * - * - * // foo is any name (try to be specific), and is used to get configuration - * // & storage info - * $cache = SS_Cache::factory('foo'); - * - * if (!($result = $cache->load($cachekey))) { - * $result = caluate some how; - * $cache->save($result); - * } - * - * return $result; - * * - * Normally there's no need to remove things from the cache - the cache - * backends clear out entries based on age & maximum allocated storage. If you - * include the version of the object in the cache key, even object changes - * don't need any invalidation. - * - *

Disabling cache in dev mode

- * - * - * // _config.php - * if (Director::isDev()) { - * SS_Cache::set_cache_lifetime('any', -1, 100); - * // - * - * - *

Using memcached as a store

- * - * - * // _config.php - * SS_Cache::add_backend( - * 'primary_memcached', - * 'Memcached', - * array( - * 'host' => 'localhost', - * 'port' => 11211, - * 'persistent' => true, - * 'weight' => 1, - * 'timeout' => 5, - * 'retry_interval' => 15, - * 'status' => true, - * 'failure_callback' => '' - * ) - * ); - * - * SS_Cache::pick_backend('primary_memcached', 'any', 10); - * - * // Aggregate needs a backend with tag support, which memcached doesn't - * // provide - * SS_Cache::pick_backend('default', 'aggregate', 20); - * - * - *

Using APC as a store

- * - * - * SS_Cache::add_backend('two-level', 'Two-Levels', array( - * 'slow_backend' => 'File', - * 'fast_backend' => 'Apc', - * 'slow_backend_options' => array( - * 'cache_dir' => TEMP_FOLDER . DIRECTORY_SEPARATOR . 'cache' - * ) - * )); - * - * // No need for special backend for aggregate - TwoLevels with a File slow - * // backend supports tags - * SS_Cache::pick_backend('two-level', 'Two-Levels', 10); - * - * - *

Invalidate an element

- * - * - * $cache = SS_Cache::factory('foo'); - * $cache->remove($cachekey); - * - * - *

Clear the cache

- * - * This clears the entire backend, not just this named cache partition. - * - * - * $cache = SS_Cache::factory('foo'); - * $cache->clean(Zend_Cache::CLEANING_MODE_ALL); - * + * @see http://doc.silverstripe.org/framework/en/topics/caching * * @package framework * @subpackage core diff --git a/docs/en/topics/caching.md b/docs/en/topics/caching.md index 61a6a4225..26c8d55e4 100644 --- a/docs/en/topics/caching.md +++ b/docs/en/topics/caching.md @@ -21,6 +21,127 @@ executing the action is limited to the following cases when performed via a web * A user is logged in with ADMIN permissions * An error occurs during startup -## Custom Caches +## The Cache API -See `[api:SS_Cache]`. \ No newline at end of file +The `[api:SS_Cache]` class provides a bunch of static functions wrapping the Zend_Cache system +in something a little more easy to use with the SilverStripe config system. + +A `Zend_Cache` has both a frontend (determines how to get the value to cache, +and how to serialize it for storage) and a backend (handles the actual +storage). + +Rather than require library code to specify the backend directly, cache +consumers provide a name for the cache backend they want. The end developer +can then specify which backend to use for each name in their project's +configuration. They can also use 'all' to provide a backend for all named +caches. + +End developers provide a set of named backends, then pick the specific +backend for each named cache. There is a default File cache set up as the +'default' named backend, which is assigned to 'all' named caches. + +## Using Caches + +Caches can be created and retrieved through the `SS_Cache::factory()` method. +The returned object is of type `Zend_Cache`. + + :::php + // foo is any name (try to be specific), and is used to get configuration + // & storage info + $cache = SS_Cache::factory('foo'); + if (!($result = $cache->load($cachekey))) { + $result = caluate some how; + $cache->save($result); + } + return $result; + +Normally there's no need to remove things from the cache - the cache +backends clear out entries based on age and maximum allocated storage. If you +include the version of the object in the cache key, even object changes +don't need any invalidation. You can force disable the cache though, +e.g. in development mode. + + :::php + // Disables all caches + SS_Cache::set_cache_lifetime('any', -1, 100); + +You can also specifically clean a cache. +Keep in mind that `Zend_Cache::CLEANING_MODE_ALL` deletes all cache +entries across all caches, not just for the 'foo' cache in the example below. + + :::php + $cache = SS_Cache::factory('foo'); + $cache->clean(Zend_Cache::CLEANING_MODE_ALL); + +A single element can be invalidated through its cache key. + + :::php + $cache = SS_Cache::factory('foo'); + $cache->remove($cachekey); + +In order to increase the chance of your cache actually being hit, +it often pays to increase the lifetime of caches ("TTL"). +It defaults to 10 minutes (600s) in SilverStripe, which can be +quite short depending on how often your data changes. +Keep in mind that data expiry should primarily be handled by your cache key, +e.g. by including the `LastEdited` value when caching `DataObject` results. + + :::php + // set all caches to 3 hours + SS_Cache::set_cache_lifetime('any', 60*60*3); + +## Alternative Cache Backends + +By default, SilverStripe uses a file-based caching backend. +Together with a file stat cache like [APC](http://us2.php.net/manual/en/book.apc.php) +this is reasonably quick, but still requires access to slow disk I/O. +The `Zend_Cache` API supports various caching backends ([list](http://framework.zend.com/manual/1.12/en/zend.cache.backends.html)) +which can provide better performance, including APC, Xcache, ZendServer, Memcached and SQLite. + +### Memcached + +This backends stores cache records into a [memcached](http://www.danga.com/memcached/) +server. memcached is a high-performance, distributed memory object caching system. +To use this backend, you need a memcached daemon and the memcache PECL extension. + + :::php + // _config.php + SS_Cache::add_backend( + 'primary_memcached', + 'Memcached', + array( + 'host' => 'localhost', + 'port' => 11211, + 'persistent' => true, + 'weight' => 1, + 'timeout' => 5, + 'retry_interval' => 15, + 'status' => true, + 'failure_callback' => '' + ) + ); + SS_Cache::pick_backend('primary_memcached', 'any', 10); + +### APC + +This backends stores cache records in shared memory through the [APC](http://pecl.php.net/package/APC) + (Alternative PHP Cache) extension (which is of course need for using this backend). + + :::php + SS_Cache::add_backend('primary_apc', 'APC'); + SS_Cache::pick_backend('primary_apc', 'any', 10); + +### Two-Levels + +This backend is an hybrid one. It stores cache records in two other backends: +a fast one (but limited) like Apc, Memcache... and a "slow" one like File or Sqlite. + + :::php + SS_Cache::add_backend('two_level', 'Two-Levels', array( + 'slow_backend' => 'File', + 'fast_backend' => 'APC', + 'slow_backend_options' => array( + 'cache_dir' => TEMP_FOLDER . DIRECTORY_SEPARATOR . 'cache' + ) + )); + SS_Cache::pick_backend('two_level', 'any', 10); \ No newline at end of file