Files
CloudObjects-PHP-SDK/docs/caching.md
T

11 KiB

Caching

Standard Caching

Object descriptions that are requested through the ObjectRetriever are fetched through the CloudObjects Core Object API. They are cached in-memory inside the class for a default of 60 seconds. This can be changed using the cache_ttl option, which takes a number of seconds.

use CloudObjects\SDK\ObjectRetriever;

// Keep objects in memory for 5 minutes
$retriever = new ObjectRetriever([
    'cache_ttl' => 300,
]);

This in-class memory obviously only works as long as the class is alive and prevents subsequent requests for the same object hitting the API, but the cache will not exist between multiple requests handled in different processes. For this purpose, an external cache should be added.

To add an external cache, provide a storage using the cache_storage interface. The storage must be a class that implements either of the following interfaces:

  • Kevinrob\GuzzleCache\Storage\CacheStorageInterface
  • Psr\Cache\CacheItemPoolInterface
  • Psr\SimpleCache\CacheInterface

External storage is used twice:

  • Storing the HTTP request according to the APIs caching headers.
  • Storing the object data according to cache_ttl.

All keys written to the external cache are prefixed with the value of the cache_prefix option (default: clobj:), so you can share a cache with other parts of your application.

The following examples use the Symfony Cache component, but any library implementing one of the interfaces above works.

Using a PSR-6 cache pool, e.g. on the filesystem:

use CloudObjects\SDK\ObjectRetriever;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;

$retriever = new ObjectRetriever([
    'cache_storage' => new FilesystemAdapter('cloudobjects', 0, '/var/cache/myapp'),
    'cache_ttl' => 300,
]);

Using a PSR-16 simple cache, e.g. in Redis:

use CloudObjects\SDK\ObjectRetriever;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\Psr16Cache;

$redis = RedisAdapter::createConnection('redis://localhost');

$retriever = new ObjectRetriever([
    'cache_storage' => new Psr16Cache(new RedisAdapter($redis)),
    'cache_prefix' => 'myapp:clobj:',
]);

Fallback Store

Fallback store is a mechanism that can be used by applications that require high availability but may lose connectivity to CloudObjects Core. With fallback store, a copy of an object is saved in a directory. If the API fails with a 5xx error, times out, or the request fails without a response, an ObjectRetriever will return the stored copy instead. Be advised that fallback storage will, once configured, result in your application not noticing when the Object API is unreachable - at least for objects in the store - and will continue to deliver the saved copy even if the original in CloudObjects Core is changed or removed.

It is possible for the fallback store to be configured for all objects or only for objects in specified namespaces. Since the SDK updates stored copies whenever it retrieves an object successfully but never deletes them, not even when the object has been removed from CloudObjects Core, the fallback store can grow large; hence it should be limited to specific namespaces in applications where many random objects may be retrieved. Use this feature with caution and disable it if it causes any problems.

