skip to content

APCu & Realpath Cache

APCu keeps user data in one server's shared memory across requests, and the realpath cache saves repeated path lookups. Interviewers ask when a local cache beats a network cache and when it lies.

part ofPHPoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In PHP, when does caching a feature-flag list in APCu on each web server beat Redis, and when does it give wrong answers?

level: middleimportance: must knowfreq 42%

answer

  1. no network round-trip, no serialization over the wire
  2. one copy per server
  3. no cross-server invalidation
  4. staleness bounded by the TTL
  5. CLI and restarts see different caches

basics

~20 s

APCu wins for small, read-heavy, rarely changing data such as a flag list: a local memory read, no network hop. It gives wrong answers when servers must agree, because each server holds its own copy and cannot invalidate the others.

solid answer

~50 s

APCu reads from the server's own shared memory, so a lookup costs a memory copy rather than a network round-trip and survives an outage of the cache server. For a feature-flag list read on every request and changed a few times a day, that is ideal: `apcu_store('flags:v1', $flags, 30)` on each server, refreshed from the source of truth when it expires. The price is **coordination**. Each server has its own copy, so after a flag change servers disagree for up to the TTL; `apcu_delete()` on one server, or from a deploy script run with the CLI, clears nothing elsewhere. A PHP-FPM restart empties one server's store, and a new server starts cold. Use a shared cache such as Redis when every node must see the same value at once, when data is written by requests, or when the data set is too large to copy onto every server.

go deeper

for a junior

Remember that APCu lives on one server and a network cache is shared. Local is faster; shared keeps every server on the same value.

for a middle

Explain the per-server copy, bounded staleness by TTL, and why a CLI script or one server's delete does not clear the other caches.

for a senior

Pick APCu or a shared cache from the data's properties: size, write rate, staleness tolerance, rebuild cost. Plan cold starts and deploy-time invalidation explicitly.

for a principal

Frame the choice as a consistency budget: which data may be stale per server and for how long, and where a two-tier design with a shared source of truth is worth its complexity.

## Two kinds of cache A PHP application can cache data in two very different places: - A **local, in-process-tree cache** such as APCu: each server keeps its own copy in shared memory, and PHP-FPM workers on that server read it directly. - A **shared network cache** such as Redis or Memcached: one logical store that every server reaches over the network. The choice is a trade between speed and independence on one side, and coordination on the other. ## The feature-flag scenario A shop reads a list of about 40 feature flags on every request. The flags live in a database table and change a few times a day. Reading the table on every request is wasteful; caching it is obvious. The question is where. ```php <?php declare(strict_types=1); function flags(PDO $pdo): array { $flags = apcu_fetch('shop:flags:v1', $hit); if (!$hit) { $flags = $pdo->query('SELECT name, enabled FROM feature_flags') ->fetchAll(PDO::FETCH_KEY_PAIR); apcu_store('shop:flags:v1', $flags, 30); } return $flags; } ``` With APCu, each web server fetches the table at most about once every 30 seconds, and every request in between reads a local copy. ## Where APCu wins | Property | APCu | Network cache | |---|---|---| | Cost per read | a shared-memory lookup and copy | a network round-trip plus deserialization | | Failure dependency | none beyond the server itself | the cache server and the network | | Data size that fits | small; copied onto every server | large; stored once | | Consistency across servers | none; each server has its own copy | one value for everyone | | Invalidation | local only | one delete is seen by all | | Survives a PHP-FPM restart | no | yes | APCu is the better fit when the data is: 1. **small** enough to copy onto every server; 2. **read far more often than written**; 3. **tolerant of bounded staleness**, where a few seconds of disagreement between servers is acceptable; 4. **cheap to rebuild** when a server starts cold. A feature-flag list, a country list, a routing table or a compiled configuration array usually meets all four. ## Where APCu gives wrong answers - **Servers disagree after a change.** Turn a flag off at 12:00:00 and each server keeps serving its cached copy until its own entry expires. With a 30-second TTL, for up to 30 seconds some requests see the old value and some the new, depending on which server handles them. A user can see a feature appear and disappear between two clicks. - **Invalidation does not propagate.** An admin action that calls `apcu_delete('shop:flags:v1')` clears the cache on the one server that handled that request. The others keep the stale entry until it expires. - **The CLI has a different cache.** A deploy or cron script run with `php` starts its own process, and APCu is disabled there by default (`apc.enable_cli=0`). Even when enabled, a CLI process gets its own short-lived store, so `apcu_clear_cache()` from a deploy script does not touch what PHP-FPM workers read. - **Restarts and new servers start cold.** A restart or a scale-out produces a burst of rebuilds against the database, one per server. - **Writes from requests do not belong here.** Counters, sessions, rate limits or carts updated by requests would diverge per server; they need one shared store. ## Making APCu's weaknesses acceptable - Pick the TTL as the **maximum staleness** you accept, not as a performance knob. - For urgent changes, version the key (`shop:flags:v2`) in a deploy so every server misses immediately after the new code arrives. - To clear APCu on purpose, do it through a web request to each server, or by restarting PHP-FPM, not from a CLI script. - If flags must switch everywhere at once (a kill switch for a broken payment provider), read that particular flag from a shared store. Whatever you choose, write down the maximum staleness the product accepts for each cached value; the TTL follows from it, and the choice of APCu or a shared cache follows from whether that staleness may differ between servers. Some teams combine both: a shared cache as the source every server agrees on, and APCu with a short TTL in front of it to absorb the per-request reads.

  • A deploy script runs php bin/clear-cache.php, which calls apcu_clear_cache(), but web requests keep serving old data. Why?
    The script runs in a separate CLI process. APCu is disabled in the CLI by default (`apc.enable_cli=0`), and even when enabled the CLI process gets its own short-lived store, not the shared memory of the PHP-FPM master. Clear the web cache from inside PHP-FPM, for example through an internal endpoint on each server, or by restarting PHP-FPM, or change the key prefix in the deploy.
  • How would you make a flag change visible on all servers within one second without giving up APCu?
    Keep APCu only as a short buffer: a TTL of about one second in front of a shared store such as Redis or the database. Each server then consults the shared source at most once a second, which is still far fewer reads than one per request, and servers never disagree for longer than that TTL. The shared store carries the truth.

