Index: django/conf/global_settings.py
===================================================================
--- django/conf/global_settings.py	(revision 5770)
+++ django/conf/global_settings.py	(working copy)
@@ -283,6 +283,7 @@
 CACHE_BACKEND = 'simple://'
 CACHE_MIDDLEWARE_KEY_PREFIX = ''
 CACHE_MIDDLEWARE_SECONDS = 600
+CACHE_AVOID_STAMPEDE = False
 
 ####################
 # COMMENTS         #
Index: django/core/cache/__init__.py
===================================================================
--- django/core/cache/__init__.py	(revision 5770)
+++ django/core/cache/__init__.py	(working copy)
@@ -16,8 +16,10 @@
 """
 
 from cgi import parse_qsl
+from datetime import datetime, timedelta
+
 from django.conf import settings
-from django.core.cache.backends.base import InvalidCacheBackendError
+from django.core.cache.backends.base import InvalidCacheBackendError, BaseCache
 
 BACKENDS = {
     # name for use in settings file --> name of module in "backends" directory
@@ -29,6 +31,84 @@
     'dummy': 'dummy',
 }
 
+class WrappedValueSentinel(object):
+    pass
+
+class NoStampedeWrapper(BaseCache):
+    """
+    Avoids cache stampeding by missing on *one* request prior to actual
+    expiration.  That single missed requestor then refills the cache prior to
+    real expiration.  All other requestors still get hits.
+    
+    Adapted from MintCache from gfranxman:
+    http://www.djangosnippets.org/snippets/155/
+    """
+    def __init__(self, backend, params):
+        self.backend = backend
+
+        near_miss_seconds = int(params.get('near_miss_seconds', 5))
+        self.near_miss_delta = timedelta(seconds=near_miss_seconds)
+
+        super(NoStampedeWrapper, self).__init__(params)
+
+    def get(self, key, default=None):
+        inner_value = self.backend.get(key, None)
+        if inner_value is None: #real miss.
+            return default
+        
+        #Ensure we packed this value.
+        #If not, return the value unaltered.
+        if not isinstance(inner_value, tuple):
+            return inner_value
+            
+        try:
+            is_wrapped_sentinel, target_expire, value = inner_value
+        except ValueError:
+            return inner_value
+                
+        if not isinstance(is_wrapped_sentinel, WrappedValueSentinel):
+            return inner_value
+
+        #We're now definitely dealing with managed value.
+        near_miss_time = target_expire - self.near_miss_delta
+        now = datetime.now()
+            
+        if now >= near_miss_time:
+            #This is a near miss.
+                
+            #Push out the target_expire out past real expiration
+            # to avoid extra misses.
+            #Use a new timeout that agrees with the originally
+            # requested expiration.
+
+            #Then return a miss to this requestor, who will then be
+            # responsible for filling the cache while any other
+            # requestors still get hits.
+            new_timeout = (target_expire - now).seconds
+            pushed_out = target_expire + timedelta(seconds=(new_timeout+1))
+
+            new_inner_val = (is_wrapped_sentinel,
+                             pushed_out,
+                             value)
+
+            self.backend.set(key, new_inner_val, new_timeout)
+            
+            return default 
+        return value
+
+    def set(self, key, value, timeout=0):
+        timeout = timeout or self.default_timeout
+        target_expire = datetime.now() + timedelta(seconds=timeout)
+
+        inner_value = (WrappedValueSentinel(),
+                       target_expire,
+                       value)
+
+        self.backend.set(key, inner_value,  timeout)
+
+    def delete(self, key):
+        self.backend.delete(key)
+                               
 def get_cache(backend_uri):
     if backend_uri.find(':') == -1:
         raise InvalidCacheBackendError, "Backend URI must start with scheme://"
@@ -49,6 +129,10 @@
         host = host[:-1]
 
     cache_class = getattr(__import__('django.core.cache.backends.%s' % BACKENDS[scheme], {}, {}, ['']), 'CacheClass')
-    return cache_class(host, params)
+    cache_backend = cache_class(host, params)
 
+    if settings.CACHE_AVOID_STAMPEDE:
+        cache_backend = NoStampedeWrapper(cache_backend, params)
+    return cache_backend
+
 cache = get_cache(settings.CACHE_BACKEND)
Index: tests/regressiontests/cache/tests.py
===================================================================
--- tests/regressiontests/cache/tests.py	(revision 5770)
+++ tests/regressiontests/cache/tests.py	(working copy)
@@ -2,10 +2,12 @@
 
 # Unit tests for cache framework
 # Uses whatever cache backend is set in the test settings file.
-
-from django.core.cache import cache
 import time, unittest
 
+from django.conf import settings
+from django.core.cache import cache, get_cache, NoStampedeWrapper
+from django.core.cache.backends.base import BaseCache
+
 # functions/classes for complex data type tests
 def f():
     return 42
@@ -81,5 +83,50 @@
             cache.set(key, value)
             self.assertEqual(cache.get(key), value)
 
+class StampedeCache(Cache):
+    def setUp(self):
+        self.prior_stampede = settings.CACHE_AVOID_STAMPEDE
+        settings.CACHE_AVOID_STAMPEDE = True
+
+    def tearDown(self):
+        settings.CACHE_AVOID_STAMPEDE = self.prior_stampede
+
+    def test_stampede_delta(self):
+        from datetime import timedelta
+        stuff = [
+            ('simple://', timedelta(seconds=5)),
+            ('simple://?near_miss_seconds=30', timedelta(seconds=30))
+            ]
+        for backend_uri, delta in stuff:
+            c = get_cache(backend_uri)
+            self.assertEqual(c.near_miss_delta,
+                             delta)
+    def test_got_stampede_wrapper(self):
+        c = get_cache('simple://')
+        self.assertEqual(type(c), NoStampedeWrapper)
+        self.assert_(isinstance(c, BaseCache))
+
+    def test_near_miss(self):
+        c = get_cache('simple://?near_miss_seconds=2')
+        c.set('test_near_miss', 'actual hit', 3)
+        time.sleep(1.1)
+        self.assertEqual(c.get('test_near_miss'), None)
+        self.assertEqual(c.get('test_near_miss'), 'actual hit')
+
+    def test_unwrapped_set(self):
+        c = get_cache('simple://?near_miss_seconds=2')
+
+        test_values = [
+            1,
+            'unwrapped iterator',
+            ('unwrapped', 'three', 'tuple'),
+            ('unwrapped', 'shorter tuple'),
+            ]
+
+        for test_value in test_values:
+            c.backend.set('test_unwrapped_set', test_value, 5)
+            self.assertEqual(c.get('test_unwrapped_set'), test_value)
+            c.backend.set('test_unwrapped_set', test_value, 5)
+
 if __name__ == '__main__':
     unittest.main()
Index: docs/settings.txt
===================================================================
--- docs/settings.txt	(revision 5770)
+++ docs/settings.txt	(working copy)
@@ -248,6 +248,14 @@
 The default number of seconds to cache a page when the caching middleware or
 ``cache_page()`` decorator is used.
 
+CACHE_AVOID_STAMPEDE
+--------------------
+
+Default: ``False``
+
+If True, the selected ``CACHE_BACKEND`` is wrapped to avoid stampeding on 
+cache misses.  See the `cache docs`_.
+
 DATABASE_ENGINE
 ---------------
 
Index: docs/cache.txt
===================================================================
--- docs/cache.txt	(revision 5770)
+++ docs/cache.txt	(working copy)
@@ -203,6 +203,10 @@
         dumped when max_entries is reached. This makes culling *much* faster
         at the expense of more cache misses.
 
+    near_miss_seconds
+        The estimated number of seconds needed to reproduce a cache value.  
+        Only used when ``CACHE_AVOID_STAMPEDE`` is True.  Defaults to 5.
+
 In this example, ``timeout`` is set to ``60``::
 
     CACHE_BACKEND = "memcached://127.0.0.1:11211/?timeout=60"
@@ -342,6 +346,30 @@
 That's it. The cache has very few restrictions: You can cache any object that
 can be pickled safely, although keys must be strings.
 
+Avoiding cache stampedes
+========================
+
+Caching is intended to make your site more efficient by avoiding reproducing 
+expensive results.  Django's cache does this by storing the expensive result
+for a specific amount of time.  This generally works very well.  However, when 
+many requests for the same cache key occur in the time that it takes to produce 
+the cached result, the goal may not be achieved.  For example, if 20 requests 
+per second are made to a key which takes 5 seconds to produce, the work to 
+produce the value corresponding to the key may be done as much as 100 (5 x 20) 
+times.  
+
+This condition is called stampeding (or dog-piling), and can be avoided by 
+setting ``CACHE_AVOID_STAMPEDE`` to ``True``.  Doing so will slightly slow 
+down both ``get`` and ``set`` operations.  It will also the value stored in 
+the cache backend by including values needed to manage the stampede condition.
+
+Recommended for high traffic sites with uniform access to keys.
+
+Note that ``CACHE_AVOID_STAMPEDE`` will not affect normal key expiration.
+It will not avoid stampedes which occur just before the normal expiration of 
+the key because ``CACHE_AVOID_STAMPEDE`` will not extend the normal expiration 
+to avoid it.  
+
 Upstream caches
 ===============
 
