Skip to content

[2.x] feat: phpredis auto-detect, cache:subscribe fix, settings memory cache#19

Merged
imorland merged 5 commits into
2.xfrom
im/phpredis-auto-detect-2x
Mar 7, 2026
Merged

[2.x] feat: phpredis auto-detect, cache:subscribe fix, settings memory cache#19
imorland merged 5 commits into
2.xfrom
im/phpredis-auto-detect-2x

Conversation

@imorland

@imorland imorland commented Mar 6, 2026

Copy link
Copy Markdown
Member

Changes

1. Auto-detect phpredis, fall back to Predis

Detects the available Redis client at runtime rather than assuming Predis. If the phpredis extension is loaded, it is used directly; otherwise falls back to Predis. No configuration change required.

2. Fix cache:subscribe with phpredis

The cache:subscribe command was hardcoded to check instanceof \Predis\Client, causing it to abort immediately when phpredis is active. Replaced with driver-agnostic routing:

  • phpredis: uses callback-based subscribe() with OPT_READ_TIMEOUT = -1 and $redis->close() to exit on --once
  • Predis: existing pub/sub loop preserved, creates a new client with read_write_timeout: 0

3. Add MemoryCacheSettingsRepository layer to eliminate per-call Redis fetches

RedisCacheSettingsRepository::get() calls all() on every invocation, which calls cache->remember() every time — a Redis GET of the full flarum:settings blob on each call.

On a typical forum page, settings are read 60–100 times (core serializers, frontend JS variable injection, middleware, extensions). Each call fetches the entire settings blob from Redis — on production this is ~154 KB. That's up to ~15 MB of Redis egress per page load from settings reads alone, which at scale accounts for the majority of observed Redis outbound bandwidth (~300 Mbps on Nothing Community).

Flarum core's SettingsServiceProvider uses MemoryCacheSettingsRepository to provide a per-request in-process cache. fof/redis's Settings provider replaced the entire binding without restoring this layer.

The fix wraps RedisCacheSettingsRepository with MemoryCacheSettingsRepository, restoring the three-layer chain:

MemoryCacheSettingsRepository   ← per-request in-process cache (zero network cost after first read)
  └── RedisCacheSettingsRepository  ← cross-request Redis cache (one Redis GET per request, 1h TTL)
        └── DatabaseSettingsRepository  ← source of truth (MySQL, hit only on Redis miss)

After this change, Redis is hit at most once per request for settings instead of once per settings->get() call. Expected Redis egress reduction: ~95%+ of settings-related bandwidth eliminated.

If ext-redis is loaded, use phpredis as the driver. Otherwise fall back
to Predis. No configuration change required by consumers.

Documents persistent connections, Unix socket, and compression options
in the README.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@imorland imorland changed the title feat: auto-detect phpredis, fall back to Predis [2.x] feat: auto-detect phpredis, fall back to Predis Mar 6, 2026
imorland and others added 4 commits March 6, 2026 22:21
The cache subscriber hardcoded a Predis client check, causing it to
immediately abort with "Expected Predis client" when phpredis (ext-redis)
is the active driver.

Route to a new `subscribePhpRedis()` method when the underlying client is
a `\Redis` instance. phpredis `subscribe()` is blocking and callback-based
rather than an iterator — set `OPT_READ_TIMEOUT = -1` for infinite timeout,
and close the connection from within the callback to exit on `--once`.

Predis path is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
phpredis 6.x accepts a Closure as the second argument to subscribe() at
runtime, but the PHPStan stubs incorrectly type it as array|string. Add
@phpstan-ignore-next-line to suppress the false positive.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…edis fetches

RedisCacheSettingsRepository::get() calls all() on every invocation,
which calls cache->remember() every time — a Redis GET of the full
flarum:settings blob on each call.

On a typical forum page, settings are read 60–100 times. Each call
fetches the entire settings blob from Redis (~154 KB on production),
accounting for the majority of observed Redis egress bandwidth.

Wrapping with MemoryCacheSettingsRepository restores the per-request
in-process cache that Flarum core's SettingsServiceProvider provides
by default. After the first settings read per request, all subsequent
reads within the same request are local array lookups with zero network
cost. Redis is hit at most once per request instead of once per
settings->get() call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@imorland imorland changed the title [2.x] feat: auto-detect phpredis, fall back to Predis [2.x] feat: phpredis auto-detect, cache:subscribe fix, settings memory cache Mar 6, 2026
@imorland imorland marked this pull request as ready for review March 7, 2026 00:51
@imorland imorland requested a review from a team as a code owner March 7, 2026 00:51
@imorland imorland merged commit 693c376 into 2.x Mar 7, 2026
9 checks passed
@imorland imorland deleted the im/phpredis-auto-detect-2x branch March 7, 2026 00:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant