APCu in php — some notes

This entry is part 2 of 3 in the series php features

I’ve been working on the SQLIite Object Cache plugin for WordPress. I’m using the APCu User Cache, php’s RAM cache subsystem, to accelerate cache lookups — specifically to support wp_cache_get() and wp_cache_get_multiple() operations. I’ve had to figure out a few things about this php feature.

Here are some notes.

Its documentation is not as comprehensive as some parts of php.

Installing

It is an optional php extension. It needs to be installed and/or activated in a php instance. On Ubuntu you can install it with

sudo apt install -y php8.2-apcu

mentioning your version of php in place of php8.2. In a cPanel-controlled shared server, it can be chosen via a checkbox on the modules page of the Software -> php tab.

Don’t forget to restart your web server after adding this extension.

Is APCu enabled?

Use code like this to determine whether the APCu extension is installed and enabled.

$apcu_ok = function_exists( 'apcu_enabled' ) ? apcu_enabled() : false;

Organization, Size, Time-to-live

On an Ubuntu Linux machine, the cache goes into a single 32MiB shared memory segment, with about 4000 distinct cache slots. Each slot has a text-string name and an arbitary item of php data (string, scalar, array, or object) as a value. Each slot has a time-to-live (ttl) value,. When a new entry into the cache arrives and there’s no room for it, the APCu subsystem discards some entry with an expired ttl, or some aging entry. Under some circumstances the APCu cache gets flushed (gets all its enries deleted) when this happens.

When you place an item in the cache with an explicit Time-to-live (ttl) value, it vanishes at the appointed time. This is the same behavior as WordPress transients, which cannot be read after their ttls expire.

Features and limitations

It works as a shared-memory cache visible, between the various php worker processes working on behalf of a web server on a machine.

Unfortunately it does not work as a shared memory cache between php code running in command line context (for example in wp-cli or Drupal console) and code running on behalf of a web server. Without extra work this makes it unsuitable for supporting a persistent object cache for a hybrid web app with some command line work and some web server work.

It has a php.ini configuration setting called apc.enable_cli. It’s best to leave that setting at its default of 0, because the APCu that’s available to a CLI php program is not visible to the web server php programs or vice versa.

Avoid Key Collisions With Prefixes

A single APCu cache is shared among different applications running on the same web server. This is because the same pool of php worker threads serves the various applications.

So, it is vital to ensure that the cache keys on different applications don’t collide. If they do collide, one application will get information intended for another. This can wreak havoc (ask the author how he knows this sometime).

The best way to do this is by creating a unique key-name prefix for each application. When the applications are WordPress instances, this seems to be a good choice of unique key.

$prefix = substr( base64_encode( 
              md5( $table_prefix . DB_HOST . DB_USER . DB_NAME . AUTH_KEY . AUTH_SALT )
          ), 0, 12) . '.'; 

It takes about a microsecond to generate a 12-character randomized prefix by hashing the database access information and a couple of the random strings in wp-config.ini. (Notice that creating a staging site sometimes does not change AUTH_KEY and AUTH_SALT so using the database is necessary.

Flushing the cache

You can flush the cache (delete all the entries) with apcu_clear_cache(). But, that deletes all the cached information, including that from other web apps or WordPress instances besides your own. This means two things:

  1. Applications can disrupt each others’ caches.
  2. Applications must be coded to be resilient to missing entries in the cache.

A good practice is only to delete the entries with your application’s name prefix when there is a need to flush your cache. Code like this will do that efficiently using apcu_delete().

$prefix = // Your app prefix
foreach ( new APCUIterator( '/^' . $prefix . '/', APC_ITER_KEY ) as $item ) {
    apcu_delete( $item['key'] );
}

Key Info

Use the apcu_key_info() function to return an array of integers with information about a particular cache key. If the key doesn’t exist in the cache, it returns null. The array looks like this

array (
  'hits'          =>         10, // Count of the times apcu_fetch retrieved this value.
  'access_time'   => 1739197489, // UNIX timestamp of most recent apcu_fetch.
  'mtime'         => 1739197300, // UNIX timestamp of most recent change to value.
  'creation_time' => 1739197200, // UNIX timestamp of when the key was first created.
  'deletion_time' => 1739197600, // UNIX timestamp of when the key was deleted. 0 means active.
  'ttl'           =>      86400, // Time-to-live in seconds. 0 means default.
  'refs'          =>          0  // Number of internal references to this key.
)

Notice that the ttl item in this array is not a UNIX timestamp, but rather the value you gave upon creating or setting the key’s value.

When you give an explicit ttl value when creating or setting a key, the value is never returned after the ttl expires. So, it is safe for use with ephemeral objects like WordPress transients with expiration times.

Diagnostics and debugging

The APCu extension developers have made a file called apc.php. If you put it in your web server root directory, you can use it to examine the use of your APCu cache. You can get it here. Avoid putting this file on production web sites, as it can give cybercreeps a way to look at your cached data and possibly steal information you don’t want stolen.

Series Navigation<< SQLite3 in php — some notesphp’s microtime() function >>

3 thoughts on “APCu in php — some notes”

Leave a Comment