The following type of messages appear in my ‘application.log’:
2015/05/29 22:42:50 [error] [system.db.CDbCommand] CDbCommand::execute() failed: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '6941f72ad3bc0878c388c2969f06f811' for key 'PRIMARY'. The SQL statement executed was: INSERT INTO cache (id,expire,value) VALUES ('6941f72ad3bc0878c388c2969f06f811',1432939375,:value).
2015/05/29 22:42:50 [error] [system.db.CDbCommand] CDbCommand::execute() failed: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '2cad0038d7a678540cbd1442525e1b7c' for key 'PRIMARY'. The SQL statement executed was: INSERT INTO cache (id,expire,value) VALUES ('2cad0038d7a678540cbd1442525e1b7c',0,:value).
2015/05/29 23:10:29 [error] [system.db.CDbCommand] CDbCommand::execute() failed: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '2cad0038d7a678540cbd1442525e1b7c' for key 'PRIMARY'. The SQL statement executed was: INSERT INTO cache (id,expire,value) VALUES ('2cad0038d7a678540cbd1442525e1b7c',0,:value).
After checking the contents of the key (because I do not have a stack trace), I conclude that this is related to the following section of code:
$scopes=Yii::app()->getCache()->get($id);
$scopes=false;
if(!is_array($scopes)) {
$scopes= ...;
Yii::app()->getCache()->set($id,$scopes);
}
return $scopes;
As you can see ‘$scopes’ is explicitally set to ‘false’ - the purpose is to temporarily inhibit the use of the cache contents and to recompute the scopes every time.
(Regarding the reason to cache the scopes: there are a lot of scopes with some special constructions).
My conclusion is that the key already exists when calling ‘set’ - especially because ‘set’ does not define an expiration time.
However the documentation says:
* Stores a value identified by a key into cache.
* If the cache already contains such a key, the existing value and
* expiration time will be replaced with the new ones.
Meaning that it is allowed to set a value in the cache even if the key exists already.
So why might this fail?
As the reader can conclude, the CDbCache mechanism is used. Checking the Yii “core” code, one can read that ‘set’ eventually calls ‘CDbCache->setValue()’. This latter method will first call ‘CDbCache->deleteValue()’ and then ‘CDbCache->addValue()’. Which means that in case of concurrency, another process could add the value just after this ‘deleteValue’ is done and hence ‘addValue’ will result in the notifications mentionned at the top of this message.
I have been using this setup for a long time already (I forgot to remove the ‘$scopes=false’ from my code) and I did not notice the issue on another system. Why?
The production system is using CApcCache for all PHP executions (Web AND CLI) while the setup with the issue is using APC only for the Web application.
In the case of ‘CApcCache’, ‘apc_store’ is called - which is a single instruction to add or update the cache value. Hence no conflict appears there.
What could be done?
-
I could setup the server to use APC Cache or another cache in all cases, but that would not fix the issue for servers where this is not possible.
-
I could remove ‘$scope=false’ in this application - this is what is done now, but this does not inhibit the rare occurrence where the cache entry has to be created, as I have added an expiration value.
-
CDbCache could be improved, to use an ‘UPDATE’ method if the key already exists and an ‘INSERT’ method if it does not exist. The ‘UPDATE’ could fail because the key was just deleted by another process as it was on the edge of expiring. That would not be a real issue (if the failure is ignored). -> PREFERRED
-
One could argue to do nothing because these cache saving failures are not impacting functionnality. It seems better though to limit the number of potential occurrences of this behavior and change the ‘INSERT’ with an ‘UPDATE’ if the key exists. The ‘deleteValue’ call would be replaced with a test of existance which will probably execute faster too.