Tag Archives: activerecord

Avoiding Caching with Rails ActiveRecord

Today, I came across a puzzling issue with Rails 4.1, ActiveRecord, and Postgres when trying to select random records from a database table.

To demonstrate, let’s use a simple social network API example. Clients will POST a list of user, and I’ll “match” them to someone in my database.

Easy enough, for testing purposes we can just select a random user from our “users” table in postgres to simulate a match.

contacts = [{:id => 1}, {:id => 2}, {:id => 3}, {:id => 4}]
contacts.each do |c|
  user = User.order('random()').first
  c[:detail] = {:username => user.username}
end

Run that in our rails console, and good things happen. Seems like we have some random users from my (small) database.

[{:id=>1, :detail=>{:username=>"adam5"}},
 {:id=>2, :detail=>{:username=>"adam1"}},
 {:id=>3, :detail=>{:username=>"adam11"}},
 {:id=>4, :detail=>{:username=>"adam5"}}]

Let’s move that code into a controller action.

  def create
    contacts = [{:id => 1}, {:id => 2}, {:id => 3}, {:id => 4}]
    contacts.each do |c|
      user = User.order('random()').first
      c[:detail] = {:username => user.username}
    end
    render :json => contacts, :root => false
  end

Uh-oh. This time, we get a decidedly un-random response:

[
    {
        "id": 1,
        "detail": {
            "username": "adam6"
        }
    },
    {
        "id": 2,
        "detail": {
            "username": "adam6"
        }
    },
    {
        "id": 3,
        "detail": {
            "username": "adam6"
        }
    },
    {
        "id": 4,
        "detail": {
            "username": "adam6"
        }
    }
]

Looking at the logs, we’ll see this:

  User Load (0.7ms)  SELECT  "users".* FROM "users"   ORDER BY random() LIMIT 1
  User Load (0.7ms)  SELECT  "users".* FROM "users"   ORDER BY random() LIMIT 1
  CACHE (0.0ms)  SELECT  "users".* FROM "users"   ORDER BY random() LIMIT 1
  CACHE (0.0ms)  SELECT  "users".* FROM "users"   ORDER BY random() LIMIT 1
  CACHE (0.0ms)  SELECT  "users".* FROM "users"   ORDER BY random() LIMIT 1
  CACHE (0.0ms)  SELECT  "users".* FROM "users"   ORDER BY random() LIMIT 1
  CACHE (0.0ms)  SELECT  "users".* FROM "users"   ORDER BY random() LIMIT 1
  CACHE (0.0ms)  SELECT  "users".* FROM "users"   ORDER BY random() LIMIT 1

Rails caching, normally so useful, causes a problem in this situation. No sweat. You just need to use ActiveRecord’s uncached method.

Modifying the action to use uncached looks like this:

  def create
    contacts = [{:id => 1}, {:id => 2}, {:id => 3}, {:id => 4}]
    contacts.each do |c|
      User.uncached do
        user = User.order('random()').first
        c[:detail] = {:username => user.username}
      end
    end
    render :json => contacts, :root => false
  end

The database query no longer gets cached for the duration of the “User.uncached” block, and a new query is executed for each iteration. The controller action now gives us some nice, randomized output.

[
    {
        "id": 1,
        "detail": {
            "username": "adam3"
        }
    },
    {
        "id": 2,
        "detail": {
            "username": "adam3"
        }
    },
    {
        "id": 3,
        "detail": {
            "username": "adam4"
        }
    },
    {
        "id": 4,
        "detail": {
            "username": "adam1"
        }
    }
]
Advertisements
Tagged , ,
%d bloggers like this: