Mattschwarzer Schubladen-Block mit acht identischen Fächern und Brass-Schildern, eines leicht aufgezogen, weiches Tageslicht.
Extension · moselwal/keyvalue-store

keyvalue-store — Redis/Valkey, production-grade.

Redis/Valkey integration for TYPO3 14 with production-ready cache backend, session backend, and distributed locking strategy. Includes Sentinel discovery, TLS/mTLS, and phpredis 6.3+. The cache backend resolves four anti-patterns from TYPO3 Core’s RedisBackend (KEYS, blocking DEL, sequential tag flushing, missing Sentinel/TLS support).

  • Composer package:composer require moselwal/keyvalue-store
  • TYPO3: ^14.0 · PHP: 8.5+ · ext-redis: >= 6.3 · GPL-2.0-or-later
Das Problem

TYPO3 caching is standard — but really production-ready?

With keyvalue-store

  • TYPO3 caching framework backends for pages, hash, RootlinePath, ImageSizes, etc.
  • Session storage in Redis/Valkey with replication awareness
  • Distributed locking across all nodes
  • Sentinel support out of the box
  • TLS and mTLS as a configuration option

Until now

  • Standard Redis backend without Sentinel awareness
  • Sessions in the database — unnecessary load
  • Locking as file lock or DB lock — not cluster-capable
  • TLS/mTLS configuration as custom hack

Four building blocks

Sentinel & mTLS

Sentinel discovery enables automatic failover. mTLS configuration for encrypted inter-service communication in container setups.

Distributed locking

Lua-EVAL + BLPOP pattern — atomic SET key NX EX ttl for tryLock(), server-side BLPOP instead of client polling for wait(). Cluster-correct, no polling overhead.

Session storage

Frontend and backend sessions in Redis/Valkey — takes load off the database and makes multi-node setups seamless.

Caching backends

Drop-in backends for all TYPO3 caching framework caches: pages, hash, RootlinePath, ImageSizes — including tag-based flushing.

Architecture

Classes/
├── Cache/Backend/
│   └── KeyValueBackend.php          (extends TYPO3 Core RedisBackend)
├── Session/Backend/
│   └── KeyValueSessionBackend.php   (implements SessionBackendInterface)
├── Locking/
│   └── KeyValueLockingStrategy.php  (implements LockingStrategyInterface)
└── Connection/
    ├── KeyValueConnectionFactory.php
    ├── SentinelResolver.php
    ├── TlsContextBuilder.php
    └── ValueObject/

 

All three TYPO3-facing components route their phpredis instantiation through KeyValueConnectionFactory — Sentinel, TLS, mTLS, and the phpredis 6.x backoff are configured in exactly one place.

Requirements

Configuration

The easiest path is via moselwal/typo3-configautoconfigureCaching() wires all three components consistently. Manual configuration in config/config.php:

Cache backend

 

$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['pages'] = [
    'backend' => \Moselwal\KeyValueStore\Cache\Backend\KeyValueBackend::class,
    'options' => [
        'hostname'             => 'valkey.internal',
        'port'                 => 6379,
        'database'             => 3,
        'password'             => 'secret',
        'persistentConnection' => true,
        'defaultLifetime'      => 2592000,
        'compression'          => true,
        // TLS/mTLS (optional)
        'tls'                  => true,
        'ca_file'              => '/run/tls/ca.crt',
        'cert_file'            => '/run/tls/httpd.crt',
        'key_file'             => '/run/tls/httpd.key',
        // Sentinel (optional)
        'sentinel'             => true,
        'sentinel_host'        => 'sentinel.internal',
        'sentinel_port'        => 26379,
        'sentinel_service'     => 'mymaster',
    ],
];

 

Session backend

 

$GLOBALS['TYPO3_CONF_VARS']['SYS']['session']['BE'] = [
    'backend' => \Moselwal\KeyValueStore\Session\Backend\KeyValueSessionBackend::class,
    'options' => [
        'hostname'             => 'valkey.internal',
        'database'             => 1,
        'password'             => 'secret',
        'persistentConnection' => true,
        'persistentId'         => 'typo3-session-be',
        'prefix'               => 'typo3:sess:be:',
        'sessionLifetime'      => 3600,
    ],
];

 

Locking strategy

 

$GLOBALS['TYPO3_CONF_VARS']['SYS']['locking']['strategies'][
    \Moselwal\KeyValueStore\Locking\KeyValueLockingStrategy::class
] = [
    'hostname'             => 'valkey.internal',
    'database'             => 0,
    'password'             => 'secret',
    'persistentConnection' => true,
    'ttl'                  => 10,
];

 

Dependencies

PackageTypePurpose
ext-redis >= 6.3Requiredphpredis with v6 constructor API
moselwal/devDevShared QA tooling

How v4.x differs from TYPO3 Core's Redis backend

KeyValueBackend extends TYPO3's own RedisBackend and overrides four operations that produce server-side anti-patterns. All other operations (get, has, remove, findIdentifiersByTag) are inherited unchanged — Core already pipelines tag tracking there correctly.

OperationTYPO3 CoreKeyValueBackend v4.3.0
set()SETEX + SMEMBERS + optional MULTI/PIPELINE tag diffSingle Lua EVAL — atomic, 1 roundtrip
flush()KEYS prefix* + DEL — blocks all clients in the event loopSCAN + UNLINK batches — server stays responsive
flushByTag() / flushByTags()N× sequential fan-outOne sUnion + one pipelined UNLINK
collectGarbage()KEYS identTags:*SCAN loop
Connectionpconnect() only, no Sentinel/TLSKeyValueConnectionFactory — Sentinel, mTLS, jitter backoff
SerializerPHP-native, hard-codedConfigurable: php / igbinary / none / auto

Lazy connect (lazy=true default) — the TCP/TLS handshake is deferred to the first command. Bootstrapping 11 cache backends drops from ~25 ms to ~0.07 ms on a real mTLS Valkey setup (381×). OPT_SCAN = SCAN_RETRY is set on every connection so phpredis 6.x retries empty SCAN pages internally instead of returning false.

Benchmarks (container-side against Valkey/mTLS, phpredis 6.3.0)

Measured container-side against Valkey with mTLS. Numbers compare TYPO3 Core / v4.0.x with v4.3.0.

OperationCore / v4.0.xv4.3.0Factor
Bootstrap 11 caches25.1 ms0.07 ms381×
getAll() 500 sessions37.2 ms1.5 ms24.6×
renew() (session fixation)360 µs161 µs2.2×
Retry-Backoff (2 failures)162 ms31 ms5.1×
set() 1 tag353 µs264 µs1.3×
set() 5 tags421 µs266 µs1.6×
set() 10 tags421 µs286 µs1.5×
set() 20 tags582 µs299 µs1.9×
flushByTags(10 tags)4.1 ms1.2 ms3.3×
flush(10 k keys)7.4 ms9.5 ms−30 % wallclock, no event-loop block
collectGarbage(5 k keys)1.4 ms2.8 ms−50 % wallclock, no event-loop block

flush() and collectGarbage() are intentionally slower wallclock-wise for the caller. The trade-off: KEYS blocks every other client in the Valkey instance while it runs. With SCAN, the server can interleave other clients between batches — for a multi-site or multi-pod setup that is the more important property. Neither is a hot path (flush() is BE/CLI-triggered; collectGarbage() runs in the scheduler tick).

Serializer

KeyValueBackend defaults to PHP-native serialization. The serializer option lets you change this per cache backend:

ValueBehaviour
'php'(default)PHP-native, BC-safe, identical to v4.2.0
'igbinary'igbinary when ext-igbinary is loaded; falls back to PHP-native with a notice otherwise
'none'No phpredis-layer serialization — the caller serializes
'auto'igbinary if loaded, php otherwise (not recommended, see below)

⚠️ Switching the serializer requires a full cache flush of all affected cache databases. Existing payloads remain in the previous format and will fail to deserialize. Recommended deploy sequence:

 

# 1. Flush each affected Valkey DB
valkey-cli -n 3 FLUSHDB    # pages
valkey-cli -n 4 FLUSHDB    # hash
# ... repeat for each cache DB

# 2. Restart workers so connections re-initialise with the new option

# 3. Deploy the new typo3-config with the serializer change

 

Why auto is the wrong default: an image update shipping ext-igbinary would silently switch the on-disk format for any cache using auto — the next read of an old PHP-serialised payload would throw. Pinning the value explicitly ('php' or 'igbinary') makes the contract observable.

When igbinary is worth it: only for caches storing deeply nested arrays/objects (e.g. Extbase ClassSchema, Fluid template reflection). For string-content caches (rendered pages, large text blobs) or flat key/value caches (hash, imagesizes), the igbinary encoder overhead dominates the marginal payload-size gain — keep the default.

The session backend (KeyValueSessionBackend) uses JSON internally for debuggability via valkey-cli — this option has no effect there.

Source code & docs

Packagist

composer require moselwal/keyvalue-store

Package on Packagist →
Packagist

GitHub

Source code, issues, and changelogs. GPL-2.0-or-later.

View on GitHub →
GitHub
Nächster Schritt

Cluster setup or migration from the standard backend?

For migration from the standard Redis backend, Sentinel setup or mTLS configuration in production, we are happy to support you as a service.

Cluster-Setup besprechen

Oder direkt schreiben: kontakt@moselwal.de

Where we use this …

This package carries the cache and session layer in TYPO3 Kubernetes — without a central cache store no multi-pod setup runs cleanly. In the managed variant it runs as part of our AI-Ready CMS as a Service.