Git Product home page Git Product logo

cache-money's Introduction

What is Cache Money

Cache Money is a write-through and read-through caching library for ActiveRecord.

Read-Through: Queries like User.find(:all, :conditions => ...) will first look in Memcached and then look in the database for the results of that query. If there is a cache miss, it will populate the cache.

Write-Through: As objects are created, updated, and deleted, all of the caches are automatically kept up-to-date and coherent.

Howto

What kinds of queries are supported?

Many styles of ActiveRecord usage are supported:

  • User.find
  • User.find_by_id
  • User.find(:conditions => {:id => ...})
  • User.find(:conditions => ['id = ?', ...])
  • User.find(:conditions => 'id = ...')
  • User.find(:conditions => 'users.id = ...')

As you can see, the find_by_, find_all_by, hash, array, and string forms are all supported.

Queries with joins/includes are unsupported at this time. In general, any query involving just equality (=) and conjunction (AND) is supported by Cache Money. Disjunction (OR) and inequality (!=, <=, etc.) are not typically materialized in a hash table style index and are unsupported at this time.

Queries with limits and offsets are supported. In general, however, if you are running queries with limits and offsets you are dealing with large datasets. It's more performant to place a limit on the size of the Cache Money index like so:

DirectMessage.index :user_id, :limit => 1000

In this example, only queries whose limit and offset are less than 1000 will use the cache.

Multiple indices are supported

class User < ActiveRecord::Base
  index :screen_name
  index :email
end

with_scope support

with_scope and the like (named_scope, has_many, belongs_to, etc.) are fully supported. For example, user.devices.find(1) will first look in the cache if there is an index like this:

class Device < ActiveRecord::Base
 index [:user_id, :id]
end

Ordered indices

class Message < ActiveRecord::Base
  index :sender_id, :order => :desc
end

The order declaration will ensure that the index is kept in the correctly sorted order. Only queries with order clauses compatible with the ordering in the index will use the cache:

  • Message.find(:all, :conditions => {:sender_id => ...}, :order => 'id DESC').

Order clauses can be specified in many formats ("messages.id DESC", "messages.id DESC", and so forth), but ordering MUST be on the primary key column.

class Message < ActiveRecord::Base
  index :sender_id, :order => :asc
end

will support queries like:

  • Message.find(:all, :conditions => {:sender_id => ...}, :order => 'id ASC')
  • Message.find(:all, :conditions => {:sender_id => ...})

Note that ascending order is implicit in index declarations (i.e., not specifying an order is the same as ascending). This is also true of queries (order is not nondeterministic as in MySQL).

Window indices

class Message < ActiveRecord::Base
  index :sender_id, :limit => 500, :buffer => 100
end

With a limit attribute, indices will only store limit + buffer in the cache. As new objects are created the index will be truncated, and as objects are destroyed, the cache will be refreshed if it has fewer than the limit of items. The buffer is how many "extra" items to keep around in case of deletes.

It is particularly in conjunction with window indices that the :order attribute is useful.

Calculations

Message.count(:all, :conditions => {:sender_id => ...}) will use the cache rather than the database. This happens for "free" -- no additional declarations are necessary.

Version Numbers

class User < ActiveRecord::Base
  version 7
  index ...
end

You can increment the version number as you migrate your schema. Be careful how you deploy changes like this as during deployment independent mongrels may be using different versions of your code. Indices can be corrupted if you do not plan accordingly.

Transactions

Because of the parallel requests writing to the same indices, race conditions are possible. We have created a pessimistic "transactional" memcache client to handle the locking issues.

The memcache client library has been enhanced to simulate transactions.

$cache.transaction do
  $cache.set(key1, value1)
  $cache.set(key2, value2)
end

The writes to the cache are buffered until the transaction is committed. Reads within the transaction read from the buffer. The writes are performed as if atomically, by acquiring locks, performing writes, and finally releasing locks. Special attention has been paid to ensure that deadlocks cannot occur and that the critical region (the duration of lock ownership) is as small as possible.

