Brian Pane wrote:
> Graham Leggett wrote:
> 
>>> For the expiration case, there's a much easier solution than 
>>> shadowing the
>>> incomplete response.  Add a new state for cache entries: 
>>> "being_updated."
>>> When you get a request for a cached object that's past its expiration 
>>> date,
>>> set the cache entry's state to "being_updated" and start retrieving 
>>> the new
>>> content.  Meanwhile, as other threads handle requests for the same 
>>> object,
>>> they check the state of the cache entry and, because it's currently 
>>> being
>>> updated, they deliver the old copy from the cache rather than 
>>> dispatching
>>> the request to the backend system that's already working on a different
>>> instance of the same request.  As long as the thread that's getting 
>>> the new
>>> content can replace the old content with the new content atomically, 
>>> there's
>>> no reason to make any other threads wait for the new content.
>>
>>
>>
>> Hmmm... ok, not a bad idea, however I see a few hassles though.
>>
>> What if a user force-reloads the page. In theory the cached copy 
>> should be expired immediately - but here we don't, because shadow 
>> threads need to access the cached content in the meantime.
> 
> 
> 
> I actually think we *shouldn't* expire the cached copy in this
> case.  I want the server, rather than the client, to be able to
> decide whether to expire a cache entry.  If the client gets to
> dictate the expiration semantics, then we're vulnerable to a
> DoS attack (all a malicious client would need to do is start
> submitting lots of requests that invalidate cache entries).
> 
this is not RFC compliant, so some people will prefer RFC compliance.
to give the more sane (imho ;-) use case, we have a directive which
will ignore the client cache request.

>> Also - there will be a load spike until the first page is cached (in 
>> the case where no previous cache existed).
> 

> 
> 
> Yep.  I think the ideal solution would be to combine this
> technique with shadowing support.  Then mod_cache could do
> one of four things depending on the cache state:
>  * cache hit, normal case: deliver the object from cache
>  * cache hit, while another thread is updating: deliver the old object
>  * cache miss, normal case: go retrieve the content
>  * cache miss, while another thread is updating: shadow the response in 
> progress
> 
>> What happens if an attempt to update an expired cached page hangs? The 
>> proxy could find itself serving stale content for a long time while a 
>> timeout occurs.
> 
> 
> 
> One solution is to add a timestamp for the "being updated" flag.
> If another thread sees that the flag is set but the timestamp is
> more than 'n' seconds old, that thread sets the timestamp to the
> current time and tries to retrieve the object itself.  (This
> logic just needs to be atomic so that we don't end up with multiple
> threads all deciding to retrieve the content at once.)

I was actually thinking that we could use another priority queue
and a seperate thread(s) to call the back end, limiting the amount of 
traffic which can ever get to the backend at all.
> 
>>>  * It's going to take a while to make the shadowing work
>>>    (due to all the race conditions that have to be addressed).
>>
>>
>>
>> I don't see any significant race conditions though.
>>
>> All that needs to happen is that all cached responses are 
>> readable-from-the-cache the moment they are created, rather than the 
>> moment the download is complete. A flag against the cache entry marks 
>> it as "still busy". If this flag is set, shadowed threads know to keep 
>> waiting for more data appearing in the cache. If the flag is not set, 
>> the shadow thread is a normal CACHE_OUT case, and finishes up the 
>> request knowing it to be complete.
> 
> 
> 
> The race conditions arise in cases where, for example, we find out
> halfway through a streamed request that it isn't cacheable after all
> because it's just exceeded the max cache size.  Therefore we won't
> be caching this response permanently, but we do need to keep the
> buffered buckets around until all threads that are shadowing the
> response have finished using those buckets.  And then, after the
> last of those threads finishes using the data, we need to delete
> the saved brigade.  But if the response is *really* large, we can't
> keep the whole thing in memory until the last thread finishes
> delivering it.  Instead, we'd have to incrementally delete buckets
> from the start of the brigade as all the threads finish dealing
> with each one.  (And keep new threads from shadowing the response
> from the moment when we first delete a bucket.)  This can all be
> done with reference counts and careful synchronization, but it's
> going to be challenging to debug.
> 
> Brian
> 
> 


Reply via email to