zendtech / ZendOptimizerPlus

Other
915 stars 142 forks source link

Feature request: Add function to reset cache for specified path - opcache_clear_path($path) #107

Closed altexdim closed 11 years ago

altexdim commented 11 years ago

Feature request: Add function to reset cache for specified path - opcache_clear_path($path)

When i'm deploying new code i need to reset cache every time to be sure opcache is not run out of memory. But if i'm doing reset all of the opcache it makes LA rise up too high every time. My server serves 100 requests per second with zend opcache, but without it it can serve up to 10 requests maximum. So it would be great workaround for me if there will be a way to reset opcache for only specified path.

For example if there is a version 1 (in the folder /project/my/version1 for instance) of a project, then i deploy 2nd version of the project (/project/my/version2), then i run precache of /project/my/version2 folder (simply include()'ing all the files with classes in the project), then i reload my nginx config pointing docroot to /project/my/version2, and then i just want to clear opcache for the folder /project/my/version1.

TerryE commented 11 years ago

Have you tried using opcache_invalidate(string $script [, bool $force = false])? with a find enumeration? This will invalidate all scripts on a given path, such as your /project/my/version2/... Unfortunately scripts concurrently executing on that path may fall because of version mix-ups. However, you can work around this programmatically by adding application logic to iniset opcache.enable=false within the app which will disable caching for a given request to allow you to offline caching for a specific app for a safety window around the invalidation cascade, or by embedding the app version in the path as you suggest.

OPcache doesn't currently reuse this stale space, so you will need to have a large enough cache to contain multiple application versions and set the opcache.max_wasted_percentage low enough to avoid automatic restart, and then you can at least do a controlled restart if you have a daily low load point (e.g. at 4AM if the bulk of your customers are from a single geography).

However this is all messy, and IMO it would be nice if OPcache gracefully reused storage and had some standard templates for Apache2, Nginix, etc for doing application upgrade non-stop / non-restart, with the appropriate opcache directives as PER_DIR or INI_ALL to facilitate these templates.

I've spent a bit of time over this last month thinking about how OPcache could elegantly address such scaling issues, but this github issue list and the PHP RFC process don't seem be a good fit to table working discussions for comment. However, if Dmitry or one of the other team members wants to make a recommendation then I'll follow it.

altexdim commented 11 years ago

I've not tried using opcache_invalidate because of zend opcache does not reuse invalidated space. I dont want to disable opcache for any reqeusts because it will lead to performance drop (that's what i'm trying to avoid)

Unfortunately scripts concurrently executing on that path may fall because of version mix-ups.

i dont have any, because i'm using incremental version aproach

I believe some day opcache will reuse storage without reset. It would be great. Or double buffered storage at least. I mean if i could switch two storages and reset them reparately.

TerryE commented 11 years ago

@altexdim , sorry but the following response ended up as more a brain dump of how to address this functional gap. I really need @dstogov and/or the rest of the team to give feedback before I should comment further


OPcache uses two techniques to simplify SMA management:

To a large extent these two techniques are independent in that an opcode cache implementation could both or either. The two pass+brick allocation give the biggest performance improvement. And with this brick allocator, the main advantage of the create-only allocator is that it materially simplifies the implementation of the SMA.

The major downside is that the only technique available to recover storage is one of dead-and-rebirth, that is when exhaustion occurs, the current algorithm disables all cache use effectively until the last script has finished executing a cached script (or killed if a maximum timeout is reached), at which point the whole SMA is reinitialised with a blank and the subsequent scripts left to rebuild a working cache.

Simple, but this scales terribly. Why? As you point out, all opcode caching is disabled during the restart phase, and for a typical work mix, this can drop the overall system service capacity by 2-3x (and you quote 10x for your outlier case). This is a bit like closing a freeway after an accident and diverting all traffic onto the side roads). Unless the system is oversized by 2-3x (which isn't the case for enterprise systems), this will cause major queue build-up on service fulfilment within the web service, creating a cascade that can also block upstream and downstream services that might take 30+ minutes to settle down again. (My article Performance Collapse in Systems describes the mechanisms and characteristics in more detail). An enterprise-class opcode cache shouldn't do this.

My recommendation is that the next major release of OPcache should address this by replacing the SMA allocator with one that permits rolling replacement / compaction of the cache, and does not introduce this global cache disabled window. I think that most enterprise-scale implementations could live with a update freeze of up to, say, a few minutes, as long as the read-only script caching service still continued.

Also, given current memory pricing and trends, over-allocation of SMA storage (say 2Gb instead of 1.5Gb) isn't a big issue, so storage housekeeping doesn't need to be aggressive. One possible approach would be to divide the SMA storage into N regions perhaps only using N-1 for update at any one time. When region K is if flagged as due for reclamation, the spare region L is immediately brought online and updates to K stopped. The entire content of K could be tagged as wasted, with any subsequent requests triggering a rebuild in one of the active regions; the reformating of K itself would need to be deferred until the last script / request using resources in the region completes, which in most cases would be less than 1min after the start of sequence. This could permit rotated purging by region, which would incrementally recover wasted storage without needing either the restart big-bang, or offlining the OPcache cache functions.

The use of the three hashes adds complications, but one approach could be to assign each to a separate region and simply rebuild the entire hash if that region is being purged; perhaps adding some API calls so that some of this higher level management and orchestration can be hoisted out of OPcache into a set of management scripts. This might seem like overkill, but this approach has the advantage of simplicity and even a rebuild of the biggy, the interned string pool, would still only lock updates for typically less than 1s.

The main issue with this is that such an approach needs to detect / determine which scripts are still actively using given script resources, and this read-resource tracking requires some form of loose per-resource read-lock. This is because you can't reclaim and reformat a region until every thread/request using it has completed. The current implementation uses an flock-based lock manager. This is a lot slower than platform-specific lock managers, but it is pretty (non-Winxx) universal and plenty fast enough for the current update locking. Using it for read locks on a per script basis would be a performance killer. I've worked out one scheme which gets this down to a R/W lock per request, but this is still too much of a burden for an flock approach.

I've been brooding about this ever since I realised that this is one of the two major functional limitations of OPcache (IMO). I am now confident that I could now develop a robust performant solution to address this need, but its not a small patch; it's going to end up of the same order of change as the MLC opcache functionality, so I don't even want to start down this route again until I have a working collaboration with the development team and everyone is comfortable with this.

dstogov commented 11 years ago

In current implementation OPcache can't reuse allocated shared memory without complete cache reset. It was done on purpose and makes cache a bit faster. However, I see that transparent SHM reuse is one of the main things OPcache really needs. We will look into this problem when ave enough time (not soon). Once it's implemented opcache_invalidate() will do what you like.

I've opened issue #118 instead of this one.