orieg/judy-cache
| Install | |
|---|---|
composer require orieg/judy-cache |
|
| Latest Version: | v0.2.0 |
| PHP: | >=8.1 |
| License: | MIT |
| Last Updated: | Aug 22, 2026 |
| Links: | GitHub · Packagist |
judy-cache
High-performance PSR-16 and PSR-6 in-memory cache backed by Judy arrays, built for long-running PHP: Laravel Octane, Swoole, RoadRunner, FrankenPHP worker mode, queue workers, and CLI daemons — anywhere process memory survives across requests. (In classic FPM the process dies with the request; use APCu there.)
- O(range) Prefix Invalidation: Invalidate key hierarchies instantly without scanning the whole cache.
- Up to 70% Less Memory: Radix-trie storage compresses key/bucket overhead at scale (172 MB vs 495 MB at 1M keys).
- Works Out of the Box: Uses orieg/judy-polyfill by default; automatically upgrades to native C ext-judy >= 2.6.0 when loaded.
composer require orieg/judy-cache
# Optional for native C performance:
pie install orieg/judy
Note: If you install the C extension, use ext-judy >= 2.6.0 for memory-safe teardown on mixed-type arrays (php-judy#162).
Quickstart (PSR-16)
use Orieg\JudyCache\JudySimpleCache;
$cache = new JudySimpleCache();
$cache->set('user.42.profile', $profile, ttl: 300);
$profile = $cache->get('user.42.profile');
What Judy Adds: Prefix Invalidation & Introspection
Because the trie maintains keys in lexicographic order, range operations walk only matching keys ($O(\text{matching keys})$ instead of scanning all $N$ entries):
$cache->deletePrefix('user.42.'); // O(matching keys), not O(all keys)
$cache->keysByPrefix('report.', 100); // ordered prefix exploration
$cache->prune(); // eager eviction of expired entries
Array-backed caches (plain arrays, Symfony ArrayAdapter, APCu) have no fast path for range eviction and must scan every key.
Symfony Cache / PSR-6
With symfony/cache installed:
use Orieg\JudyCache\JudyAdapter;
use Orieg\JudyCache\JudySimpleCache;
$judy = new JudySimpleCache();
$pool = new JudyAdapter(cache: $judy); // PSR-6 cache pool
$report = $pool->get('report.7', fn () => computeReport(7));
$judy->deletePrefix('report.'); // range invalidation underneath
Examples & Interactive Testbeds
Explore runnable implementations in examples/:
- Large-Value Storage Shootout: Standalone headless CLI benchmark evaluating transparent adaptive compression, content-addressable interning, zero-alloc cursor pruning, and multi-worker memory models ($W \times \text{Size}$).
- Live Multi-Worker Simulation: Multi-process simulation harness spawning real OS child worker processes to measure true host
VmRSSresident memory amplification across $W \in {1 \dots 64}$ workers. - FrankenPHP Worker Mode Testbed: Interactive web dashboard with real-time SSE telemetry, live process VmRSS metrics, CRC lossless integrity verification, and side-by-side shootouts ($10\text{k} \dots 10\text{M}$ keys).
- Multi-Worker Owner Process: Reference implementation of an IPC/Unix-socket cache daemon providing a single-writer cache across multi-worker pools.
Semantics & Configuration
- Keys: Standard PSR-16 rules (
{}()/\@:reserved). Use.as your hierarchy separator (user.42.profile). - Values: Serialized snapshots by default (like Symfony's
ArrayAdapter), ensuring fetched objects are safe from mutation. PassstoreSerialized: falsefor faster by-reference storage. - TTL & Pruning:
intseconds orDateInterval. Expired entries are evicted lazily on access, or eagerly via native in-CpruneExpired()(or zero-allocation cursorprune()when external slab/shmop allocations exist). TheSTRING_TO_ENTRYC struct packs expiry timestamps and 16-bit metadata flags directly into the entry, eliminating secondary lookup arrays and userland packing overhead. - Adaptive Compression: Pass
compressionThreshold: 1024(bytes) andcompressionCodec: 'gzip'('gzip','deflate','zstd','lz4') to auto-compress payloads exceeding the threshold. Codec metadata is stored directly in 16-bit entry flags and automatically skips compression if the compressed size exceeds original payload size. - Content-Addressable Interning: Pass
enableInterning: true(and optionalinternThreshold: 256) to deduplicate identical payloads across distinct keys into a shared 8-byte binary XXH3 reference-counted pool. - Chunked Slab Arena: Pass
slabArena: new SlabArena()andslabThreshold: 1024to route large byte payloads (JSON docs, HTML fragments) into pre-allocated contiguous memory blocks via 8-byte uint64 chunk offsets to prevent Zend Memory Manager (ZMM) heap fragmentation. - Shared Memory Pool (shmop): Pass
shmPool: new SharedMemoryPool()andshmThreshold: 1024for zero-copy shared memory payload segments across multi-worker pools (FrankenPHP, Octane, Swoole). - Clock: Injectable (
new JudySimpleCache(clock: fn() => $timestamp)) for deterministic testing. - Backend Choice: Built natively on
Judy::STRING_TO_ENTRYfor optimal C-level TTL and metadata handling.
Performance & Memory
Measured on CI (PHP 8.4, ext-judy 2.6.0, median of 5, small serialized array values):
| Metric / Workload | judy-cache (ext-judy) |
Native PHP Array | Symfony ArrayAdapter |
Symfony TagAwareAdapter |
|---|---|---|---|---|
| RAM (1M entries) | 172 MB | 495 MB | 921 MB | 1,407 MB |
| Prefix Prune (50k) | 50 µs | 12.4 ms | 13.1 ms | 14.0 µs (deferred) |
| Prefix Prune (1M) | 58 µs | 252.0 ms | 268.0 ms | 14.0 µs (deferred) |
| Write Ops/s (set) | ~1.6M ops/s | ~830k ops/s | ~790k ops/s | ~85k ops/s (10x slower) |
Process Scope: Like PHP arrays,
judy-cacheis per-process. For data shared read-hot across many worker processes without invalidation needs, APCu remains optimal. For per-worker state, single-process daemons, and $O(\text{range})$ invalidation,judy-cacheprovides unmatched memory density and eviction speed.
See BENCHMARK.md for full benchmarks, methodology, and vendoring analyses.
Validation
Four independent layers executed across PHP 8.1–8.5 on both ext-judy and judy-polyfill:
- Spec Compliance:
tests/simplecache.phpverifies every testable clause of PSR-16. - Behavior Tests: Clock/TTL edge cases, prefix operations, and serialization semantics.
- Model-Based Fuzzing:
tests/fuzz.phptests randomized operations diffed step-by-step against an oracle. - Backend Parity: Fully verified against
php-judyC extension viajudy-polyfill's differential test suite.
License
MIT.
Related Packages
Serves Winter CMS from a persistent application server (FrankenPHP, Swoole or Ro...