
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
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
- PHP 8.5+
- TYPO3 ^14.0 (Composer mode)
ext-redis>= 6.3 (phpredis with v6 constructor-config API)- Redis 4.0+ or Valkey (
UNLINKrequired)
Configuration
The easiest path is via moselwal/typo3-config — autoconfigureCaching() 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
| Package | Type | Purpose |
|---|---|---|
ext-redis >= 6.3 | Required | phpredis with v6 constructor API |
moselwal/dev | Dev | Shared 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.
| Operation | TYPO3 Core | KeyValueBackend v4.3.0 |
|---|---|---|
set() | SETEX + SMEMBERS + optional MULTI/PIPELINE tag diff | Single Lua EVAL — atomic, 1 roundtrip |
flush() | KEYS prefix* + DEL — blocks all clients in the event loop | SCAN + UNLINK batches — server stays responsive |
flushByTag() / flushByTags() | N× sequential fan-out | One sUnion + one pipelined UNLINK |
collectGarbage() | KEYS identTags:* | SCAN loop |
| Connection | pconnect() only, no Sentinel/TLS | KeyValueConnectionFactory — Sentinel, mTLS, jitter backoff |
| Serializer | PHP-native, hard-coded | Configurable: 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.
| Operation | Core / v4.0.x | v4.3.0 | Factor |
|---|---|---|---|
| Bootstrap 11 caches | 25.1 ms | 0.07 ms | 381× |
getAll() 500 sessions | 37.2 ms | 1.5 ms | 24.6× |
renew() (session fixation) | 360 µs | 161 µs | 2.2× |
| Retry-Backoff (2 failures) | 162 ms | 31 ms | 5.1× |
set() 1 tag | 353 µs | 264 µs | 1.3× |
set() 5 tags | 421 µs | 266 µs | 1.6× |
set() 10 tags | 421 µs | 286 µs | 1.5× |
set() 20 tags | 582 µs | 299 µs | 1.9× |
flushByTags(10 tags) | 4.1 ms | 1.2 ms | 3.3× |
flush(10 k keys) | 7.4 ms | 9.5 ms | −30 % wallclock, no event-loop block |
collectGarbage(5 k keys) | 1.4 ms | 2.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:
| Value | Behaviour |
|---|---|
'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.
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.
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.