2026年5月17日(日) 5:14 Larry Garfield <[email protected]>: > On Sat, May 16, 2026, at 10:19 AM, Go Kudo wrote: > > Hi internals, > > > > I'd like to start the discussion for a new RFC, OPcache Static Cache. > > > > RFC: https://wiki.php.net/rfc/opcache_static_cache > > Implementation: https://github.com/php/php-src/pull/22052 > > > > The proposal adds an OPcache-managed shared-memory cache for explicit > > userland values and for selected PHP static state. It introduces > > explicit functions under the OPcache namespace (volatile_* and > > persistent_*) and two attributes, #[OPcache\VolatileStatic] and > > #[OPcache\PersistentStatic], that let selected static properties and > > method static variables survive across requests. The feature is > > disabled by default and only activates once memory is allocated through > > the new INI directives. > > > > The RFC covers the motivation, the deliberate split between the two > > backends, the trust model (one PHP runtime = one trust domain; this is > > not a tenant isolation boundary), and benchmarks against APCu on NTS > > php-fpm and ZTS FrankenPHP. The PR is the full implementation, with > > PHPT coverage summarized in the Validation section. > > > > One thing to flag on the implementation status: the Windows build is > > currently broken. I don't have a Windows development environment > > available yet — one is being arranged through work, and I'll get the > > Windows side fixed once that's in place. > > > > Feedback welcome. > > > > Best Regards, > > Go Kudo > > Interesting! I can definitely see uses for it, and I appreciate the level > of detail in the RFC. > > Some thoughts, though: > > - atomic-decrement throwing if a value doesn't exist sounds like a > footgun. For something like an up/down voting widget, I could very easily > see someone hitting the Down Vote button first, which would cause it to > crash. Having to always check _exists() on decrement but not on increment > is inconsistent and likely to confuse people. > > - Are either of these stores purged on reboot? > > - "persistent cache and returns void on success." - No, returns null. You > can't return void. A function can have a void return type, whether it's > successful or not. Not quite the same thing. > > - Having the locks automatically self-unlock sure sounds elegant at first, > but the lack of symmetry in the API feels very error prone. People will > want to use it like a transaction, but since it unlocks on the first write > there's no way to make it one. It just silently unlocks if certain > functions are called. But if they're not called, there's no way to unlock > it. That's even more of an issue in a persistent-process use case, where > you could easily not hit the process-end for minutes or hours, so the lock > never automatically clears. > > Use case: You need to update some lookup table, so you lock the stored > key, compute the new table, then write the new table. But if the compute > step fails for some reason, you now have a locked value with no way to > unlock it, but no new value to write to it. It's better in many cases to > leave the stale data there rather than delete it, but this API doesn't > offer a way to do that. > > - It's not made clear: Do objects have their __serialize() methods called > when storing (and vice versa on load), or no? "They have to be > serializable" is not something that can be otherwise determined. > > - Status API: Uh, what are the keys? No arrays here please. Please make > it an object with defined readonly properties. Please. > > - You realize you're effectively adding a Memoize attribute to PHP by > another name, right? Just making sure. :-) > > - A property using the volatile-tracking strategy, if run in a persistent > process, seems like it would never get written. That feels like a problem. > > - The section on write times is rather abstract and academic, so a bit > hard to follow. If I read correctly, though, it means that writing to a > sub-property of an array/object on a cached property won't trigger a > resave? That feels like another footgun waiting to happen. > > - It's not clear if there's a way to clear an attribute-cached value other > than nuking the entire volatile/persistent cache. Is there not? It feels > like there should be one... Especially for "persistent," I don't want to > have to nuke my entire "persistent" cache from orbit because one value got > corrupted somehow. > > - Defaulting off... I can see the argument for that, but that means it > will be off for most users. That means I, as a library author of, say, a > routing system or a DI container, cannot use it, because I have to assume > most users won't have it. That kneecaps the usefulness of this feature > dramatically. I would strongly recommend setting at least some default-on > amount, even if it's only the minimum 8 MB, if we want this feature to > actually be used. (And I can already think of a few places where I'd want > to use it myself.) > > - Related, what happens if they're disabled but someone tries to use this > functionality? Does it operate like every read is a cache miss, or does it > error? If the latter, that means any code that uses the attributes > REQUIRES that the ini directives be turned on. We generally try to avoid > this kind of "your code may or may not work depending on ini settings" > issues. (Hello, magic_quotes!) > > - What's the development experience with this? Frequently, in dev mode > frameworks will disable caches. If the volatile cache just misses silently > that would work there, but not for the persistent cache. How would I have > a persistent route cache that is automatically rebuilt on every request > during development while I'm messing with routes? > > - I understand the value of keeping it simple by making it single-tenant. > However, I can very easily see different 3rd party libraries wanting to > make use of the cache at the same time. That poses a risk of key-space > collision, though that's resolvable by a convention to use a key prefix. > What it does not resolve is cases where one library wants to > wipe-and-rebuild a dynamic list of keys, but some other library isn't > expecting a total purge. There's a high risk of libraries stepping on each > other here. > > - Currently, this is just a basic key/value store. That's great for many > things, but not very queryable. This is absolutely scope creep, but would > there be some way to extend this (in a future RFC, I'm sure) to allow, say, > a persistent memory-resident SQLite database? Currently you can write one > to disk, but then you have to deal with disk permissions. A memory > resident database now is request-specific, so not useful outside of > testing. It would be lovely if there were some way to extend in that > direction. > > --Larry Garfield >
Hi Larry, Thank you for the detailed feedback. I updated the RFC and the implementation to address the concrete API and documentation issues you pointed out. https://wiki.php.net/rfc/opcache_static_cache The main changes are: - `persistent_atomic_decrement()` now creates a missing key with -$step, matching `persistent_atomic_increment()`. - `volatile_lock()` and `persistent_lock()` now have matching `*_unlock()` functions. - The lock APIs also accept an optional lease value, so abandoned builder reservations can expire even in persistent-worker environments. - The status APIs now return a read-only `OPcache\StaticCacheInfo` object instead of arrays. - The RFC now documents that both static-cache backends are scoped to the lifetime of the current OPcache static-cache shared-memory segment, and are not durable storage. - The wording around persistent_store() was corrected to describe the void return type rather than "returning void". - The RFC now describes when `__serialize()` and `__unserialize()` are called, and that userland serialization keeps those object graphs off the fastest direct/shared-graph path. - The publication rules for `VolatileStatic immediate` mode, `VolatileStatic tracking` mode, and `PersistentStatic` have been expanded. - `VolatileStatic tracking` is now documented as publishing at PHP request shutdown, not process shutdown, even under FPM/FrankenPHP/persistent workers. - Attribute-backed entries can now be deleted either by loaded class name or by documented exact static-property/method-static keys, without clearing the whole backend. - I also reran the benchmark matrix from clean NTS FPM, NTS/ZTS CLI, and ZTS FrankenPHP builds, and updated the RFC tables. Some of the broader points are open questions or design tradeoffs rather than direct fixes. On the default-off setting, I want to be more direct about where I actually stand. I would prefer this feature to be default-on. Administrator opt-in noticeably limits library adoption, and that significantly reduces the value of the API as a portable primitive that libraries can rely on being present. What is holding me back is the shared-hosting case. The cache is a single shared-memory trust domain, so on a host where multiple tenants share one OPcache segment, default-on without an isolation story could expose those tenants to each other through the static cache. I do not yet have a design I am confident makes default-on safe in that environment, and that is the only reason the RFC currently ships with the feature disabled. I would genuinely appreciate your input here. If you (or anyone on the list) see a viable path — per-pool / per-SAPI segments, a trust-domain or namespace mechanism enforced by the engine, a configuration model where the host opts in per vhost rather than per server, or something I have not considered — I would be very happy to rework the proposal around it. If we can land on a model that is safe for shared hosting, I would gladly flip the default in this RFC rather than defer it to a follow-up. For disabled backends, the explicit APIs fail rather than silently pretending to be a miss-only cache. Attribute-backed state falls back to ordinary request local static behavior when the corresponding backend is unavailable. I can add a short FAQ entry for this development-mode behavior if that would make the RFC clearer. For multi-library key collisions, the explicit cache remains a shared namespace and applications/libraries still need key prefixes, similar to APCu. The new attribute deletion and exact-key deletion support should reduce the need for whole-backend clears, but this RFC does not add a separate namespace mechanism. I left the broader trust-domain/namespace question as an Open Issue, and it is closely related to the shared-hosting question above. The memory-resident SQLite idea is interesting, but I think it is outside the scope of this RFC. This proposal is intentionally a small key/value and static state facility first. Thanks again. The feedback helped make several parts of the API much more explicit. Best Regards, Go Kudo
