APC’s opcode cache and its user cache are separate things

APC is talked about as “the PHP cache”, and it is two caches in one extension. The opcode cache stores compiled files and requires nothing from the application. The user cache, behind apc_store() and apc_fetch(), stores whatever you put in it. By default they share one shared-memory segment, which means the second one can evict the first.

// opcode cache: nothing to call, it either has the file or compiles it
$opcode = apc_cache_info('', true);
printf("%d files, %d hits, %d missesn",
    $opcode['num_entries'], $opcode['num_hits'], $opcode['num_misses']);

// user cache: explicit, and in the same segment unless you say otherwise
$rates = apc_fetch('vat:rates', $found);

if (!$found) {
    $rates = $this->rates->load();
    apc_store('vat:rates', $rates, 3600);
}

$user = apc_cache_info('user', true);
printf("%d entries, %d expungesn", $user['num_entries'], $user['expunges']);

The failure mode is a site that gets slower with no deploy behind it: a user cache filling with serialised result sets pushes compiled files out of the segment, they are recompiled on the next request, and the only visible symptom is a rising expunge count and fragmentation on apc.php. So watch the expunges rather than the hit rate — a hit rate can look excellent while the cache is thrashing. Give the segment enough room for both; apc.shm_size = 128M is a sensible floor for a framework application and the default is nowhere near it. Two limits are worth stating plainly. Nothing may treat the user cache as storage, because the segment is torn down on an Apache restart and the data is simply gone. And the CLI SAPI gets its own segment, with apc.enable_cli off by default, so a cron script cannot warm or invalidate anything the web server is holding. The moment more than one process needs to agree on a cached value, the answer is Memcached rather than a larger APC.