APCu is a printed timetable pinned up in each station: reading it is instant and works when the head office phone is down, but when the schedule changes, each station keeps its old sheet until its next scheduled reprint. A network cache is phoning head office every time: slower, but everyone hears the same answer.

saying these in an interview costs you the question

  • Assumes apcu_delete() on one web server clears that key on all servers.
  • Clears the web servers' APCu from a CLI deploy script and expects it to work.
  • Uses APCu for counters or rate limits that must be global across servers.
  • Picks the APCu TTL for speed without considering how long servers may disagree.
  • Believes APCu entries survive a PHP-FPM restart like Redis data does.
open as a page

In PHP, how do APCu's apcu_store() and apcu_fetch() keep data between requests, and why is comparing apcu_fetch()'s result with false not enough?

level: juniorimportance: should knowfreq 36%

basics

~20 s

APCu is a PECL extension that keeps user data in the shared memory of one PHP server, readable by later requests. apcu_fetch() returns false on a miss, so a cached false is indistinguishable unless you pass its by-reference $success argument.

open as a page

In PHP, what does the realpath cache store, and how do realpath_cache_size, realpath_cache_ttl and open_basedir affect it?

level: middleimportance: should knowfreq 26%

basics

~20 s

The realpath cache stores resolved absolute paths for files and directories PHP touches, saving repeated filesystem lookups. It is sized by realpath_cache_size (default 4M), expires entries after realpath_cache_ttl (default 120 s), and is disabled when open_basedir is set.

open as a page

In PHP, what does apcu_entry() guarantee when many requests miss the same cached feature-flag list at once, and what does its lock cost?

level: seniorimportance: should knowfreq 24%

basics

~20 s

apcu_entry($key, $callback, $ttl) returns the cached value or runs the callback once and caches the result, so concurrent misses do not all rebuild it. The callback runs under APCu's exclusive cache lock, blocking every other APCu call on the server.

open as a page

In APCu for PHP, what happens when apc.shm_size fills up, and how do apc.ttl and per-entry TTLs change what gets evicted?

level: seniorimportance: should knowfreq 22%

basics

~20 s

When APCu's shared memory (apc.shm_size, default 32M) is full, it first removes expired entries if it can; if apc.ttl is 0 (the default) or that frees too little, it clears the entire cache, causing a burst of misses.

open as a page