Cache backends

A variety of storage backends exists. They have different characteristics and can be used for different caching needs. The best backend depends on a given server setup and hardware, as well as cache type and usage. A backend should be chosen wisely, as a wrong decision could end up actually slowing down a TYPO3 installation.

Backend API

All backends must implement at least interface TYPO3\CMS\Core\Cache\Backend\BackendInterface.

Changed in version 14.0

BackendInterface

interface BackendInterface
Fully qualified name
\TYPO3\CMS\Core\Cache\Backend\BackendInterface

A contract for a Cache Backend

setCache ( \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache)

Sets a reference to the cache frontend which uses this backend

param $cache

The frontend for this backend

set ( ?string $entryIdentifier, ?string $data, array $tags = [], ?int $lifetime = NULL)

Saves data in the cache.

param $entryIdentifier

An identifier for this specific cache entry

param $data

The data to be stored

param $tags

Tags to associate with this cache entry. If the backend does not support tags, this option can be ignored., default: []

param $lifetime

Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited lifetime., default: NULL

get ( ?string $entryIdentifier)

Loads data from the cache.

param $entryIdentifier

An identifier which describes the cache entry to load

Return description

The cache entry's content as a string or FALSE if the cache entry could not be loaded

Returns
mixed
has ( ?string $entryIdentifier)

Checks if a cache entry with the specified identifier exists.

param $entryIdentifier

An identifier specifying the cache entry

Return description

TRUE if such an entry exists, FALSE if not

Returns
bool
remove ( ?string $entryIdentifier)

Removes all cache entries matching the specified identifier.

Usually this only affects one entry but if - for what reason ever - old entries for the identifier still exist, they are removed as well.

param $entryIdentifier

Specifies the cache entry to remove

Return description

TRUE if (at least) an entry could be removed or FALSE if no entry was found

Returns
bool
flush ( )

Removes all cache entries of this cache.

collectGarbage ( )

Does garbage collection

All operations on a specific cache must be done with these methods. There are several further interfaces that can be implemented by backends to declare additional capabilities. Usually, extension code should not handle cache backend operations directly, but should use the frontend object instead.

TaggableBackendInterface

interface TaggableBackendInterface
Fully qualified name
\TYPO3\CMS\Core\Cache\Backend\TaggableBackendInterface

A contract for a cache backend which supports tagging.

flushByTag ( ?string $tag)

Removes all cache entries of this cache which are tagged by the specified tag.

param $tag

The tag the entries must have

flushByTags ( array $tags)

Removes all cache entries of this cache which are tagged by any of the specified tags.

param $tags

List of tags

findIdentifiersByTag ( ?string $tag)

Finds and returns all cache entry identifiers which are tagged by the specified tag

param $tag

The tag to search for

Return description

An array with identifiers of all matching entries. An empty array if no entries matched

Returns
array
setCache ( \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache)

Sets a reference to the cache frontend which uses this backend

param $cache

The frontend for this backend

set ( ?string $entryIdentifier, ?string $data, array $tags = [], ?int $lifetime = NULL)

Saves data in the cache.

param $entryIdentifier

An identifier for this specific cache entry

param $data

The data to be stored

param $tags

Tags to associate with this cache entry. If the backend does not support tags, this option can be ignored., default: []

param $lifetime

Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited lifetime., default: NULL

get ( ?string $entryIdentifier)

Loads data from the cache.

param $entryIdentifier

An identifier which describes the cache entry to load

Return description

The cache entry's content as a string or FALSE if the cache entry could not be loaded

Returns
mixed
has ( ?string $entryIdentifier)

Checks if a cache entry with the specified identifier exists.

param $entryIdentifier

An identifier specifying the cache entry

Return description

TRUE if such an entry exists, FALSE if not

Returns
bool
remove ( ?string $entryIdentifier)

Removes all cache entries matching the specified identifier.

Usually this only affects one entry but if - for what reason ever - old entries for the identifier still exist, they are removed as well.

param $entryIdentifier

Specifies the cache entry to remove

Return description

TRUE if (at least) an entry could be removed or FALSE if no entry was found

Returns
bool
flush ( )

Removes all cache entries of this cache.

collectGarbage ( )

Does garbage collection

PhpCapableBackendInterface

interface PhpCapableBackendInterface
Fully qualified name
\TYPO3\CMS\Core\Cache\Backend\PhpCapableBackendInterface

A contract for a cache backend which is capable of storing, retrieving and including PHP source code.

requireOnce ( ?string $entryIdentifier)

Loads PHP code from the cache and require_onces it right away.

param $entryIdentifier

An identifier which describes the cache entry to load

Return description

Potential return value from the include operation

Returns
mixed
setCache ( \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache)

Sets a reference to the cache frontend which uses this backend

param $cache

The frontend for this backend

