ActiveRecord Identity Map

by: Josh Kalderimis | posted: April 21st, 2011

Rails 3.1

Due to some recently discovered issues with updating objects in associations, the Identity Map will be turned off by default in Rails 3.1. You can still turn the Identity Map on, but it is recommended you read the documentation for further information.

If you’ve been using rails for a while now you may be familiar with Active Record’s query cache. The query cache is a powerful part of Active Record which reduces unnecessary SQL calls and provides general speed improvements, especially when dealing with associations. The problem with the query cache, however, is when retrieving two identical records from the database two in-memory objects will still be created.

rails console
  user1 = User.find(1) # => #<User id: 1, name: "Josh">
  user2 = User.find(1) # => #<User id: 1, name: "Josh">

  user1 == user2 # => true, b/c AR::Base recognizes that
                 # they have the same primary key

  user1.object_id == user2.object_id # => false, b/c these are two
                                     # different in-memory objects
log/development.log
  User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1  [["id", 1]]
  CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1  [["id", 1]]

Thanks to the fantastic work of Emilio Tagua during the Ruby Summer of Code 2010, Active Record in 3.1 will gain an identity map. What’s an identity map you ask? An identity map keeps a collection of previously instantiated records and returns the object associated with the record if a request is made for it again.

rails console
  user1 = User.find(1) # => #<User id: 1, name: "Josh">
  user2 = User.find(1) # => #<User id: 1, name: "Josh">

  user1 == user2 # => true

  user1.object_id == user2.object_id # => true, b/c these really are
                                     # the same in-memory objects
log/development.log
  User Load (2.2ms)  SELECT "users".* FROM "users" LIMIT 1
  User with ID = 1 loaded from Identity Map

Why is having the same in-memory object returned important? Because it ensures that there is only one copy of a model instance floating around your system at any one time. Without this assurance, modifications made to a model object in one context won’t be reflected if a copy exists in another context which can produce hard to trace bugs and inconsistencies.

The identity map is created on a per-request basis and is flushed at the completion of the request (as can be expected, the implementation is thread-safe). You can also use an identity map in the console, background worker, or manually within a request (if it’s turned off by default).

Ruby - app/models/user.rb
  ActiveRecord::IdentityMap.use do
    user = User.find(id)
    user.do_that_heavy_thing_you_do!
  end

Although Rails 3.1 will come with the identity map built-in but turned off by default, you can try it out for yourself by living on the edge and adding the following to application.rb :

Ruby - config/application.rb
  config.active_record.identity_map = true

And while the query cache is all about speed improvements, the identity map is primarily focused on consistency, thus they go hand in hand.

The following resources were instrumental in the research, creation and construction of this article. They may also provide a different angle should you be left wanting after reading this post: