
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, I am happy to support you as a service.
Oder direkt schreiben: kontakt@moselwal.de
Where I 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 my AI-Ready CMS as a Service.