set ( ?string $entryIdentifier, ?string $data, array $tags = [], ?int $lifetime = NULL)

Saves data in the cache.

param $entryIdentifier

An identifier for this specific cache entry

param $data

The data to be stored

param $tags

Tags to associate with this cache entry. If the backend does not support tags, this option can be ignored., default: []

param $lifetime

Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited lifetime., default: NULL

get ( ?string $entryIdentifier)

Loads data from the cache.

param $entryIdentifier

An identifier which describes the cache entry to load

Return description

The cache entry's content as a string or FALSE if the cache entry could not be loaded

Returns
mixed
has ( ?string $entryIdentifier)

Checks if a cache entry with the specified identifier exists.

param $entryIdentifier

An identifier specifying the cache entry

Return description

TRUE if such an entry exists, FALSE if not

Returns
bool
remove ( ?string $entryIdentifier)

Removes all cache entries matching the specified identifier.

Usually this only affects one entry but if - for what reason ever - old entries for the identifier still exist, they are removed as well.

param $entryIdentifier

Specifies the cache entry to remove

Return description

TRUE if (at least) an entry could be removed or FALSE if no entry was found

Returns
bool
flush ( )

Removes all cache entries of this cache.

collectGarbage ( )

Does garbage collection

Common options of caching backends

defaultLifetime

defaultLifetime
Type
integer
Default
3600

Default lifetime in seconds of a cache entry if it is not specified for a specific entry on set().

Database Backend

This is the main backend suitable for most storage needs. It does not require additional server daemons nor server configuration.

The database backend does not automatically perform garbage collection. Instead the Scheduler garbage collection task should be used.

It stores data in the configured database (usually MySQL) and can handle large amounts of data with reasonable performance. Data and tags are stored in two different tables, every cache needs its own set of tables. In terms of performance the database backend is already pretty well optimized and should be used as default backend if in doubt. This backend is the default backend if no backend is specifically set in the configuration.

The Core takes care of creating and updating required database tables "on the fly".

For caches with a lot of read and write operations, it is important to tune the MySQL setup. The most important setting is innodb_buffer_pool_size. A generic goal is to give MySQL as much RAM as needed to have the main table space loaded completely in memory.

The database backend tends to slow down if there are many write operations and big caches which do not fit into memory because of slow harddrive seek and write performance. If the data table grows too big to fit into memory, it is possible to compress given data transparently with this backend, which often shrinks the amount of needed space to 1/4 or less. The overhead of the compress/uncompress operation is usually not high. A good candidate for a cache with enabled compression is the Core pages cache: it is only read or written once per request and the data size is pretty large. The compression should not be enabled for caches which are read or written multiple times during one request.

InnoDB Issues

The database backend for MySQL uses InnoDB tables. Due to the nature of InnoDB, deleting records does not reclaim the actual disk space. E.g. if the cache uses 10GB, cleaning it will still keep 10GB allocated on the disk even though phpMyAdmin will show 0 as the cache table size. To reclaim the space, turn on the MySQL option file_per_table, drop the cache tables and re-create them using the Install Tool. This does not by any mean that you should skip the scheduler task. Deleting records still improves performance.

Options of database backends

compression

compression
Type
boolean
Default
false

Whether or not data should be compressed with gzip. This can reduce size of the cache data table, but incurs CPU overhead for compression and decompression.

compressionLevel

compressionLevel
Type
integer from -1 to 9
Default
-1

Gzip compression level (if the compression option is set to true). The default compression level is usually sufficient.

-1
Default gzip compression (recommended)
0
No compression
9
Maximum compression (costs a lot of CPU)

Memcached backend

Memcached is a simple, distributed key/value RAM database. To use this backend, at least one memcached daemon must be reachable, and the PECL module "memcache" must be loaded. There are two PHP memcached implementations: "memcache" and "memcached". Currently, only memcache is supported by this backend.

Limitations of memcached backends

Memcached is a simple key-value store by design . Since the caching framework needs to structure it to store the identifier-data-tags relations, for each cache entry it stores an identifier->data, identifier->tags and a tag->identifiers entry.

This leads to structural problems:

  • If memcache runs out of memory but must store new entries, it will toss some other entry out of the cache (this is called an eviction in memcached speak).
  • If data is shared over multiple memcache servers and some server fails, key/value pairs on this system will just vanish from cache.

Both cases lead to corrupted caches. If, for example, a tags->identifier entry is lost, dropByTag() will not be able to find the corresponding identifier->data entries which should be removed and they will not be deleted. This results in old data delivered by the cache. Additionally, there is currently no implementation of the garbage collection that could rebuild cache integrity.

It is important to monitor a memcached system for evictions and server outages and to clear caches if that happens.

Furthermore memcache has no sort of namespacing. To distinguish entries of multiple caches from each other, every entry is prefixed with the cache name. This can lead to very long runtimes if a big cache needs to be flushed, because every entry has to be handled separately and it is not possible to just truncate the whole cache with one call as this would clear the whole memcached data which might even hold non TYPO3 related entries.

Because of the mentioned drawbacks, the memcached backend should be used with care or in situations where cache integrity is not important or if a cache has no need to use tags at all. Currently, the memcache backend implements the TaggableBackendInterface, so the implementation does allow tagging, even if it is not advised to used this backend together with heavy tagging.

Options for the memcached backend

servers

servers
Type
array
Required

true

Array of used memcached servers. At least one server must be defined. Each server definition is a string, allowed syntaxes:

hostname or IP
TCP connect to host on memcached default port (usually 11211, defined by PHP ini variable memcache.default_port)
hostname:port
TCP connect to host on port
tcp://hostname:port
Same as above
unix:///path/to/memcached.sock
Connect to memcached server using unix sockets

compression

compression
Type
boolean
Default
false

Enable memcached internal data compression. Can be used to reduce memcached memory consumption, but adds additional compression / decompression CPU overhead on the related memcached servers.

Redis Backend

Redis is a key-value storage/database. In contrast to memcached, it allows structured values. Data is stored in RAM but it allows persistence to disk and doesn't suffer from the design problems of the memcached backend implementation. The redis backend can be used as an alternative to the database backend for big cache tables and helps to reduce load on database servers this way. The implementation can handle millions of cache entries each with hundreds of tags if the underlying server has enough memory.

Redis is known to be extremely fast but very memory hungry. The implementation is an option for big caches with lots of data because most important operations perform O(1) in proportion to the number of (redis) keys. This basically means that the access to an entry in a cache with a million entries is not slower than to a cache with only 10 entries, at least if there is enough memory available to hold the complete set in memory. At the moment only one redis server can be used at a time per cache, but one redis instance can handle multiple caches without performance loss when flushing a single cache.

The implementation is based on the PHP phpredis module, which must be available on the system.

Redis example

The Redis caching backend configuration is very similar to that of other backends, but there is one caveat.

TYPO3 caches should be separated in case the same keys are used. This applies to the pages and pagesection caches. Both use "tagIdents:pageId_21566" for a page with an id of 21566. How you separate them is more of a system administrator decision. We provide examples with several databases but this may not be the best option in production where you might want to use multiple cores (which do not support databases). The separation has the additional advantage that caches can be flushed individually.

If you have several of your own caches which each use unique keys (for example by using a different prefix for the cache identifier for each cache), you can store them in the same database, but it is good practice to separate the core caches.

In practical terms, Redis databases should be used to separate different keys belonging to the same application (if needed), and not to use a single Redis instance for multiple unrelated applications.

https://redis.io/commands/select/

config/system/additional.php | typo3conf/system/additional.php
<?php

$redisHost = '127.0.0.1';
$redisPort = 6379;
$redisCaches = [
    'pages' => [
        'defaultLifetime' => 86400 * 7, // 1 week
        'compression' => true,
    ],
    'pagesection' => [
        'defaultLifetime' => 86400 * 7,
    ],
    'hash' => [],
    'rootline' => [],
];

$redisDatabase = 0;
foreach ($redisCaches as $name => $values) {
    $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'][$name]['backend']
        = \TYPO3\CMS\Core\Cache\Backend\RedisBackend::class;
    $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'][$name]['options'] = [
        'database' => $redisDatabase++,
        'hostname' => $redisHost,
        'port' => $redisPort,
    ];
    if (isset($values['defaultLifetime'])) {
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'][$name]['options']['defaultLifetime']
            = $values['defaultLifetime'];
    }
    if (isset($values['compression'])) {
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'][$name]['options']['compression']
            = $values['compression'];
    }
}
Copied!

Options for the redis caching backend

servers

servers
Type
string
Default
127.0.0.1

IP address or name of redis server to connect to.

port

port
Type
integer
Default
6379

Port of the redis daemon.

persistentConnection

persistentConnection
Type
boolean
Default
false

Activate a persistent connection to redis server. This could be a benefit under high load cloud setups.

database

database
Type
integer
Default
0

Number of the database to store entries. Each cache should use its own database, otherwise all caches sharing a database are flushed if the flush operation is issued to one of them. Database numbers 0 and 1 are used and flushed by the Core unit tests and should not be used if possible.

password

password
Type
string

Password used to connect to the redis instance if the redis server needs authentication.

compression

compression
Type
boolean
Default
false

Whether or not data compression with gzip should be enabled. This can reduce cache size, but adds some CPU overhead for the compression and decompression operations in PHP.

compressionLevel

compressionLevel
Type
integer from -1 to 9
Default
-1

Set gzip compression level to a specific value. The default compression level is usually sufficient.

-1
Default gzip compression (recommended)
0
No compression
9
Maximum compression (but more CPU overhead)

