Symbol :@new_record is stored instead of the actual object #250

Closed
International opened this Issue Aug 8, 2012 · 13 comments

Comments

Projects
None yet
5 participants

I have a query like the following:

def self.all_available
  Rails.cache.fetch("all_available") { MyModel.where(:online => true).select (&:available?) }
end

The thing is, if I run the query without the cache line, it always returns a correct array of all the objects where the online is attribute, and the available? method returns true( I had 2 such objects in my test ). If I add the Rails.cache.fetch, the cache miss appears to retrieve the correct objects, but the cache hit returns a line like the following:

[ < First Object including attributes .... >, :@new_record ]

Has anyone encountered such a situation before? Where should I begin investigating?

jsqu99 commented Aug 14, 2012

This is sometimes happening for me as well. I'm new enough to Rails caching to not know if the problem is me or dalli, or memcached, or rails, etc.

Feel free to share anything you learn and I'll do the same.

Collaborator

mperham commented Aug 14, 2012

Looks like you might be caching a lazy proxy. Try adding .all to the end of the query to actually query the database and return the results.

Collaborator

mperham commented Aug 14, 2012

It also looks like you are confusing Enumerable#select and ActiveRecord's select. Use Enumerable#find_all instead.

@mperham I also tried explicitly calling to_a on the result, so that I would not be caching a relation. Same thing happens.

jsqu99 commented Aug 16, 2012

I'm fairly certain this was happening for me due to a nested ActiveRelation not having 'all' called on it. Thanks for your time.

It happened to me as well, we are not using memcached but filestore. Any hints?

jsqu99 commented Oct 22, 2012

I found a workaround for my issue. I'm convinced there's an issue w/ Ruby's Marshal dump. I'd file a bug report, but the 'stepst to reproduce' would require my whole database.

The workaround for me was to call Marshal.dump (or actually, Rails.cache.write) twice in a row.

The first one has the bogus data, the subsequent one works beautifully. HTH

@jsqu99 Thanks for the hint.

We tracked it down (at least we think so... steps to reproduce are pretty nasty) the last hour to a problem with Marshal dump and nested/loaded associations. Something like

foo = Foo.find(1)
foo.children.each do |c| something end
foo. another_association.do_something
Marshal.load(Marshal.dump([foo])) # ERROR => [Foo Instance, :@new_record, false]
Marshal.load(Marshal.dump([foo.reload])) # SUCCESS => [Foo Instance] 

So reloading the object fixed it for us. We now are switching all cache calls to just store the ids. This is OK for our project since not the retrieval from the DB is expensive but the calculation.

And as mentioned: we are not using Memcached, so this does not seem to be a dalli issue, i'm just commenting here for the next one who is having bad dreams because of this:-)

@jsqu99 It's problem with Marshal.load/Marshal.dump when the object beeing marshalled has a "initialized" association. I wrote a sample app and can reproduce the problem. Need to verify my findings and then hopefully can file a bug report. Wherever that will be:-)

Created an issue on rails/rails#8020

raykin commented Oct 30, 2012

@jsqu99 thanks for your tips. I've got a solution like following:

    # return value contains :new_record and false , so failed
    Rails.cache.fetch "no_change_roles" do
      init_roles.normalize.upstream.compress.extend.compress.extend_tile_role.compress
    end

wrote cache.write twice got expected value

     if cached = Rails.cache.read('no_change_roles')
        cached
      else
        roles = init_roles.normalize.upstream.compress.extend.compress.extend_tile_role.compress
        Rails.cache.write "no_change_roles", roles
        Rails.cache.write "no_change_roles", roles
        roles
      end

roles is a hash which values are array of AR object which includes associations.
I think wrote cache.write twice did the same thing as reload in @pascalbetz sample code. But in my sample, it's not easy to reload roles.

i think you could rewrite this like

Rails.cache.fetch('no_change_roles') do
roles = ....
Marshal.dump(roles)
roles
end

as dumping the objects once initializes everything as it should be...

you could also go through all the roles and call to_a on all the associations, should work as well. Or just store the IDs of the roles and then refetch from DB.

raykin commented Oct 31, 2012

great, it works and it's much more clean
btw, you did a typo, Marhsal should be Marshal.
to_a is not good for my example since values on roles are complecated, they can have three nested associations.
and save id is also not my expected cause roles was used in frontend pages, I don't want to query the db, that's why I need cache it.

@mperham mperham closed this Nov 13, 2012

fiedl added a commit to fiedl/wingolfsplattform that referenced this issue Aug 16, 2014

model caching: circumvent marshaling bug.
The problems we had, actually did not occur when converting an arel, but were due to a bug
described here:
petergoldstein/dalli#250

This circumvents this bug, but writing a cache takes a little longer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment