[Rails-core] Making ActiveSupport::Cache consistent

245 views
Skip to first unread message

Brian Durand

unread,
Apr 22, 2010, 12:35:33 AM4/22/10
to Ruby on Rails: Core
Lighthouse ticket: https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/4452

I have recently been working on some gems that utilize
ActiveSupport::Cache and ran into some issues with the different
implementations handling the same functionality differently. One of
the issues was that I couldn't rely on expiring entries with
the :expires_in option. MemCacheStore takes this option on a write,
while FileStore takes it on a read, and MemoryStore ignores it all
together so that the cache will just grow until you run out of memory.

I ended up doing a pretty large refactoring of ActiveSupport::Cache to
provide universal support for some options, fix some bugs, and update
the documentation. The patch is attached to this ticket.

Here are the highlights:

All Caches

* Add default options to initializer that will be sent to all
read, write, fetch, exist?, increment, and decrement
* Add support for the :expires_in option to fetch and write for
all caches. Cache entries are stored with the create timestamp and a
ttl so that expiration can be handled independently of the
implementation.
* Add support for a :namespace option. This can be used to set a
global prefix for cache entries.
* Deprecate expand_cache_key on ActiveSupport::Cache and move it
to ActionController::Caching and ActionDispatch::Http::Cache since the
logic in the method used some Rails specific environment variables and
was only used by ActionPack classes. Not very DRY but there didn't
seem to be a good shared spot and ActiveSupport really shouldn't be
Rails specific.
* Add support for :race_condition_ttl to fetch. This setting can
prevent race conditions on fetch calls where several processes try to
regenerate a recently expired entry at once.
* Add support for :compress option to fetch and write which will
compress any data over a configurable threshold.
* Nil values can now be stored in the cache and are distinct from
cache misses for fetch.
* Easier API to create new implementations. Just need to implement
the methods read_entry, write_entry, and delete_entry instead of
overwriting existing methods.
* Since all cache implementations support storing objects, update
the docs to state that ActiveCache::Cache::Store implementations
should store objects. Keys, however, must be strings since some
implementations require that.
* Increase test coverage.
* Document methods which are provided as convenience but which may
not be universally available.

MemoryStore

* MemoryStore can now safely be used as the cache for single
server sites.
* Make thread safe so that the default cache implementation used
by Rails is thread safe. The overhead is minimal and it is still the
fastest store available.
* Provide :size initialization option indicating the maximum size
of the cache in memory (defaults to 32Mb).
* Add prune logic that removes the least recently used cache
entries to keep the cache size from exceeding the max.
* Deprecated SynchronizedMemoryStore since it isn't needed
anymore.

FileStore

* Escape key values so they will work as file names on all file
systems, be consistent, and case sensitive
* Use a hash algorithm to segment the cache into sub directories
so that a large cache doesn't exceed file system limits.
* FileStore can be slow so implement the LocalCache strategy to
cache reads for the duration of a request.
* Add cleanup method to keep the disk from filling up with expired
entries.
* Fix increment and decrement to use file system locks so they are
consistent between processes.

MemCacheStore

* Support all keys. Previously keys with spaces in them would fail
* Deprecate CompressedMemCacheStore since it isn't needed anymore

--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group.
To post to this group, send email to rubyonra...@googlegroups.com.
To unsubscribe from this group, send email to rubyonrails-co...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/rubyonrails-core?hl=en.

Ken Collins

unread,
Apr 23, 2010, 8:27:28 AM4/23/10
to rubyonra...@googlegroups.com

This is a great topic and I just wanted to add that possibly a RedisStore might be good in core too. I was thinking of finding some time next week and doing a ActiveSupport::Cache::RedisStore that passes some tests and conforms to the LocalStore strategy too. I have seen this:


But it does not look like it does not do exactly all the things the MemCacheStore does. Question, why is compressed memcached store deprecated now? 


- Ken

Chris Hanks

unread,
Apr 23, 2010, 11:34:06 AM4/23/10
to Ruby on Rails: Core
Moneta already supports Redis (and a bunch of other stores, too).
ActiveSupport's cache could be based on it, rather than duplicating a
lot of work here.

http://github.com/wycats/moneta
> > For more options, visit this group athttp://groups.google.com/group/rubyonrails-core?hl=en.
>
> --
> You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group.
> To post to this group, send email to rubyonra...@googlegroups.com.
> To unsubscribe from this group, send email to rubyonrails-co...@googlegroups.com.
> For more options, visit this group athttp://groups.google.com/group/rubyonrails-core?hl=en.

Michael Koziarski

unread,
Apr 24, 2010, 6:39:09 AM4/24/10
to rubyonra...@googlegroups.com
> I ended up doing a pretty large refactoring of ActiveSupport::Cache to
> provide universal support for some options, fix some bugs, and update
> the documentation. The patch is attached to this ticket.

First, nice work. Great to see someone really roll up their sleeves.

> Here are the highlights:
>
> All Caches
>
>    * Add default options to initializer that will be sent to all
> read, write, fetch, exist?, increment, and decrement

