2009-07-27 4 views
4

Mon site Django a récemment commencé à lancer des erreurs de mon code de mise en cache et je ne peux pas comprendre pourquoi ...Django cache.set() provoquant clé doublon

J'appelle:

from django.core.cache import cache 
cache.set('blogentry', some_value) 

Et l'erreur lancée par Django est:

TransactionManagementError: This code isn't under transaction management 

Mais en regardant les journaux de base de données PostgreSQL, il semble provenir de cette erreur:

STATEMENT: INSERT INTO cache_table (cache_key, value, expires) VALUES (E'blogentry', E'pickled_version_of_some_value', E'2009-07-27 11:10:26') 
ERROR: duplicate key value violates unique constraint "cache_table_pkey" 

Pour la vie de moi, je n'arrive pas à comprendre pourquoi Django essaie de faire un INSERT au lieu d'un UPDATE. Des pensées?

+0

Est-ce que la mise en mémoire cache n'est pas mise en cache dans la base de données? – thedz

+4

Dépend de ce que vous mettez en cache. –

+0

Je l'ai édité à "blogentry" là-bas, mais il est en train de mettre en cache toute une série de données connexes pour un widget de la barre latérale du blog. –

Répondre

4

C'est une course typique. Il vérifie si la clé que vous avez insérée existe; si ce n'est pas le cas, il fait une insertion, mais quelqu'un d'autre peut insérer la clé entre le compte et l'insertion. Les transactions n'empêchent pas cela.

Le code semble s'attendre à cela et essayer de le traiter, mais quand j'ai regardé le code pour gérer ce cas, je pouvais voir immédiatement qu'il était cassé. Rapporté ici: http://code.djangoproject.com/ticket/11569

Je vous recommande vivement de rester dans le backend memcache.

+0

Je lisais la source pour le backend du cache db et pensais la même chose à propos du ticket que vous avez ouvert. Content que ce ne soit pas juste moi qui me sentais comme si quelque chose me manquait ici. Nous allons mettre en place memcached à la place. La lecture de la source pour le backend dernier cri est tellement plus rassurante. Merci! –

0

Le code dans le noyau/cache/backend/db.py se lit en partie:

cursor.execute("SELECT cache_key, expires FROM %s WHERE cache_key = %%s" % self._table, [key]) 
try: 
    result = cursor.fetchone() 
    if result and (mode == 'set' or 
      (mode == 'add' and result[1] < now)): 
     cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE cache_key = %%s" % self._table, [encoded, str(exp), key]) 
    else: 
     cursor.execute("INSERT INTO %s (cache_key, value, expires) VALUES (%%s, %%s, %%s)" % self._table, [key, encoded, str(exp)]) 

Je dirais que vous faites l'instruction INSERT INTO au lieu de la mise à jour car résultat la valeur false . Pour une raison quelconque, cursor.fetchone() renvoie 0 lignes lorsqu'il y en a réellement une.

Si vous ne pouvez pas casser un débogueur ici, je mettrais des instructions de trace dans la source pour confirmer que cela se produit réellement.

+1

ouais, je lisais la source encore et encore ... comme l'a souligné Glenn, le vrai coupable ici est que le backend db a des problèmes de concurrence potentiels où une seconde instruction INSERT peut survenir après que select retourne 0, causant le cache d'origine .set() échoue, ce qui déclenche alors un appel transaction.rollback() non fonctionnel. –

0

J'ai résolu ce problème en créant un backend de cache personnalisé, en remplaçant la fonction _base_set() et en modifiant l'instruction INSERT INTO comme ceci. Cette astuce SQL empêche l'INSERT de se produire dans le cas où la clé cache_key existe déjà.

cursor.execute("INSERT INTO %s (cache_key, value, expires) SELECT %%s, %%s, %%s WHERE NOT EXISTS (SELECT 1 FROM %s WHERE cache_key = %%s)" % (table, table), 
       [key, encoded, connections[db].ops.value_to_db_datetime(exp), key]) 
Questions connexes