What you essentially want is a lock pattern:
$lockPrefix = "!lock__";
$result = $this->loadFromCache($key);
if (empty($result)) {
$sleepLimit = 2000; // 2s timeout
$sleepCount = 0;
$cacheBlocked = 0;
while ($this->loadFromCache($lockPrefix . $key) == 1) {
// signal that something else is updating the cache
$cacheBlocked = 1;
// sleep for 1ms
usleep(1000);
// timeout logic...
$sleepCount++
if ($sleepCount == $sleepLimit) {
die("Cache read timeout.");
}
}
if ($cacheBlocked == 1) {
// something else updated the cache while we were waiting
// so we can just read that result now
$result = $this->loadFromCache($key);
} else {
$this->writeToCache($lockPrefix . $key, 1); // lock
$result = $this->makeSlowSqlQuery();
$this->writeToCache($key, $result);
$this->writeToCache($lockPrefix . $key, 0); // release
}
}
The idea is that the cache is global, so can be used to hold a lock pattern across requests. You're essentially creating a mutex across the cache entry with an added bit of logic to ensure that only one slow query is kicked off.