From 1b182514e811391e93409611376feaf4099a3905 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Mon, 8 Jul 2024 11:57:59 +1200 Subject: [PATCH] DOC Document changes to caching --- .../00_Server_Requirements.md | 5 +- .../03_Environment_Management.md | 1 + .../05_Extending/05_Injector.md | 2 +- .../08_Performance/01_Caching.md | 92 ++++++++++++------- .../16_Execution_Pipeline/02_Manifests.md | 6 +- en/08_Changelogs/6.0.0.md | 43 +++++++++ 6 files changed, 113 insertions(+), 36 deletions(-) diff --git a/en/00_Getting_Started/00_Server_Requirements.md b/en/00_Getting_Started/00_Server_Requirements.md index 6d1a873d7..f10ab9519 100644 --- a/en/00_Getting_Started/00_Server_Requirements.md +++ b/en/00_Getting_Started/00_Server_Requirements.md @@ -231,8 +231,9 @@ See [silverstripe/vendor-plugin](https://github.com/silverstripe/vendor-plugin) Silverstripe CMS relies on various [caches](/developer_guides/performance/caching/) to achieve performant responses. By default, those caches are stored in a temporary filesystem folder, and are not -shared between multiple server instances. Alternative cache backends such as Redis can be -[configured](/developer_guides/performance/caching/). +shared between multiple server instances. + +No in-memory cache is used by default. To improve performance, we recommend [configuring an in-memory cache](/developer_guides/performance/caching/#adapters) such as Redis or Memcached. While cache objects can expire, when using filesystem caching the files are not actively pruned. For long-lived server instances, this can become a capacity issue over time - see diff --git a/en/00_Getting_Started/03_Environment_Management.md b/en/00_Getting_Started/03_Environment_Management.md index ad6bc346c..5cd17a036 100644 --- a/en/00_Getting_Started/03_Environment_Management.md +++ b/en/00_Getting_Started/03_Environment_Management.md @@ -126,6 +126,7 @@ Silverstripe core environment variables are listed here, though you're free to d | `SS_DATABASE_MEMORY` | Used for [SQLite3](https://github.com/silverstripe/silverstripe-sqlite3) database connectors. | | `SS_TRUSTED_PROXY_IPS` | IP address or CIDR range to trust proxy headers from. If left blank no proxy headers are trusted. Can be set to 'none' (trust none) or '*' (trust all). See [Request hostname forgery](/developer_guides/security/secure_coding/#request-hostname-forgery) for more information. | | `SS_ALLOWED_HOSTS` | A comma deliminated list of hostnames the site is allowed to respond to. See [Request hostname forgery](/developer_guides/security/secure_coding/#request-hostname-forgery) for more information. | +| `SS_IN_MEMORY_CACHE_FACTORY` | The [`InMemoryCacheFactory`](api:SilverStripe\Core\Cache\InMemoryCacheFactory) class to use for instantiating in-memory cache adapters. See [caching adapters](/developer_guides/performance/caching/#adapters) for more details. | | `SS_MANIFESTCACHE` | The manifest cache to use (defaults to file based caching). Must be a `Psr\Cache\CacheItemPoolInterface`, `Psr\SimpleCache\CacheInterface`, or `SilverStripe\Core\Cache\CacheFactory` class implementation. | | `SS_IGNORE_DOT_ENV` | If set, the `.env` file will be ignored. This is good for live to mitigate any performance implications of loading the `.env` file. | | `SS_BASE_URL` | The URL to use when it isn't determinable by other means (for example for CLI commands). Should either start with a protocol (e.g. `https://www.example.com`) or with a double forward slash (e.g. `//www.example.com`). | diff --git a/en/02_Developer_Guides/05_Extending/05_Injector.md b/en/02_Developer_Guides/05_Extending/05_Injector.md index bab12f39c..b606eaca6 100644 --- a/en/02_Developer_Guides/05_Extending/05_Injector.md +++ b/en/02_Developer_Guides/05_Extending/05_Injector.md @@ -466,7 +466,7 @@ use SilverStripe\Core\Injector\Factory; class MyFactory implements Factory { - public function create($service, array $params = []) + public function create(string $service, array $params = []): object { return new MyServiceImplementation(...$params); } diff --git a/en/02_Developer_Guides/08_Performance/01_Caching.md b/en/02_Developer_Guides/08_Performance/01_Caching.md index 2c55ad956..db18e787d 100644 --- a/en/02_Developer_Guides/08_Performance/01_Caching.md +++ b/en/02_Developer_Guides/08_Performance/01_Caching.md @@ -56,7 +56,7 @@ SilverStripe\Core\Injector\Injector: > Please read the [versioned cache segmentation](#versioned-cache-segmentation) section for more information. Cache objects are instantiated through a [CacheFactory](api:SilverStripe\Core\Cache\CacheFactory), -which determines which cache adapter is used (see "Adapters" below for details). +which determines which cache adapter is used (see [adapters](#adapters) below for details). This factory allows us you to globally define an adapter for all cache instances. ```php @@ -180,44 +180,74 @@ interface. Use this interface to trigger `clear()` on your caches. ## Adapters -Silverstripe CMS tries to identify the most performant cache available on your system -through the [DefaultCacheFactory](api:SilverStripe\Core\Cache\DefaultCacheFactory) implementation: +We use the `smyfony/cache` library which supports various [cache adapters](https://github.com/symfony/cache/tree/6.1/Adapter). + +Silverstripe CMS tries to provide a sensible default cache implementation for your system +through the [`DefaultCacheFactory`](api:SilverStripe\Core\Cache\DefaultCacheFactory) implementation. - `PhpFilesAdapter` (PHP with [opcache](https://php.net/manual/en/book.opcache.php) enabled). This cache has relatively low [memory defaults](https://php.net/manual/en/opcache.configuration.php#ini.opcache.memory-consumption). We recommend increasing it for large applications, or enabling the - [file_cache fallback](https://php.net/manual/en/opcache.configuration.php#ini.opcache.file-cache) -- `ApcuAdapter` (requires APC) with a `FilesystemAdapter` fallback (for larger cache volumes) -- `FilesystemAdapter` if none of the above is available + [`file_cache` fallback](https://php.net/manual/en/opcache.configuration.php#ini.opcache.file-cache). + You must have [`opcache.enable_cli`](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.enable-cli) set to `true` + to use this cache adapter. This is so that your cache is shared between CLI and the webserver. +- `FilesystemAdapter` if the above isn't available -The library supports various [cache adapters](https://github.com/symfony/cache/tree/6.1/Adapter) -which can provide better performance, particularly in multi-server environments with shared caches like Memcached. +### Adding an in-memory cache adapter -Since we're using dependency injection to create caches, -you need to define a factory for a particular adapter, -following the `SilverStripe\Core\Cache\CacheFactory` interface. -Different adapters will require different constructor arguments. -We've written factories for the most common cache scenarios: -`FilesystemCacheFactory`, `MemcachedCacheFactory` and `ApcuCacheFactory`. +The cache adapter needs to be available before configuration has been loaded, so we use an environment variable to determine which class will be used to instantiate the in-memory cache adapter. The class must be referenced using its Fully Qualified Class Name (including namespace), and must be an instance of [`InMemoryCacheFactory`](api:SilverStripe\Core\Cache\InMemoryCacheFactory). -Example: Configure core caches to use [memcached](https://www.danga.com/memcached/), -which requires the [memcached PHP extension](https://php.net/memcached), -and takes a `MemcachedClient` instance as a constructor argument. +```bash +SS_IN_MEMORY_CACHE_FACTORY="SilverStripe\Core\Cache\MemcachedCacheFactory" +``` -```yml ---- -After: '#versionedcache' ---- -SilverStripe\Core\Injector\Injector: - MemcachedClient: - class: 'Memcached' - calls: - - [ addServer, [ 'localhost', 11211 ] ] - MemcachedCacheFactory: - class: 'SilverStripe\Core\Cache\MemcachedCacheFactory' - constructor: - client: '%$MemcachedClient' - SilverStripe\Core\Cache\CacheFactory: '%$MemcachedCacheFactory' +Silverstripe CMS comes with three in-memory cache factories you can choose from, each with their own requirements. You can of course add your own, as well. + +#### Memcached cache + +[Memcached](https://www.danga.com/memcached/) is a "high-performance, distributed memory object caching system". To use this cache, you must install the [memcached PHP extension](https://php.net/memcached). + +You can tell the cache adapter which Memcached server(s) to connect to by defining a DSN in the `SS_MEMCACHED_DSN` environment variable. + +> [!TIP] +> The format for the DSN is exactly as defined in [the Symfony documentation](https://symfony.com/doc/current/components/cache/adapters/memcached_adapter.html#configure-the-connection). + +```bash +SS_IN_MEMORY_CACHE_FACTORY="SilverStripe\Core\Cache\MemcachedCacheFactory" +SS_MEMCACHED_DSN="memcached://localhost:11211" +``` + +#### Redis cache + +[Redis](https://redis.io/) is an "in-memory database for caching and streaming" with built-in replication and a lot of deployment options. To use this cache, you must install one of: + +- [predis/predis](https://github.com/predis/predis/) +- [cachewerk/relay](https://github.com/cachewerk/relay) +- [the Redis PHP extension](https://github.com/phpredis/phpredis) + +You can tell the cache adapter which Redis server(s) to connect to by defining a DSN in the `SS_REDIS_DSN` environment variable. + +> [!TIP] +> The format for the DSN is exactly as defined in [the Symfony documentation](https://symfony.com/doc/current/components/cache/adapters/redis_adapter.html#configure-the-connection). + +```bash +SS_IN_MEMORY_CACHE_FACTORY="SilverStripe\Core\Cache\RedisCacheFactory" +SS_REDIS_DSN="redis://verysecurepassword@localhost:6379" +``` + +#### APCu cache + +APCu is "an in-memory key-value store for PHP". This runs locally on the same computer as your webserver, and is only available for the webserver itself. To use this cache, you must install the [APCu PHP extension](https://www.php.net/apcu). + +> [!WARNING] +> APCu cache cannot be shared with your CLI, which means flushing cache from the terminal will not flush the cache your webserver uses. +> +> The filesystem cache will still be used as a backup, which *is* shared with CLI, so flushing the cache with a web request will always flush the cache CLI uses. + +We include this option because it requires very little effort to set up, so it may be appropriate for smaller projects. Just remember to always flush the cache with an HTTP request using `?flush=1`. + +```bash +SS_IN_MEMORY_CACHE_FACTORY="SilverStripe\Core\Cache\ApcuCacheFactory" ``` ## Versioned cache segmentation diff --git a/en/02_Developer_Guides/16_Execution_Pipeline/02_Manifests.md b/en/02_Developer_Guides/16_Execution_Pipeline/02_Manifests.md index 85487eb04..49b33420c 100644 --- a/en/02_Developer_Guides/16_Execution_Pipeline/02_Manifests.md +++ b/en/02_Developer_Guides/16_Execution_Pipeline/02_Manifests.md @@ -20,11 +20,13 @@ which need to be generated and expired during runtime. ## Storage -By default, manifests are serialised and cached via a cache generated by the [ManifestCacheFactory](api:SilverStripe\Core\Cache\ManifestCacheFactory). +By default, manifests are serialised and cached via a cache generated by the [`ManifestCacheFactory`](api:SilverStripe\Core\Cache\ManifestCacheFactory). This can be customised via `SS_MANIFESTCACHE` environment variable to point to either another -[CacheFactory](api:SilverStripe\Core\Cache\CacheFactory) or a class that implements the `CacheInterface` +[`CacheFactory`](api:SilverStripe\Core\Cache\CacheFactory) or a class that implements the `CacheInterface` interface provided by [php-fig/cache](https://github.com/php-fig/cache). +See [caching](/developer_guides/performance/caching/) for more information about caching, including setting up in-memory cache adapters. + ## Traversing the filesystem Since manifests usually extract their information from files in the project root, diff --git a/en/08_Changelogs/6.0.0.md b/en/08_Changelogs/6.0.0.md index 25e550757..95509b80a 100644 --- a/en/08_Changelogs/6.0.0.md +++ b/en/08_Changelogs/6.0.0.md @@ -8,11 +8,13 @@ title: 6.0.0 (unreleased) - [Features and enhancements](#features-and-enhancements) - [Run `CanonicalURLMiddleware` in all environments by default](#url-middleware) + - [Changes to default cache adapters](#caching) - [Changes to scaffolded form fields](#scaffolded-fields) - [Other new features](#other-new-features) - [Bug fixes](#bug-fixes) - [API changes](#api-changes) - [Most extension hook methods are now protected](#hooks-protected) + - [Strict typing for `Factory` implementations](#factory-strict-typing) - [General changes](#api-general) ## Features and enhancements @@ -32,6 +34,20 @@ CanonicalURLMiddleware::singleton()->setEnabledEnvs([ ]); ``` +### Changes to default cache adapters {#caching} + +The [`DefaultCacheFactory`](api:SilverStripe\Core\Cache\DefaultCacheFactory) used to use APCu cache by default for your webserver - but we found this cache isn't actually shared with CLI. This means flushing cache from the CLI didn't actually flush the cache your webserver was using. + +What's more, the `PHPFilesAdapter` used as a fallback wasn't enabled for CLI, which resulted in having a different filesystem cache for CLI than is used for the webserver. + +We've made the following changes to resolve this problem: + +- The `PHPFilesAdapter` will only be used if it's available for both the webserver *and* CLI. Otherwise, `FilesystemAdapter` will be used for both. +- There is no default in-memory cache used by `DefaultCacheFactory`. +- Cache factories have been implemented for Redis, Memcached, and APCu, with a new [`InMemoryCacheFactory`](api:SilverStripe\Core\Cache\InMemoryCacheFactory) interface available for your own implementations. + +We strongly encourage you to configure an appropriate in-memory cache for your use-case. See [adding an in-memory cache adapter](/developer_guides/performance/caching/#adapters) for details. + ### Changes to scaffolded form fields {#scaffolded-fields} Some of the form fields have changed when scaffolding form fields for relations. @@ -68,6 +84,33 @@ This release includes a number of bug fixes to improve a broad range of areas. C Core implementations of most extension hooks such as `updateCMSFields()` now have protected visibility. Formally they had public visibility which meant they could be called directly which was not how they were intended to be used. Extension hook implementations are still able to be declared public in project code, though it is recommended that all extension hook methods are declared protected in project code to follow best practice. +### Strict typing for `Factory` implementations {#factory-strict-typing} + +The [`Factory::create()`](api:SilverStripe\Core\Injector\Factory::create()) method now has strict typehinting. The first argument must be a string, and either `null` or an object must be returned. + +One consequence of this is that you can no longer directly pass an instantiated anonymous class object into [`Injector::load()`](SilverStripe\Core\Injector\Injector::load()). Instead, you need to get the class name using [`get_class()`](https://www.php.net/manual/en/function.get-class.php) and pass that in as the class. + +```php +use App\ClassToReplace; +use SilverStripe\Core\Injector\Injector; + +// Use `get_class()` to get the class name for your anonymous class +$replacementClass = get_class(new class () { + private string $property; + + public function __construct(string $value = null) + { + $this->property = $value; + } +}); + +Injector::inst()->load([ + ClassToReplace::class => [ + 'class' => $replacementClass, + ], +]); +``` + ### General changes {#api-general} - [`DataObject::write()`](api:SilverStripe\ORM\DataObject::write()) has a new boolean `$skipValidation` parameter. This can be useful for scenarios where you want to automatically create a new record with no data initially without restricting how developers can set up their validation rules.