Storages
The storage decides where cached data physically lives: in files on disk, on a Redis or Memcached server, in an SQLite database or in the PSR-16 cache of another framework. You can switch it without touching the code that uses the cache. This page helps you:
- choose the right storage and see what each one supports
- configure it in a Nette application, or turn the cache off
- use a Nette storage as a PSR-16 cache, and the other way round
- write a storage of your own
Choosing a Storage
If you are not sure, keep the default FileStorage. It needs nothing but a directory, it is safe when many requests
read and write at once, and it supports every kind of dependency.
Reach for RedisStorage when your application runs on several servers. With files, each server would have its own
cache, and deleting an article by its tag on one server would leave the stale copies on the others. A Redis server is shared by
all of them.
The storages differ in which dependencies they can honor:
| Storage | Tags, priorities | Files, constants, callbacks | Other items | Sliding | Cleaning namespaces | Stampede protection |
|---|---|---|---|---|---|---|
| FileStorage | with journal | yes | yes | yes | yes | yes |
| RedisStorage | yes | yes | no | yes | yes | yes |
| MemcachedStorage | with journal | yes | no | yes | no | no |
| SQLiteStorage | tags only | no | no | yes | yes | no |
| MemoryStorage | yes | ignored | ignored | yes | yes | no |
| Psr16Storage | with journal | yes | no | no | no | no |
| DevNullStorage | stores nothing |
A storage that cannot honor a dependency throws Nette\NotSupportedException instead of silently ignoring it, so
you find out right away, not from stale data. The only exception is MemoryStorage, which ignores what it
cannot do.
Stampede protection means that when many requests ask for a missing item at once, only the first one runs the generator of
load() and the others wait for its result. Without it, all of them compute the value at the same time.
Configuration
In a Nette application, the DI container provides a FileStorage that stores data in the cache
subdirectory of the directory for temporary
files, together with a journal. You get it via dependency
injection by asking for the type Nette\Caching\Storage.
To use another storage, replace the service cache.storage in the configuration:
services:
cache.storage: Nette\Caching\Storages\RedisStorage('10.0.0.5')
The container holds these two services:
| Name | Type | Description |
|---|---|---|
cache.storage |
Nette\Caching\Storage | the storage |
cache.journal |
Nette\Caching\Storages\Journal | the journal for tags and priorities |
Turning Off the Cache
To turn the cache off, for example in tests, use DevNullStorage, which stores nothing:
services:
cache.storage: Nette\Caching\Storages\DevNullStorage
This does not affect the caches of Latte templates and of the DI container, which are separate and do not use this storage. They do not need to be turned off during development either.
FileStorage
Nette\Caching\Storages\FileStorage writes each item into a file in the given directory, which must exist. Tags and
priorities need a journal:
$journal = new Nette\Caching\Storages\SQLiteJournal('/path/to/temp/journal.s3db');
$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp', $journal);
All operations are atomic. You can never read a file another request has not finished writing yet, and nobody can delete it while you are reading it. That makes the storage safe even under heavy load.
It also protects against the cache stampede. When many requests ask for a missing item at once, for example right after a deployment, the first one locks the file and runs the generator. The others wait for the lock and then read the fresh value instead of computing it again.
Expired files are removed gradually. Now and then, the constructor spends at most 20 ms deleting them, so even a large cache
does not noticeably slow down a request. To remove all expired files at once, call $storage->clean([]).
Data is stored with PHP's serialize(). If you prefer another format, create a subclass and override the methods
serializeData() and unserializeData().
RedisStorage
Nette\Caching\Storages\RedisStorage stores data on a Redis server. It keeps tags
and priorities itself, so it needs no journal, and it protects against the cache stampede just like FileStorage.
Requires the redis PHP extension.
services:
cache.storage: Nette\Caching\Storages\RedisStorage('10.0.0.5')
The constructor takes the host and the port, 6379 by default. If you already have a connected Redis object, pass
it instead of the host.
All keys get the prefix nette.cache:, so the database can be shared with other data; the parameter
prefix changes it. An empty prefix means that the whole database belongs to the cache, and clearing the cache then
empties the database.
While one process generates a missing item, the others wait for it, at most for 30 seconds by default (the parameter
lockTimeout). A generator that takes longer only means that another process computes the same value too, never that
a wrong value is stored.
The storage does not support the dependency on other items.
MemcachedStorage
Nette\Caching\Storages\MemcachedStorage stores data on a Memcached server. The
constructor takes the host and the port, 11211 by default. Tags and priorities need a journal.
Requires the memcached PHP extension.
services:
cache.storage: Nette\Caching\Storages\MemcachedStorage('10.0.0.5')
Memcached cannot list its keys, so the storage cannot clean namespaces. It does not support the dependency on other items either. Clearing the whole cache empties the entire Memcached server, including the data of other applications.
When Memcached refuses to store an item, typically one larger than its size limit, the storage triggers a warning.
SQLiteStorage
Nette\Caching\Storages\SQLiteStorage keeps the whole cache in a single SQLite database file. It stores tags in its
own table, so it needs no journal.
Requires the pdo and pdo_sqlite PHP extensions.
services:
cache.storage: Nette\Caching\Storages\SQLiteStorage('%tempDir%/cache.db')
The database runs in the WAL mode, in which reading does not wait for writing. SQLite then creates two more files next to the database, so PHP must be able to write to its directory.
The storage supports expiration, sliding expiration, tags and cleaning namespaces. It does not support priorities, nor dependencies on files, constants, callbacks and other items.
MemoryStorage
Nette\Caching\Storages\MemoryStorage keeps data in a PHP array, so it is lost when the request ends. It is handy
in tests and for caching within a single request.
It honors expiration, sliding expiration, tags, priorities and cleaning namespaces. Dependencies on files, constants, callbacks and other items are silently ignored. Keep that in mind when you use it in tests instead of a real storage: an item that would expire there stays here.
Psr16Storage
Nette\Bridges\Psr\Psr16Storage lets you use any PSR-16 cache as
a Nette storage, for example the cache of another framework your application already uses. Tags and priorities need a journal:
// $simpleCache is any Psr\SimpleCache\CacheInterface
$storage = new Nette\Bridges\Psr\Psr16Storage($simpleCache, journal: $journal);
PSR-16 allows only some characters in keys, so the storage turns every key into a hash. The parameter prefix, at
most 32 letters, digits, underscores and dots, is put in front of each of them.
PSR-16 can neither extend the validity of an item nor list keys, so the storage does not support sliding expiration and
cleaning namespaces. It does not support the dependency on other items either. Clearing the whole cache calls clear()
on the PSR-16 cache, so it also removes the data of anyone else sharing it, whatever the prefix.
DevNullStorage
Nette\Caching\Storages\DevNullStorage stores nothing, so every load() calls the generator. Use it to
turn the cache off.
Journal
FileStorage, MemcachedStorage and Psr16Storage cannot search their items by tag or
priority, so they keep a separate index, the so-called journal. The built-in Nette\Caching\Storages\SQLiteJournal
stores it in an SQLite database:
$journal = new Nette\Caching\Storages\SQLiteJournal('/path/to/temp/journal.s3db');
Requires the pdo and pdo_sqlite PHP extensions.
In a Nette application, the journal is created for you as the service cache.journal in the file
journal.s3db and passed to the storage automatically. Your own implementation of the interface
Nette\Caching\Storages\Journal can replace it:
services:
cache.journal: MyJournal
Without a journal, these storages work too, but storing an item with tags or a priority throws an exception.
Using a Storage as a PSR-16 Cache
Some libraries expect a PSR-16 cache. The adapter
Nette\Bridges\Psr\PsrCacheAdapter gives them one on top of any Nette storage:
$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage);
$psrCache->set('key', 'value', 3600); // stores the value for 1 hour
$value = $psrCache->get('key', 'default');
The adapter supports all methods of PSR-16, including getMultiple(), setMultiple() and
deleteMultiple(). As PSR-16 requires, it rejects an empty key and a key containing any of the characters
{}()/\@:, such as user:1.
The opposite direction, a PSR-16 cache used as a Nette storage, is covered by Psr16Storage.
Writing Your Own Storage
A storage is a class implementing the interface Nette\Caching\Storage with four methods:
read(string $key): mixedreturns the stored value, ornullwhen the item does not exist or has expiredwrite(string $key, mixed $data, array $dependencies): voidstores the valueremove(string $key): voiddeletes the itemclean(array $conditions): voiddeletes items by the conditions of clean(); an empty array means removing the expired items
The dependencies arrive as an array with Cache::* keys, already simplified by Cache: the expiration
is a number of seconds from now, files and constants have become callbacks, and other items are given by their storage keys. When
reading an item, check its callbacks with Nette\Caching\Cache::checkCallbacks(). Throw
Nette\NotSupportedException for a dependency or condition your storage cannot honor.
Optional interfaces add more. Nette\Caching\BulkReader and Nette\Caching\BulkWriter let the storage
read and write several items in one round trip. Nette\Caching\LockingReader adds the method
readOrGenerate(), which protects against the cache stampede: it gets the generator of load() as a
function and has to run it under a lock. The function stores the result itself, through write() or
remove() of the same storage, so these methods must work while the lock is held.
A short complete example is the source code of SQLiteStorage.