Writes are not truly atomic as reads do not pay attention to locks. Therefore, it is possible to peak inside a partially committed transaction. This is a performance compromise, since acquiring a lock for a read was deemed too expensive. Again, the critical region is as small as possible, reducing the frequency of such "peeks".

Rollbacks

$cache.transaction do
  $cache.set(k, v)
  raise
end

Because transactions buffer writes, an exception in a transaction ensures that the writes are cleanly rolled-back (i.e., never committed to memcache). Database transactions are wrapped in memcache transactions, ensuring a database rollback also rolls back cache transactions.

Nested transactions are fully supported, with partial rollback and (apparent) partial commitment (this is simulated with nested buffers).

Mocks

For your unit tests, it is faster to use a Memcached mock than the real deal. In your test environment, initialize the repository with an instance of Cash::Mock.

Locks

In most cases locks are unnecessary; the transactional Memcached client will take care locks for you automatically and guarantees that no deadlocks can occur. But for very complex distributed transactions, shared locks are necessary.

$lock.synchronize('lock_name') do
  $memcache.set("key", "value")
end

Local Cache

Sometimes your code will request the same cache key twice in one request. You can avoid a round trip to the Memcached server by using a local, per-request cache. Add this to your initializer:

$memcache = MemcachedWrapper.new(config[:servers].gsub(' ', '').split(','), config)
$local = Cash::Local.new($memcache)
$lock  = Cash::Lock.new($memcache)
$cache = Cash::Transactional.new($local, $lock)

Installation

Step 1: Get the GEM

% sudo gem install ngmoco-cache-money

Add the gem you your Gemfile:
gem 'ngmoco-cache-money', :lib => 'cache_money'

Step 2: Configure cache client

In your environment, create a cache client instance configured for your cache servers.

$memcached = Memcached.new( ...servers..., ...options...)

Currently supported cache clients are: memcached, memcache-client

Step 3: Configure Caching

Add the following to an initializer:

Cash.configure :repository => $memcached, :adapter => :memcached

Supported adapters are :memcache_client, :memcached. :memcached is assumed and is only compatible with Memcached clients. Local or transactional semantics may be disabled by setting :local => false or :transactional => false.

Caching can be disabled on a per-environment basis in the environment's initializer:

Cash.enabled = false

Step 4: Add indices to your ActiveRecord models

Queries like User.find(1) will use the cache automatically. For more complex queries you must add indices on the attributes that you will query on. For example, a query like User.find(:all, :conditions => {:name => 'bob'}) will require an index like:

class User < ActiveRecord::Base
  index :name
end

For queries on multiple attributes, combination indexes are necessary. For example, User.find(:all, :conditions => {:name => 'bob', :age => 26})

class User < ActiveRecord::Base
  index [:name, :age]
end

Optional: Selectively cache specific models

There may be times where you only want to cache some of your models instead of everything.

In that case, you can omit the following from your config/initializers/cache_money.rb

class ActiveRecord::Base
  is_cached
end

After that is removed, you can simple put this at the top of your models you wish to cache:

is_cached

Just make sure that you put that line before any of your index directives. Note that all subclasses of a cached model are also cached.

Acknowledgments

Thanks to

  • Twitter for commissioning the development of this library and supporting the effort to open-source it.
  • Sam Luckenbill for pairing with Nick on most of the hard stuff.
  • Matthew and Chris for pairing a few days, offering useful feedback on the readability of the code, and the initial implementation of the Memcached mock.
  • Evan Weaver for helping to reason-through software and testing strategies to deal with replication lag, and the initial implementation of the Memcached lock.

cache-money's People

Contributors

ashleym1972 avatar betamatt avatar codegoalie avatar dougal avatar joeyaghion avatar kbrock avatar mguymon avatar obrie avatar patricktulskie avatar seamusabshere avatar sferik avatar sideshowbandana avatar smtlaissezfaire avatar spariev avatar sreeix avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cache-money's Issues

