Rails

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.

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

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

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

Let’s move that code into a controller action.

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

 1 def create
 2     contacts = [{:id => 1}, {:id => 2}, {:id => 3}, {:id => 4}]
 3     contacts.each do |c|
 4       User.uncached do
 5         user = User.order('random()').first
 6         c[:detail] = {:username => user.username}
 7       end
 8     end
 9     render :json => contacts, :root => false
10   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"
        }
    }
]

Leave a Reply

Your email address will not be published. Required fields are marked *