Skip to content

Commit

Permalink
DOC Document changes to caching (#543)
Browse files Browse the repository at this point in the history
  • Loading branch information
GuySartorelli authored Jul 12, 2024
1 parent 9c02f9c commit 5b4516f
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 36 deletions.
5 changes: 3 additions & 2 deletions en/00_Getting_Started/00_Server_Requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions en/00_Getting_Started/03_Environment_Management.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`). |
Expand Down
2 changes: 1 addition & 1 deletion en/02_Developer_Guides/05_Extending/05_Injector.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
92 changes: 61 additions & 31 deletions en/02_Developer_Guides/08_Performance/01_Caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions en/02_Developer_Guides/16_Execution_Pipeline/02_Manifests.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
43 changes: 43 additions & 0 deletions en/08_Changelogs/6.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 5b4516f

Please sign in to comment.