TTL - not having an effect - memcache.yml

I am setting a ttl in my memcache.yml file but I noticed that is has no effect on the actual expire time of the model. The ttl is always the value set in the index.rb (DEFAULT_OPTIONS)

I can set the :ttl as an optional value in the model directly tied to the index; ex index :hrq_id, :ttl => 60.

Does anyone know how to globally add the ttl back to the .yml file? Or we should change the documentation to not include the ttl in the memcache.yml file.

thanks

Known bug with find_from_ids_with_cache...?

Try this:
User.find([100,101,102])
and note in the debug log that it is UNCACHEABLE. Now, fetch the first two, individually:
User.find(100); User.find(101)
and then retry the original array with three values. This time it does not say uncacheable. I've been looking at the code but cannot figure out where the bug lies.

On a related note, perhaps...? This works, first time, and is cacheable, even though it boils down to the same SQL query, I know it uses a different code path:
User.find(:all, :conditions=>{:id=>[300,301,302]})
while User.find([300,301,302]) does not, of course, as noted above.

Uncacheable for :all

When using simple JobPosting.find(1), the caching always works, but when doing this, it becomes uncacheable.

JobPosting.find(:all)
---- UNCACHEABLE job_postings - {} - {} - {} - {}
JobPosting Load (1.6ms) SELECT * FROM job_postings

I see the doc, :all is supported, any obvious thing I have missed?

JobPosting.find(1,2,3)
---- UNCACHEABLE job_postings - {} - {} - {:conditions=>"job_postings.id IN (2,3)"} - {}
JobPosting Load (0.5ms) SELECT * FROM job_postings WHERE (job_postings.id IN (2,3))

undefined method `default_ttl'

undefined method `default_ttl' for <MemCache: 1 servers, ns: nil, ro: false>:MemCache

I am using ngmoco-cache-money 0.2.21, with Memcached 1.0, Rails 2.3.5, Passenger 2.2.15. The error occurs on every page where a query is needed. The ttl is specified in the memcached.yml.

I tried 0.2.10 and everything worked fine.

Full trace:
/opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/local.rb:31:in send' /opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/local.rb:31:inmethod_missing'
/opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/local.rb:17:in autoload_missing_constants' /opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/local.rb:30:inmethod_missing'
/opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/buffered.rb:102:in send' /opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/buffered.rb:102:inmethod_missing'
/opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/transactional.rb:32:in send' /opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/transactional.rb:32:inmethod_missing'
/opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/config.rb:58:in ttl' /opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/index.rb:10:ininitialize'
/opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/config.rb:66:in new' /opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/config.rb:66:inindices'
/opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/config.rb:23:in __send__' /opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/config.rb:23:inindices'
/opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/write_through.rb:57:in update_caches' /opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/write_through.rb:45:insend'
/opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/write_through.rb:45:in unfold' /opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/write_through.rb:26:inupdate_caches'
/opt/ruby/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/callbacks.rb:178:in send' /opt/ruby/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/callbacks.rb:178:inevaluate_method'
/opt/ruby/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/callbacks.rb:166:in call' /opt/ruby/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/callbacks.rb:93:inrun'
/opt/ruby/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/callbacks.rb:92:in each' /opt/ruby/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/callbacks.rb:92:insend'
/opt/ruby/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/callbacks.rb:92:in run' /opt/ruby/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/callbacks.rb:276:inrun_callbacks'
/opt/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/callbacks.rb:344:in callback' /opt/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/callbacks.rb:283:inupdate'
/opt/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2874:in create_or_update_without_callbacks' /opt/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/callbacks.rb:250:increate_or_update'
/opt/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2538:in save_without_validation' /opt/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/validations.rb:1078:insave_without_dirty'
/opt/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/dirty.rb:79:in save_without_transactions' /opt/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/transactions.rb:229:insend'
/opt/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/transactions.rb:229:in with_transaction_returning_status' /opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cache_money.rb:63:intransaction'
/opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cash/transactional.rb:13:in transaction' /opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cache_money.rb:63:intransaction'
/opt/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/connection_adapters/abstract/database_statements.rb:136:in transaction' /opt/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/transactions.rb:182:intransaction_without_cache_transaction'
/opt/ruby/lib/ruby/gems/1.8/gems/ngmoco-cache-money-0.2.21/lib/cache_money.rb:62:in transaction' /opt/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/transactions.rb:228:inwith_transaction_returning_status'
/opt/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/transactions.rb:196:in save' /opt/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/transactions.rb:208:inrollback_active_record_state!'
/opt/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/transactions.rb:196:in save' /opt/ruby/lib/ruby/gems/1.8/gems/authlogic-2.1.6/lib/authlogic/acts_as_authentic/session_maintenance.rb:73:insave_without_session_maintenance'
/opt/ruby/lib/ruby/gems/1.8/gems/authlogic-2.1.6/lib/authlogic/session/callbacks.rb:95:in save_record' /opt/ruby/lib/ruby/gems/1.8/gems/authlogic-2.1.6/lib/authlogic/session/priority_record.rb:30:insave_record'
/opt/ruby/lib/ruby/gems/1.8/gems/authlogic-2.1.6/lib/authlogic/session/existence.rb:73:in save' /home/jeff/projects/dating/app/controllers/user_sessions_controller.rb:14:increate'

