Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ActiveRecord::Base.uncached() - still cached... association cached #2878

Closed
eclectic923 opened this issue Sep 5, 2011 · 5 comments
Closed

Comments

@eclectic923
Copy link

I'm working with: Rails 3.0.0

I'm working in a multi-process environment. Caching of SQL data is a bad
thing for me (mostly). I use ActiveRecord::Base.uncached() as a wrapper
around my SQL queries.

However, I discovered that some queries were failing. When I looked,
the problem was very difficult to find.

There's an association cache. Who knew, it's very well hidden. Can't even
find reference in the documentation. Had to find it by listing methods
and looking for 'cache'.

I have models like

    class Foo < ActiveRecord::Base
            belongs_to( :bar )
    end

    class Bar < ActiveRecord::Base
            has_many( :foos )
    end

In my code I do:

    ActiveRecord::Base.uncached() do
            ActiveRecord::Base.transaction( trans_args ) do
                    ...
                    tmp = Foo.where( :id => 1 )
                    tmp = tmp[0] # Array -> single element
                    tmp_bar = tmp.bar
                    # Problem can be fixed with
                    # tmp.clear_association_cache()
                    ...
            end
    end
    ...
    sleep(1)        # Stuff happens to bar via other processes
    ...
    ActiveRecord::Base.uncached() do
            ActiveRecord::Base.transaction( trans_args ) do
                    ...
                    # Check of Bar data will show data base is actually updated.
                    # This will get the correct data.
                    # bar_chk = Bar.where( <get value matching tmp> )...
                    ...
                    tmp = Foo.where( :id => 1 )
                    tmp = tmp[0] # Array -> single element
                    tmp_bar = tmp.bar # gotcha
                    ...
            end
    end

If later on in my code I try to do tmp.bar again (gotcha comment line),
and generate the same query, the association cache will remember my prior
use and give me the old data. I know it's the old data because of the
sleep(). The update_at time stamp will be old.

Going a step further, I can see that the data for my Bar row has been
correctly updated, even before I try the association with foo/tmp to
retrieve the bar value for the second time. (See example code in second
transaction block.

So, my gotcha line is really doing the wrong thing.

Going a step further, I can prove that the association caching is the
problem. If I add clearing of the association cache as indicated, this
will fix the 'gotcha' so that the proper data is returned.

I submit that if one runs code in 'uncached()' that ALL caching needs to
be turned off. Getting old data is really a bad thing.

If you believe this is a user problem, then I'd submit a warning needs to
be added to the documentation so that folks know uncached doesn't turn
off all caching. This documentation should also explain why the name of
the function is uncached when the effect is 'partially uncached'.

PS. It would be really nice if one could select a default caching mode
(cached/uncached) and then use the uncached/cached methods to deviate.
... instead of there being one choice only, and those of us writing
applications that need mostly uncached having to code around the default
caching everywhere.

PPS. Rails sure beats the heck out of what I had to do before. Thanks :-)

@swils
Copy link

swils commented Nov 12, 2011

agree, I seem to be having the same problem. It's driving me nuts. I also don't want to completely wipe out the association cache for one specific query, just bypass it seems good enough?

@isaacsanders
Copy link
Contributor

Is this still an issue?

@steveklabnik
Copy link
Member

This seems absolutely crazy, but @tenderlove or @jonleighton would be the men for the job!

@pixeltrix
Copy link
Contributor

The uncached is working correctly with associations, in that you've disabled caching of the SQL queries. However the association proxy implements an object cache of the model instances. There's no way to turn off this caching but if you call association_name(true) this does a forced reload of the association. The other way is to use the association scope - e.g. parent.association.all to fetch the records.

@travishaynes
Copy link

This is an old issue, but since it's still open I decided to post the solution I used for it in case somebody else needs it. I added a touch: true to the belongs_to association, and cleared the association cache in an after_touch callback on the model with the has_many association. Like this:

class Foo < ApplicationRecord
  has_many :bars

  after_touch :clear_association_cache
end

class Bar < ApplicationRecord
  belongs_to :foo, touch: true
end

Now any time a Bar record is modified for a Foo, it clears the association cache on the Foo model.

Unfortunately, this doesn't work for multi-threaded servers since it only clears the cache for the current thread. I don't know how to get around that, other than just clearing the association cache inside the Foo#bars method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants