databases storage and caching
APCu Object Caching
APCu is a fast local memory cache for PHP values. It can make repeated work cheaper inside one PHP server, but it is not a shared cache and it must never become the only place important data exists.
The main skill is knowing where APCu belongs. A junior PHP developer should be able to use it for safe local acceleration, avoid it for shared application state, and design cache keys and misses so the application still works when APCu is empty.
What APCu Stores
APCu stores PHP values in memory on the server running PHP. A value can be a string, number, boolean, array, or object that PHP can store safely.
Common APCu use cases include:
- derived configuration that is expensive to rebuild from files;
- small lookup maps used on many requests;
- feature flag snapshots that may be slightly stale;
- metadata discovered by a framework or library;
- results of pure calculations that can be rebuilt.
Those are acceleration use cases. The database, filesystem, API, or another durable service still owns the real data.
Local Means Local
The most important APCu rule is locality. If the application runs on three web servers, each server has its own APCu cache. A key stored on server A is not stored on server B.
<?php
declare(strict_types=1);
function describeApcuScope(int $serverCount): string
{
if ($serverCount === 1) {
return 'APCu is local to this PHP server.';
}
return 'APCu is local to each PHP server; values are not shared across servers.';
}
echo describeApcuScope(3) . PHP_EOL;
// Prints:
// APCu is local to each PHP server; values are not shared across servers.
This is why APCu is a poor fit for shared sessions, distributed locks, queues, counters, and anything that every server must see immediately. Use Redis or Memcached for a shared cache.
Cache Misses Must Be Normal
A cache miss is not an error. APCu can be empty after a deploy, restart, memory pressure, TTL expiry, or manual cache clear. Code that uses APCu must know how to rebuild the value.
This example models the read path without requiring the APCu extension to be installed.
<?php
declare(strict_types=1);
function localSettingsCachePlan(bool $apcuAvailable, bool $cacheHit): array
{
if (!$apcuAvailable) {
return [
'source' => 'loader',
'action' => 'read settings directly because APCu is unavailable',
];
}
if ($cacheHit) {
return [
'source' => 'apcu',
'action' => 'return cached settings',
];
}
return [
'source' => 'loader',
'action' => 'load settings, then store a fresh APCu copy',
];
}
print_r(localSettingsCachePlan(true, false));
// Prints:
// [source] => loader
// [action] => load settings, then store a fresh APCu copy
The important behaviour is the fallback. APCu should improve performance when it works, not decide whether the feature works at all.
Key Design
APCu keys share one local namespace. Clear, predictable keys prevent collisions between different parts of the application.
<?php
declare(strict_types=1);
function productSummaryCacheKey(int $productId): string
{
return 'app:product_summary:v1:' . $productId;
}
echo productSummaryCacheKey(42) . PHP_EOL;
// Prints:
// app:product_summary:v1:42
A useful key usually includes an application prefix, the thing being cached, a version when the stored structure may change, and the identifier for the value. Avoid keys built from raw personal data such as email addresses.
TTLs
A TTL is the number of seconds a value may stay in the cache. In APCu, a TTL of 0 usually means the value can live until it is deleted, evicted, or the process environment is restarted.
<?php
declare(strict_types=1);
function ttlForLocalCache(string $valueType): int
{
return match ($valueType) {
'feature_flags' => 60,
'navigation_settings' => 300,
'framework_metadata' => 0,
default => 120,
};
}
echo ttlForLocalCache('feature_flags') . PHP_EOL;
echo ttlForLocalCache('navigation_settings') . PHP_EOL;
// Prints:
// 60
// 300
Short TTLs suit values that can change during normal business activity. Longer TTLs suit values that only change on deployment or cache clear.
Real APCu Calls
Real code normally uses apcu_fetch() to read and apcu_store() to write. The exact project code will depend on the framework, but the shape is consistent: try the cache, rebuild on miss, store with a TTL.
<?php
declare(strict_types=1);
function readLocalProductSummary(int $productId, callable $loader): array
{
$key = 'app:product_summary:v1:' . $productId;
if (function_exists('apcu_fetch') && function_exists('apcu_store')) {
$success = false;
$cached = apcu_fetch($key, $success);
if ($success && is_array($cached)) {
return $cached;
}
$fresh = $loader($productId);
apcu_store($key, $fresh, 300);
return $fresh;
}
return $loader($productId);
}
$summary = readLocalProductSummary(42, function (int $productId): array {
return [
'id' => $productId,
'name' => 'Desk lamp',
'status' => 'active',
];
});
print_r($summary);
// Prints:
// [id] => 42
// [name] => Desk lamp
// [status] => active
The function_exists() checks make the example safe on machines where APCu is not installed. In a real application, this decision is often wrapped in a cache service instead of repeated everywhere.
Deleting And Refreshing Values
APCu values can be deleted, but deletion is still local to the PHP server handling that request. If a product changes and one server deletes its APCu key, the other servers still have their local copies until their TTLs expire or they delete them too.
<?php
declare(strict_types=1);
function productCacheInvalidationPlan(int $serverCount): string
{
if ($serverCount === 1) {
return 'Delete the APCu key and reload it on the next miss.';
}
return 'Use a short TTL or a shared cache because APCu deletion only affects one server.';
}
echo productCacheInvalidationPlan(4) . PHP_EOL;
// Prints:
// Use a short TTL or a shared cache because APCu deletion only affects one server.
This is one reason TTLs matter. In a multi-server application, APCu is usually best for data that can tolerate short-lived differences between servers.
CLI And Web Differences
APCu is often configured differently for command-line PHP and web PHP. A CLI script may not use APCu unless apc.enable_cli is enabled. A successful CLI experiment does not prove that PHP-FPM or Apache is configured the same way.
This matters when writing tests, queue workers, console commands, and debugging scripts. Check the environment that will actually run the code.
Memory, Size, And Security
APCu uses local memory. Small values are fine; large result sets are usually a bad idea. Avoid caching thousands of full database rows, large report exports, per-user data for many active users, or objects that are risky to serialize.
Treat APCu as application memory, not a secure vault. Do not casually cache secrets, access tokens, password reset data, or sensitive personal data. If sensitive data must be cached, keep the TTL short, use internal IDs in keys, and make sure the value can be removed through the application's normal invalidation path.
What To Check
Before moving on, make sure you can:
- explain that APCu is local to one PHP server environment;
- choose APCu only for values that can be rebuilt on a miss;
- design clear keys with prefixes, identifiers, and versions;
- choose a TTL based on how stale the value may become;
- explain why APCu is wrong for queues, locks, sessions, and shared counters;
- recognise that CLI APCu behaviour can differ from web PHP;
- keep source-of-truth data in durable storage, not APCu.
Practice
Practice: Design APCu Local Cache Behaviour
Build a small PHP helper that decides how an application should handle a local APCu cache for product summaries.
Requirements
- Create a cache key for a product summary using the product ID.
- Accept whether APCu is available.
- Accept whether the value was found in APCu.
- Return the source of the value, the action the application should take, and the TTL.
- Show that APCu unavailable and APCu miss both fall back to the loader.
- Include one example where APCu is not the right cache because the value must be shared across servers.
Keep the exercise focused on behaviour. You do not need to connect to a real database or require the APCu extension.
Show solution
This solution models the decisions a real cache wrapper would make before calling apcu_fetch() or apcu_store().
<?php
declare(strict_types=1);
function productSummaryKey(int $productId): string
{
return 'app:product_summary:v1:' . $productId;
}
function productSummaryCachePlan(int $productId, bool $apcuAvailable, bool $cacheHit): array
{
$key = productSummaryKey($productId);
if (!$apcuAvailable) {
return [
'key' => $key,
'source' => 'loader',
'action' => 'read from the normal data source because APCu is unavailable',
'ttl_seconds' => 0,
];
}
if ($cacheHit) {
return [
'key' => $key,
'source' => 'apcu',
'action' => 'return the local cached product summary',
'ttl_seconds' => 300,
];
}
return [
'key' => $key,
'source' => 'loader',
'action' => 'load the product summary, then store it in APCu',
'ttl_seconds' => 300,
];
}
function sharedCacheChoice(bool $mustBeSharedAcrossServers): string
{
if ($mustBeSharedAcrossServers) {
return 'Use Redis or Memcached because APCu is local to one PHP server.';
}
return 'APCu can fit when the value is small, local, and safe to rebuild.';
}
$plans = [
productSummaryCachePlan(42, true, true),
productSummaryCachePlan(42, true, false),
productSummaryCachePlan(42, false, false),
];
foreach ($plans as $plan) {
echo $plan['source'] . ' | ' . $plan['action'] . ' | ' . $plan['key'] . PHP_EOL;
}
echo sharedCacheChoice(true) . PHP_EOL;
// Prints:
// apcu | return the local cached product summary | app:product_summary:v1:42
// loader | load the product summary, then store it in APCu | app:product_summary:v1:42
// loader | read from the normal data source because APCu is unavailable | app:product_summary:v1:42
// Use Redis or Memcached because APCu is local to one PHP server.
The fallback path is the important part. APCu should make repeated local reads faster, but a miss or unavailable extension should still leave the application able to load the product summary from its normal source.