Redis server configuration

This section is about the configuration on the Redis server, not the client.

For the flushing by cache tags to work, it is important that the integrity of the cache entries and cache tags is maintained. This may not be the case, depending on which eviction policy (maxmemory-policy) is used. For example, for a page id=81712, the following entries may exist in the Redis page cache:

  1. tagIdents:pageId_81712 (tag->identifier relation)
  2. identTags:81712_7e9c8309692aa221b08e6d5f6ec09fb6 (identifier->tags relation)
  3. identData:81712_7e9c8309692aa221b08e6d5f6ec09fb6 (identifier->data)

If entries are evicted (due to memory shortage), there is no mechanism in place which ensures that all entries which are related, will be evicted. If maxmemory-policy allkeys-lru is used, for example, this may result in the situation that the cache entry (identData) still exists, but the tag entry (tagIdents) does not. The tag entry reflects the relation "cache tag => cache identifier" and is used for RedisBackend::flushByTag()). If this entry is gone, the cache can no longer be flushed if content is changed on the page or an explicit flushing of the page cache for this page is requested. Once this is the case, cache flushing (for this page) is only possible via other means (such as full cache flush).

Because of this, the following recommendations apply:

  1. Allocate enough memory (maxmemory) for the cache.
  2. Use the maxmemory-policy volatile-ttl. This will ensure that no tagIdents entries are removed. (These have no expiration date).
  3. Regularly run the TYPO3 scheduler garbage collection task for the Redis cache backend.
  4. Monitor evicted_keys in case an eviction policy is used.
  5. Monitor used_memory if eviction policy noeviction is used. The used_memory should always be less then maxmemory.

The maxmemory-policy options have the following drawbacks:

volatile-ttl
(recommended) Will flush only entries with an expiration date. Should be ok with TYPO3.
noeviction
(Not recommended) Once memory is full, no new entries will be saved to cache. Only use if you can ensure that there is always enough memory.
allkeys-lru, allkeys-lfu, allkeys-random
(Not recommended) This may result in tagIdents being removed, but not the related identData entry, which makes it impossible to flush the cache entries by tag (which is necessary for TYPO3 cache flushing on changes to work and the flush page cache to work for specific pages).

File backend

The file backend stores every cache entry as a single file to the file system. The lifetime and tags are added after the data part in the same file.

This backend is the big brother of the Simple file backend and implements additional interfaces. Like the simple file backend it also implements the PhpCapableInterface, so it can be used with PhpFrontend. In contrast to the simple file backend it furthermore implements TaggableInterface.

In general, the backend was specifically optimized to cache PHP code, the get and set operations have low overhead. The file backend is not very good with tagging and does not scale well with the number of tags. Do not use this backend if cached data has many tags.

Options for the file backend

cacheDirectory

cacheDirectory
Type
array
Default
var/cache/

The directory where the cache files are stored. By default it is assumed that the directory is below TYPO3_DOCUMENT_ROOT. However, an absolute path can be selected, too. Every cache should be assigned its own directory, otherwise flushing of one cache would flush all other caches within the same directory as well.

Simple File Backend

The simple file backend is the small brother of the file backend. In contrast to most other backends, it does not implement the TaggableInterface, so cache entries can not be tagged and flushed by tag. This improves the performance if cache entries do not need such tagging. The TYPO3 Core uses this backend for its central Core cache (that hold autoloader cache entries and other important cache entries). The Core cache is usually flushed completely and does not need specific cache entry eviction.

PDO Backend

The PDO backend can be used as a native PDO interface to databases which are connected to PHP via PDO. It is an alternative to the database backend if a cache should be stored in a database which is otherwise only supported by TYPO3 dbal to reduce the parser overhead.

The garbage collection is implemented for this backend and should be called to clean up hard disk space or memory.

Options for the PDO backend

dataSourceName

dataSourceName
Type
string
Required

true

Data source name for connecting to the database. Examples:

  • mysql:host=localhost;dbname=test
  • sqlite:/path/to/sqlite.db
  • sqlite::memory

username

username
Type
string

Username for the database connection.

password

password
Type
string

Password to use for the database connection.

Transient Memory Backend

The transient memory backend stores data in a PHP array. It is only valid for one request. This becomes handy if code logic needs to do expensive calculations or must look up identical information from a database over and over again during its execution. In this case it is useful to store the data in an array once and lookup the entry from the cache for consecutive calls to get rid of the otherwise additional overhead. Since caches are available system wide and shared between Core and extensions they can profit from each other if they need the same information.

Since the data is stored directly in memory, this backend is the quickest backend available. The stored data adds to the memory consumed by the PHP process and can hit the memory_limit PHP setting.

Null Backend

The null backend is a dummy backend which doesn't store any data and always returns false on get(). This backend becomes handy in development context to practically "switch off" a cache.