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

Reply via email to