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?
answer
- apc.shm_size defaults to 32M
- apc.ttl defaults to 0
- expired entries go first
- otherwise the whole cache is wiped
- watch evictions and free memory
basics
~20 sWhen 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.
solid answer
~50 sAPCu allocates one shared-memory segment of `apc.shm_size` (default `32M`) when the server starts. When an insert does not fit, it expunges: if `apc.ttl` is set, it first removes entries whose own TTL has passed and entries without a TTL that nobody has accessed for `apc.ttl` seconds. If `apc.ttl` is `0`, the default, or that cleanup frees too little, **it clears the entire cache**. There is no gradual least-recently-used eviction, so an undersized cache produces periodic full wipes: every request on the server misses at once and rebuilds against the database. So a non-zero `apc.ttl` is what enables partial cleanup at all: it turns on the expired-entry pass, which then also removes entries whose own `apcu_store()` TTL has passed. The fix is sizing `apc.shm_size` above the working set, setting `apc.ttl`, giving entries TTLs, and watching free memory with `apcu_sma_info()` and hit rates with `apcu_cache_info(true)`.
code
ini · 8 lines; conf.d/40-apcu.ini
extension=apcu
apc.enabled=1
apc.shm_size=128M
; entries stored without a TTL become removable after 1 hour unused
apc.ttl=3600
; keep the CLI out of APCu (the default)
apc.enable_cli=0go deeper
Know that APCu has a fixed memory size set by apc.shm_size and that a full cache loses data.
Explain the expunge order: expired entries first when apc.ttl is set, otherwise a full clear. Know the defaults of apc.shm_size and apc.ttl.
Diagnose the sawtooth of full wipes from free-memory and hit-rate data, size the segment above the working set, and set apc.ttl plus per-entry TTLs so an expunge is not all-or-nothing.
Set a memory budget per server that accounts for APCu, OPcache and worker memory together, and decide which data is worth the per-server copy at all.
## A fixed block of shared memory APCu does not grow on demand. At server start it maps one shared-memory segment whose size is set by `apc.shm_size` (default `32M`; the setting is `INI_SYSTEM`, so it goes in `php.ini` or the extension's `.ini` file and needs a restart to change). Every entry, key, value and metadata, is allocated from that segment. When a new entry does not fit, APCu must make room. ## What APCu does when it is full The APCu documentation describes the order: 1. If `apc.ttl` is set, APCu first removes **expired** entries: entries whose own TTL has passed, and entries with no TTL that have not been accessed in the last `apc.ttl` seconds. 2. If `apc.ttl` is not set, or removing expired entries did not free enough space, **APCu clears the entire cache**. That second step is the surprise. APCu is not an LRU cache that drops the coldest entries one by one. With the default `apc.ttl = 0`, the documented expunge skips the expired-entry pass entirely, so the first full cache ends in a **complete wipe**, even if some entries carry their own TTL. ## Why a full wipe hurts - Every request on the server misses at the same moment. - Each miss rebuilds its value: database queries, file parsing, remote calls. - The rebuilt entries refill the cache, which, if it is still too small, fills and wipes again. On a graph this looks like periodic spikes of database load and latency from one server at a time, a pattern easy to misattribute to the database. ## The settings | Directive | Default | Meaning | |---|---|---| | `apc.shm_size` | `32M` | size of the shared-memory segment | | `apc.ttl` | `0` | seconds after which unaccessed entries **without** their own TTL count as expired | | `apc.gc_ttl` | `3600` | how long a removed entry may stay on the garbage-collection list while still referenced | | `apc.entries_hint` | `512 * apc.shm_size` | sizing hint for the hash table | | `apc.use_request_time` | `0` | whether TTLs are measured against the request start time instead of the current time | Per-entry TTLs work alongside `apc.ttl`: an entry stored with `apcu_store($key, $value, 300)` expires after 300 seconds and is treated as a miss after that, whatever `apc.ttl` says, but when memory runs out it is `apc.ttl` that decides whether expired entries are removed selectively or the cache is cleared. The manual notes that `apc.ttl` removal is opportunistic, so an old entry without its own TTL can still be readable until an insert or an expunge actually removes it. `apc.use_request_time` matters for long-running CLI scripts with `apc.enable_cli` turned on: when it is `1`, time stands still for TTL purposes during one run. ## Sizing and monitoring - **Measure the working set.** `apcu_sma_info()` returns the segment size and available memory (`seg_size`, `avail_mem`); `apcu_cache_info(true)` returns hit and miss counters without the per-entry list. - **Watch the expunge count.** The `apc.php` status page bundled with APCu shows how often the cache filled up; in a healthy setup that number is close to zero. - **Set `apc.ttl`** to a non-zero value so that an expunge removes expired entries first instead of wiping everything, and **give entries TTLs** so that there is something expired to remove. - **Size above the peak**, with headroom for key churn: versioned keys such as `shop:v4:*` leave the old `v3` entries in memory until they are removed. - **Store less.** Large arrays that are read partially are better split into smaller entries. ## Fragmentation and churn A cache can also run out of space before it looks full. Entries of very different sizes, stored and deleted repeatedly, leave free memory split into blocks too small for the next large entry. `apcu_sma_info()` without the `true` argument lists the free blocks per segment, which shows whether free memory is one large block or many small ones. Keeping entries of similar, moderate size and avoiding rewriting large values on every request both reduce this churn. ## A diagnostic example A team sees database load spike on one web server every few minutes, but never on all servers at once. `apcu_sma_info()` shows `avail_mem` near zero just before each spike and near the full 32 MB just after. The cache is filling, being wiped, and refilling. Raising `apc.shm_size` to fit the working set with headroom, setting `apc.ttl`, and adding TTLs to entries that were stored forever, turns the sawtooth into a flat line.
- Why must the APCu health check run as a web request rather than from the command line?The PHP-FPM workers' APCu segment belongs to the FPM master's process tree. A CLI script has no access to it: APCu is disabled in the CLI by default, and when enabled it creates a separate, empty store. Expose the check through an internal endpoint served by PHP-FPM so it reports the memory the web requests actually use.
- Does raising apc.ttl make entries stored with apcu_store($k, $v, 60) live longer?No. `apc.ttl` only applies to entries stored without their own TTL, deciding when unaccessed entries count as expired. An entry with an explicit 60-second TTL still expires after 60 seconds. Raise the per-entry TTL in code if those entries should live longer.
saying these in an interview costs you the question
- Believes APCu evicts the least recently used entries one at a time when full.
- Assumes apc.shm_size grows automatically when more data is stored.
- Thinks apc.ttl overrides the TTL passed to apcu_store().
- Reads APCu memory statistics from a CLI script and trusts the numbers.
- Stores every entry with TTL 0 and leaves apc.ttl at 0 on a busy server.