Sounds good

>    * Add support for the :expires_in option to fetch and write for
> all caches. Cache entries are stored with the create timestamp and a
> ttl so that expiration can be handled independently of the
> implementation.

This I'm not so sold on, expires in is a memcached implementation
specific feature and adding it to all the other cache stores simply
seems to add overhead for very little gain. No one is seriously going
to be using MemoryStore or FileStore in production and wanting to use
:expires_in. What was it you needed this for?

>    * Add support for a :namespace option. This can be used to set a
> global prefix for cache entries.

Sweet.

>    * Deprecate expand_cache_key on ActiveSupport::Cache and move it
> to ActionController::Caching and ActionDispatch::Http::Cache since the
> logic in the method used some Rails specific environment variables and
> was only used by ActionPack classes. Not very DRY but there didn't
> seem to be a good shared spot and ActiveSupport really shouldn't be
> Rails specific.
>    * Add support for :race_condition_ttl to fetch. This setting can
> prevent race conditions on fetch calls where several processes try to
> regenerate a recently expired entry at once.

Can you expand on this?

>    * Add support for :compress option to fetch and write which will
> compress any data over a configurable threshold.

How can the read implementation tell whether what it's reading was
:compressed or just happens to look like it might have been?


Everything else looks cool.
--
Cheers

Koz

Brian Durand

unread,
Apr 24, 2010, 2:48:11 PM4/24/10
to Ruby on Rails: Core
> This I'm not so sold on, expires in is a memcached implementation
> specific feature and adding it to all the other cache stores simply
> seems to add overhead for very little gain. No one is seriously going
> to be using MemoryStore or FileStore in production and wanting to use
> :expires_in. What was it you needed this for?

Mostly what I wanted was for the caches to be consistent. I think
having caches support a time to live on write is a pretty fundamental
feature of a cache because otherwise you are stuck needing to know
what every cache entry is so you can expire it when necessary. For a
small site on a single server, I think MemoryStore or FileStore could
be a perfectly viable option.

> > * Add support for :race_condition_ttl to fetch. This setting can
> > prevent race conditions on fetch calls where several processes try to
> > regenerate a recently expired entry at once.
>
> Can you expand on this?

The logic is that if you specify a race condition ttl, and a process
finds a recently expired entry in the cache during a fetch, it will
first put it back in the cache with an expiration time shortly in the
future. It will then regenerate the entry and store it in the cache.
The advantage is that it can reduce race conditions when a cache entry
expires and you can have potentially dozens of processes trying to
regenerate the entry at the same time. If the process that is
regenerating the entry fails, another process will try again shortly
when the old entry expires again.

Marc Byrd

unread,
Apr 24, 2010, 3:57:21 PM4/24/10
to rubyonra...@googlegroups.com
Consistency is good - so is backward compatibility - our CloudCache implementation relies upon ActiveSupport::Cache, so prefer that nothing be deprecated, and if so, with a long lead time.

Thanks,


m

getCloudCache.com

Chad Woolley

unread,
Apr 24, 2010, 6:29:41 PM4/24/10
to rubyonra...@googlegroups.com
On Saturday, April 24, 2010, Brian Durand <br...@embellishedvisions.com> wrote:
>> This I'm not so sold on,  expires in is a memcached implementation
>> specific feature and adding it to all the other cache stores simply
>> seems to add overhead for very little gain.  No one is seriously going
>> to be using MemoryStore or FileStore in production and wanting to use
>> :expires_in.  What was it you needed this for?
>
> Mostly what I wanted was for the caches to be consistent. I think
> having caches support a time to live on write is a pretty fundamental
> feature of a cache because otherwise you are stuck needing to know
> what every cache entry is so you can expire it when necessary. For a
> small site on a single server, I think MemoryStore or FileStore could
> be a perfectly viable option.

Yes. Native file cache let you have caching for a simple site, and
eliminate the need to even have a web server.

+1 on the race ttl too

-- Chad

Brian Durand

unread,
Apr 26, 2010, 11:01:44 AM4/26/10
to Ruby on Rails: Core
I updated the patch to remove the deprecation of
ActiveSupport::Cache.expand_cache_key. This method still uses some
Rails specific variables which really isn't ideal. Otherwise the only
deprecations are SynchronizedMemoryStore, CompressedMemCacheStore,
and :expires_in on FileStore#read. The two classes are deprecated
because they don't provide anything beyond what is provided in
MemoryStore and MemCacheStore. FileStore#read deprecates :expires_in
because it can now be set on write.

On Apr 24, 2:57 pm, Marc Byrd <dr.marc.b...@gmail.com> wrote:
> Consistency is good - so is backward compatibility - our CloudCache
> implementation relies upon ActiveSupport::Cache, so prefer that nothing be
> deprecated, and if so, with a long lead time.
>
> Thanks,
>
> m
>
> getCloudCache.com
>

Reply all
Reply to author
Forward
0 new messages