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
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/22052The 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