In PHP's cache mechanism, apcu_entry is a very efficient function that can obtain or set a cache entry atomically. However, in high concurrency scenarios, if multiple processes or requests try to write apcu_entry to the same key at the same time, key-value conflicts may occur, which will lead to some potential problems, such as performance degradation, data inconsistency, and even cache pollution. This article will explore the causes of this situation in depth and provide several coping strategies.
Typical usage of apcu_entry is as follows:
$value = apcu_entry('my_cache_key', function() {
// Calculate and return cached content
return heavyComputation();
}, 300);
The purpose of the above code is to look for my_cache_key in the cache. If it does not exist, execute the callback function and store the result in the cache, with an effective period of 300 seconds. On the surface, this seems to be thread-safe, but the problem occurs in a high concurrency environment: when multiple requests have not yet established the key of APCu, they will enter the callback function at the same time, repeatedly perform the recalculation logic, and even lead to write conflicts.
APCu is a user-state cache of process shared memory. Although its operations are atomic, the callback itself is not exclusive. If a key is judged "at the same time" in multiple requests, each request will call the callback function and try to write, which may lead to a contender writing.
In addition, some older versions of APCu have bugs, and may even have segfaults or cache inconsistent edge cases under extreme concurrency pressure.
Use a unified naming strategy to avoid accidental use of the same keys for different business logics. For example:
$key = 'myapp_moduleX_' . md5($param);
This can significantly reduce the probability of conflict.
"Exclusive initialization" can be achieved through file locks or APCu's own lock keys:
$key = 'my_cache_key';
$lockKey = $key . '_lock';
if (!apcu_exists($key)) {
$acquired = apcu_add($lockKey, 1, 5);
if ($acquired) {
// Obtain the lock,Execute a callback and write to the cache
$value = heavyComputation();
apcu_store($key, $value, 300);
apcu_delete($lockKey);
} else {
// Wait for the cache to be available
while (!apcu_exists($key)) {
usleep(10000); // 10ms
}
$value = apcu_fetch($key);
}
} else {
$value = apcu_fetch($key);
}
This approach avoids multiple requests concurrently entering the computing logic, but also brings up wait time and implementation complexity.
The callback function itself should avoid long-term blocking operations, such as remote calls, database queries, etc. If this is required, the cache logic should be migrated to the application initialization process to avoid real-time dynamic writing. For example:
$value = apcu_fetch('config_global');
if ($value === false) {
$value = file_get_contents('https://gitbox.net/api/config/global.json');
apcu_store('config_global', $value, 600);
}
Cache the calculation results in local memory (such as static variables or request contexts) first, and write APCu to the end of the request, which can reduce competition for shared cache:
static $localCache = [];
function getCachedData($key, $callback, $ttl = 300) {
global $localCache;
if (isset($localCache[$key])) {
return $localCache[$key];
}
$value = apcu_fetch($key);
if ($value === false) {
$value = $callback();
$localCache[$key] = $value;
register_shutdown_function(function() use ($key, $value, $ttl) {
apcu_store($key, $value, $ttl);
});
}
return $value;
}
Although apcu_entry provides an elegant cache initialization mechanism, key-value conflicts in high concurrency still need to be handled by the developer. It is recommended to use explicit locks in critical business logic to control cache write behavior or avoid instant write cache operations during requests. In addition, reasonable key naming and cache hierarchy strategies can also effectively reduce the risk of conflict. The rational use of APCu can enable PHP applications to achieve a good balance between response speed and system pressure.