Re: [RFC] [Discussion] OPcache Static Cache
| From: | Go Kudo | Date: | Mon, 18 May 2026 11:00:02 +0000 |
| Subject: | Re: [RFC] [Discussion] OPcache Static Cache | ||
| References: | 1 2 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-130934@lists.php.net to get a copy of this message | ||
2026年5月17日(日) 5:14 Larry Garfield <larry@garfieldtech.com>:
> 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