Data competition refers to multiple processes or threads accessing shared data at the same time, and at least one process or thread writes the data, resulting in unstable execution results of the program or unpredictable behavior. In PHP, especially when using APC cache, since cached data is usually shared globally, if multiple requests write to the same cache key at the same time, data race issues may occur.
The function of the apcu_entry function is to check whether there are already specified key-value pairs in the cache. If not, it executes a callback function to generate the data and store it in the cache. Under normal circumstances, it can effectively avoid the duplicate calculation problem of cache failure, but in concurrent scenarios, if multiple requests operate on the same cache key at the same time, the following problems may be caused:
Repeated calculation : When multiple requests are concurrent, although the cache does not hit, multiple requests may enter the callback function at the same time and calculate the same result repeatedly, resulting in waste of resources.
Inconsistency : Since cache operations are performed in callback functions, concurrent operations may cause data overwrite or inconsistency.
In order to solve the problem of data competition, the use of apcu_entry can be optimized in the following ways:
The most common way is to use a lock mechanism to ensure that only one process can perform cache writes at a time. In PHP, you can use file locks, Redis, Memcached, etc. to implement locks. Here is a simple implementation of using file locks:
$cache_key = 'my_cache_key';
$lock_key = $cache_key . '_lock';
if (apcu_exists($lock_key)) {
// If the lock already exists,Wait for a while and try again
sleep(1);
} else {
// Add lock
apcu_store($lock_key, true, 10); // The lock expiration time is10Second
// Generate cached values
$value = generate_cache_value();
// Store cached values
apcu_store($cache_key, $value);
// Release the lock
apcu_delete($lock_key);
}
By using locks, we can ensure that at the same time only one process can enter the cache-generated callback logic, thereby avoiding repeated calculations.
The apcu_add function is similar to the apcu_entry function, but it avoids overwriting existing cache values. So when you just need to make sure the cache key exists, you can use apcu_add to reduce the chance of data competition.
$value = apcu_add('my_cache_key', generate_cache_value(), 3600);
If the cache key already exists, apcu_add will not overwrite it, thus reducing race conditions generated during concurrency.
Another simple way is to check whether the cache already exists before entering the callback function, avoid repeated calculations when multiple requests are executed simultaneously. A separate flag can be used to record whether the cache is being calculated or has been calculated. For example:
$cache_key = 'my_cache_key';
if ($result = apcu_fetch($cache_key)) {
// Cache hit,Use directly
} else {
// Cache miss,Check the calculation status
$status_key = $cache_key . '_status';
if (!apcu_exists($status_key)) {
// No signs being calculated,Mark as calculating
apcu_store($status_key, true);
// Perform cached value generation operation
$result = generate_cache_value();
// Storage cache
apcu_store($cache_key, $result);
// Remove the calculation status flag
apcu_delete($status_key);
} else {
// Wait for other requests to complete the cache operation
while (!$result = apcu_fetch($cache_key)) {
sleep(1);
}
}
}
In this way, when multiple requests operate on the same cache key at the same time, only the first request will generate a cache, and the remaining requests will wait for the cache generation to be completed.
If your cached data is very important and has high requirements for consistency, you can consider using a mechanism similar to database transactions to ensure data consistency in a concurrent environment. Although APC does not directly support transactions, you can use database transactions or external caching systems (such as Redis) to achieve similar effects.