To enable fallback store, set the fallback_store_directory option to a writeable path on the filesystem. Also, set fallback_store_scope to either the string * to save all retrieved objects, or a list of namespaces (domains only without coid:// prefix); the list of namespaces can be provided as a comma-separated string or an array of strings.

Storing all retrieved objects:

use CloudObjects\SDK\ObjectRetriever;

$retriever = new ObjectRetriever([
    'fallback_store_directory' => '/var/lib/myapp/cloudobjects-fallback',
    'fallback_store_scope' => '*',
]);

Storing only objects from specific namespaces, provided as an array:

use CloudObjects\SDK\ObjectRetriever;

$retriever = new ObjectRetriever([
    'fallback_store_directory' => '/var/lib/myapp/cloudobjects-fallback',
    'fallback_store_scope' => [ 'example.com', 'cloudobjects.io' ],
]);

Or as a comma-separated string, which is convenient when the value comes from an environment variable:

use CloudObjects\SDK\ObjectRetriever;

$retriever = new ObjectRetriever([
    'fallback_store_directory' => getenv('FALLBACK_STORE_DIRECTORY'),
    'fallback_store_scope' => 'example.com,cloudobjects.io',
]);

The fallback store can be combined with an external cache. The cache is still consulted first; the fallback store only comes into play when an object is neither cached nor retrievable from the API.

use CloudObjects\SDK\ObjectRetriever;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;

$retriever = new ObjectRetriever([
    'cache_storage' => new FilesystemAdapter('cloudobjects', 0, '/var/cache/myapp'),
    'cache_ttl' => 300,
    'fallback_store_directory' => '/var/lib/myapp/cloudobjects-fallback',
    'fallback_store_scope' => 'example.com',
]);

Permanent Store

Permanent store is a mechanism that can be used by applications that want to minimize connections to CloudObjects Core, may go offline after initialization, or want to work with the same version of an object for the lifetime of their run without getting updates. With permanent store, an ObjectRetriever saves a copy of an object in a directory the first time it retrieves it from the Object API. Once an object is in the permanent store, the API will never be contacted for this object again. Be advised that permanent storage will, once configured, result in your application continuing to deliver the saved copy even if the original in CloudObjects Core is changed or removed, and not noticing when the Object API is unreachable - at least for objects in the store.

It is possible for the permanent store to be configured for all objects or only for objects in specified namespaces. Since the permanent store is never purged or refreshed by the SDK, it can grow large; hence it should be limited to specific namespaces in applications where many random objects may be retrieved. To get the latest version of an object, delete its copy from the directory, or clear the directory entirely. Use this feature with caution and disable it if it causes any problems.

To enable permanent store, set the permanent_store_directory option to a writeable path on the filesystem. Also, set permanent_store_scope to either the string * to save all retrieved objects, or a list of namespaces (domains only without coid:// prefix); the list of namespaces can be provided as a comma-separated string or an array of strings.

Storing all retrieved objects:

use CloudObjects\SDK\ObjectRetriever;

$retriever = new ObjectRetriever([
    'permanent_store_directory' => '/var/lib/myapp/cloudobjects-permanent',
    'permanent_store_scope' => '*',
]);

Storing only objects from specific namespaces, provided as an array:

use CloudObjects\SDK\ObjectRetriever;

$retriever = new ObjectRetriever([
    'permanent_store_directory' => '/var/lib/myapp/cloudobjects-permanent',
    'permanent_store_scope' => [ 'example.com', 'cloudobjects.io' ],
]);

Or as a comma-separated string, which is convenient when the value comes from an environment variable:

use CloudObjects\SDK\ObjectRetriever;

$retriever = new ObjectRetriever([
    'permanent_store_directory' => getenv('PERMANENT_STORE_DIRECTORY'),
    'permanent_store_scope' => 'example.com,cloudobjects.io',
]);

The permanent store can be combined with an external cache. The cache is still consulted first, as it may be faster than the filesystem; the permanent store only comes into play when an object is not cached, and the API is only contacted when the object is in neither.

use CloudObjects\SDK\ObjectRetriever;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;

$retriever = new ObjectRetriever([
    'cache_storage' => new FilesystemAdapter('cloudobjects', 0, '/var/cache/myapp'),
    'cache_ttl' => 300,
    'permanent_store_directory' => '/var/lib/myapp/cloudobjects-permanent',
    'permanent_store_scope' => 'example.com',
]);

Permanent store and fallback store can be combined, for example to pin objects from your own namespace while keeping a fallback copy of everything else. Both stores must use separate directories, otherwise the ObjectRetriever throws an InvalidSDKConfigurationException. If an object is in scope of both, the permanent store takes precedence.

use CloudObjects\SDK\ObjectRetriever;

$retriever = new ObjectRetriever([
    'permanent_store_directory' => '/var/lib/myapp/cloudobjects-permanent',
    'permanent_store_scope' => 'example.com',
    'fallback_store_directory' => '/var/lib/myapp/cloudobjects-fallback',
    'fallback_store_scope' => '*',
]);

Unlike the static object directory, which contains object descriptions that you provide yourself, the permanent store is filled automatically by the SDK from the Object API.

Static Objects

The static object directory is a mechanism that is mostly intended for testing. It allows you to provide object descriptions yourself, for example as fixtures in your test suite, so that your code can be tested without connecting to CloudObjects Core, or with objects that differ from or do not yet exist in CloudObjects Core. It should usually not be configured in production.

To enable it, set the static_object_directory option to a path on the filesystem. Object descriptions are read from <directory>/<namespace>/<path of COID>/object.jsonld, which is the same layout used by the fallback and permanent stores. For example, the object coid://example.com/MyObject is read from <directory>/example.com/MyObject/object.jsonld, and the namespace object coid://example.com itself is read from <directory>/example.com/object.jsonld. Each file must contain the object description in JSON-LD format, with an @id that matches the COID.

{
    "@id": "coid://example.com/MyObject",
    "http://www.w3.org/2000/01/rdf-schema#label": "My Object"
}

The static object directory is consulted before the external cache, the permanent store and the API. Objects that are not found in the directory are retrieved as usual, so you can override only some objects and let the SDK fetch the rest:

use CloudObjects\SDK\ObjectRetriever;

$retriever = new ObjectRetriever([
    'static_object_directory' => __DIR__.'/fixtures/objects',
]);

Static objects are only used by getObjectNode() and the methods built on it; methods that fetch multiple objects from a namespace, such as fetchAllObjectsInNamespace(), always use the API. The SDK never writes to the static object directory, so it is not necessary to specify a scope; any COIDs will be considered for the static store.

In earlier versions of the SDK, this option was called static_config_path. The old name is deprecated but still supported.