find_all_by_ids returns stale records

Hello,

I have a Person model with a name attribute, and that model is cached normally by its id.

When I type in the following I can consistently produce stale results with cache_money:
$ script/console
>> p = Person.find_all_by_id([1, 2]).first
>> p.name
=> "Al Bundy"
>> p.name = "Alice Bundy"
>> p.save!
>> Person.find_all_by_id([1]).first.name
=> "Alice Bundy"
>> Person.find_all_by_id([1, 2]).first.name
=> "Al Bundy"

If you run memcached with verbose on, you will see entries like:
<28 get dev_cache:Person:1/id/1/2
<28 add dev_cache:Person:1/id/1/2 0 86400 1062
<28 get dev_cache:Person:1/id/1
<28 add dev_cache:Person:1/id/1 0 86400 729
<28 add dev_cache:lock/Person:1/id/1 0 30 6
<28 set dev_cache:Person:1/id/1 0 86400 948
<28 delete dev_cache:lock/Person:1/id/1 0
<28 get dev_cache:Person:1/id/1/2
>28 sending key dev_cache:Person:1/id/1/2

So with find_by_ids([1,2]) the resulting 2 objects are stored together using cache_key "Person:1/id/1/2". And when the name of the Person with ID 1 is updated, only the cache_key with the single object "Person:1/id/1" is updated.

This looks like a major flaw in cache_money. Am I missing something, or do other people experience the same issue?

Kind regards,
orslumen.

