Jason Moiron committed ef0acb8 Merge

Merged in tobias.mcnulty/johnny-cache (pull request #7)

Comments (0)

Files changed (4)


 encountering invalidation issues, please report a bug and see the 
 :ref:`manual-invalidation` section for possible workarounds.
+Multiple Databases
+Johnny supports multiple databases in a variety of configurations. If using
+Johnny to cache results from a slave database, one should use the
+``DATABASES .. JOHNNY_CACHE_KEY`` setting (see below) to ensure that slave
+databases use the same database key as the master database.
+Please note that, in a master/slave database configuration, all of the typical
+problems still exist.  For example, if you update a table and then initiate a
+read on the slave before the change has had time to propagate, stale data may
+be returned and cached in Johnny.  As such, be certain that read queries
+prone to this issue are executed on a database that is guaranteed to be up to
 The following settings are available for the QuerySet Cache:
 will fall back to the deprecated ``JOHNNY_CACHE_BACKEND`` setting if set,
 and then to the default cache.
+``DATABASES .. JOHNNY_CACHE_KEY`` allows  you to override the default key
+used for the given database.  This is useful, for example, in master/slave
+database setups where writes are never issued to the slave, so Johnny would
+otherwise not invalidate a query cached for that slave when a write occurs
+on the master.  For example, if you have a simple database setup with one
+master and one slave, you could set both databases to use the database key
+``default`` when constructing cache keys like so::
+    DATABASES = {
+        # ...
+        'default': {
+            # ...
+            'JOHNNY_CACHE_KEY': 'default',
+        },
+        'slave': {
+            # ...
+            'JOHNNY_CACHE_KEY': 'default',
+        },
+    }
 ``DISABLE_QUERYSET_CACHE`` will disable the QuerySet cache even if the
 middleware is installed.  This is mostly to make it easy for non-production
 environments to disable the queryset cache without re-creating the entire 
         alias. Total length up to 212 (max for memcache is 250).
         table = unicode(table)
-        db = unicode(db)
+        db = unicode(settings.DB_CACHE_KEYS[db])
         if len(table) > 100:
             table = table[0:68] + self.gen_key(table[68:])
         if db and len(db) > 100:
     def gen_multi_key(self, values, db='default'):
         """Takes a list of generations (not table keys) and returns a key."""
+        db = settings.DB_CACHE_KEYS[db]
         if db and len(db) > 100:
             db = db[0:68] + self.gen_key(db[68:])
         return '%s_%s_multi_%s' % (self.prefix, db, self.gen_key(*values))
         # these keys will always look pretty opaque
         suffix = self.keygen.gen_key(sql, params, order, result_type)
+        using = settings.DB_CACHE_KEYS[using]
         return '%s_%s_query_%s.%s' % (self.prefix, using, generation, suffix)


             getattr(settings, 'JOHNNY_TABLE_BLACKLIST', []))
+DB_CACHE_KEYS = dict((name, db.get('JOHNNY_CACHE_KEY', name))
+                     for name, db in settings.DATABASES.iteritems())


         for c in connections:
             self.failUnless(len(connections[c].queries) == 1)
+    def test_cache_key_setting(self):
+        """Tests that two databases use a single cached object when given the same DB cache key"""
+        if len(getattr(settings, "DATABASES", [])) <= 1:
+            print "\n  Skipping multi database tests"
+            return
+        from testapp.models import Genre
+        from django.db import connections
+        self.failUnless("default" in getattr(settings, "DATABASES"))
+        self.failUnless("second" in getattr(settings, "DATABASES"))
+        old_cache_keys = johnny_settings.DB_CACHE_KEYS
+        johnny_settings.DB_CACHE_KEYS = {'default': 'default', 'second': 'default'}
+        g1 = Genre.objects.using("default").get(pk=1)
+        g1.title = "A default database"
+        g2 = Genre.objects.using("second").get(pk=1)
+        g2.title = "A second database"
+        for c in connections:
+            connections[c].queries = []
+        #fresh from cache since we saved each
+        g1 = Genre.objects.using('default').get(pk=1)
+        g2 = Genre.objects.using('second').get(pk=1)
+        johnny_settings.DB_CACHE_KEYS = old_cache_keys
+        total_queries = sum([len(connections[c].queries)
+                             for c in connections])
+        self.assertEqual(total_queries, 1)
     def test_transactions(self):
         """Tests transaction rollbacks and local cache for multiple dbs"""