undefined method `<=>' error

For most parts of my rails application cache-money works really nice.

But how do I solve this kind of error.
Without cache-money I do not have this kind of errors:

NoMethodError (undefined method <=>' for #UserActivity:0x1072c9b58): /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cash/index.rb:124:inadd_object_to_cache' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cash/index.rb:123:in sort' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cash/index.rb:123:inadd_object_to_cache' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cash/index.rb:161:in update_index_with_minimal_network_operations' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cash/index.rb:31:inupdate' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cash/write_through.rb:60:in update_caches' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cash/write_through.rb:60:ineach' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cash/write_through.rb:60:in update_caches' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cash/write_through.rb:48:insend' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cash/write_through.rb:48:in unfold' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cash/write_through.rb:26:inupdate_caches' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cache_money.rb:52:in transaction_without_trace_ActiveRecord_self_name_transaction' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cash/transactional.rb:13:intransaction' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cache_money.rb:52:in transaction_without_trace_ActiveRecord_self_name_transaction' /Library/Ruby/Gems/1.8/gems/newrelic_rpm-2.12.3/lib/new_relic/agent/method_tracer.rb:319:intransaction' /Library/Ruby/Gems/1.8/gems/newrelic_rpm-2.12.3/lib/new_relic/agent/method_tracer.rb:141:in trace_execution_scoped' /Library/Ruby/Gems/1.8/gems/newrelic_rpm-2.12.3/lib/new_relic/agent/method_tracer.rb:314:intransaction' app/models/user_activity.rb:126:in generate_html' app/models/user_activity.rb:108:increate_html' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cache_money.rb:52:in transaction_without_trace_ActiveRecord_self_name_transaction' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cash/transactional.rb:13:intransaction' /Library/Ruby/Gems/1.8/gems/af-cache-money-0.2.10/lib/cache_money.rb:52:in transaction_without_trace_ActiveRecord_self_name_transaction' /Library/Ruby/Gems/1.8/gems/newrelic_rpm-2.12.3/lib/new_relic/agent/method_tracer.rb:319:intransaction' /Library/Ruby/Gems/1.8/gems/newrelic_rpm-2.12.3/lib/new_relic/agent/method_tracer.rb:141:in trace_execution_scoped' /Library/Ruby/Gems/1.8/gems/newrelic_rpm-2.12.3/lib/new_relic/agent/method_tracer.rb:314:intransaction' app/models/activity.rb:151:in `log'

"Indices can be corrupted if you do not plan accordingly."

The "version" section of the documentation has an ominous "Indices can be corrupted if you do not plan accordingly." warning. How exactly should one plan accordingly? Couldn't the migration process be solved with some sort of locking, or simply with an entirely different versioned key namespace?

ruby 1.9 issue require path

ngmoco-cache-money.gemspec
require 'lib/cash/version'

ruby 1.9 needs abosulte path

should be
require File.join(File.dirname(FILE), 'lib/cash/version')

delete, delete!, and delete_all will make the cache stale

What do you guys think is the best approach for handling this? How about one of these:

  • do nothing, and tell developers that those operations are dangerous
  • raise if someone were to try to call these methods
  • alias delete to be destroy (but that goes against the docs that delete will just be a straight sql call)
  • some hybrid where a developer can force the delete
  • implement all of the above and let the developer pick :)

cache-money:false in the config should only turn cache money off

We use memcache.yml for memcache settings(page cacheing etc), and sometimes we want to turn only cache-money off or on without disabling $memcache completely.

It would be nice to have just turn off the cache-money part and still initialize the $memcache to be use by other parts of the application

I can provide a pull request if you think it makes sense.

Uncacheable entries for very basic finds

I'm evaluating cache-money for possible uses and it looks completely awesome, but I'm seeing an issue I don't understand. Essentially, records I'd expect to be indexed aren't being indexed. Here's the code where the query comes from:

User.find(session['user'])

and in the server logs I see

UNCACHEABLE users - {:conditions=>{:id=>100001721}} - {} - {} - {}

I was under the impression I would not need to specify an index for primary keys, but I've also tried this with the same result: no caching. What must I be doing incorrectly?

Any luck with Rails 3.2?

Has anybody had luck with Cache Money & Rails 3.2? Probably a long shot, but figured I'd ask.

Any help would be appreciated!

Uncommitted data can be written to cache when mixing cached and non-cached models

The model that begins a transaction determines whether cache-safe transaction semantics are used. If safe and unsafe transactions are nested, data can end up committed to cache even when the database transaction rolls back.

It's important to continue supporting use cases where only some models are cached but I feel a refactor is needed to move transaction wrappers to the AR::Base level. If model caching is used at all then all transactions should be wrapped with cache-safe semantics, not just models marked is_cached. This change implies that a cache repository needs to be configured at the AR::Base level as well, and not just on individual models, although it can be overridden on a per-model basis.

I'm opening this issue for discussion prior to putting together a patch.

CacheMoney::Index#update( object ) hits database for every defined index

The queries triggered per index do a where over the indexed attributes, causing multiple slow queries in the database if you're trying to have indices for cache money without indexing the same attributes in the database.

Intuitively, a design that fetches the object once for the index updates and updates each index with the values from that fetched object would be a more straight forward procedure.

Alternatively, invalidate all indices for a class on a write, and fetch the data for an index once that particular index is read.

Cache keys doesnt match

I recently installed this branch and when i created a new dummy record like Book cache key setted like "Book:1/3" when i updated it a new cache key setted like "Book:1/id/3" and they doesnt match on show and update screens.plugin always call first created cache key "Book:1/3" and always same values comes up.

here is the record creation debug logs:

Started GET "/books/3" for 127.0.0.1 at 2011-02-24 12:34:00 +0200
Processing by BooksController#show as HTML
Parameters: {"id"=>"3"}
CACHE GET "Book:1/3"
Memcached get: "Book:1/3"
Memcached miss: "Book:1/3"
Book Load (0.3ms) SELECT books.* FROM books WHERE books.id = 3 LIMIT 1
CACHE ADD Book:1/3 = #<Book id: 3, title: "new book", author: "new book", created_at: "2011-02-24 10:34:00", updated_at: "2011-02-24 10:34:00"> (604800, )
Memcached add: "Book:1/3"
Memcached hit: "Book:1/3"
Rendered books/show.html.erb within layouts/application (2.7ms)
Completed 200 OK in 47ms (Views: 7.2ms | ActiveRecord: 0.3ms)

When i updated the record the following logs are coming:

Started POST "/books/3" for 127.0.0.1 at 2011-02-24 12:35:15 +0200
Processing by BooksController#update as HTML
Parameters: {"utf8"=>"โœ“", "authenticity_token"=>"Qq27y38shDTjz/CitfiZYYKePvR0HGTO1DK7rzh8fuA=", "book"=>{"title"=>"updated book", "author"=>"updated book"}, "commit"=>"Update Book", "id"=>"3"}
CACHE GET "Book:1/3"
Memcached get: "Book:1/3"
Memcached hit: "Book:1/3"
SQL (0.1ms) BEGIN
AREL (0.3ms) UPDATE books SET title = 'updated book', author = 'updated book', updated_at = '2011-02-24 10:35:15' WHERE books.id = 3
add_object_to_primary_key_cache: [["id", 3]] #<Book id: 3, title: "updated book", author: "updated book", created_at: "2011-02-24 10:34:00", updated_at: "2011-02-24 10:35:15">
SERIALIZE OBJECT: #<Book id: 3, title: "updated book", author: "updated book", created_at: "2011-02-24 10:34:00", updated_at: "2011-02-24 10:35:15">
CACHE SET Book:1/id/3 = #<Book id: 3, title: "updated book", author: "updated book", created_at: "2011-02-24 10:34:00", updated_at: "2011-02-24 10:35:15">
Memcached add: "lock/Book:1/id/3"
Memcached hit: "lock/Book:1/id/3"
Memcached set: "Book:1/id/3"
Memcached hit: "Book:1/id/3"
Memcached delete: "lock/Book:1/id/3"
Memcached hit: "lock/Book:1/id/3"
SQL (69.4ms) COMMIT
Redirected to http://0.0.0.0:3000/books/3
Completed 302 Found in 89ms

Started GET "/books/3" for 127.0.0.1 at 2011-02-24 12:35:15 +0200
Processing by BooksController#show as HTML
Parameters: {"id"=>"3"}
CACHE GET "Book:1/3"
Memcached get: "Book:1/3"
Memcached hit: "Book:1/3"
Rendered books/show.html.erb within layouts/application (33.6ms)
Completed 200 OK in 46ms (Views: 38.6ms | ActiveRecord: 0.0ms)

as you can see last call made with "Book:1/3" key and old value returned.

key too long error

So far cache-money is working great, but it failed for queries with long parameters ('key too long' error). As i know memcached keeps a MAX_KEY limit to 250. Trying to hash the keys worked first but later knew is a disaster(it gave some 30+ failures while running rake).Is there any work around or fix to overcome this issue please.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.