Search code examples
rubyruby-on-rails-3cachingmongoidrack

mongoid query caching


Rails' ActiveRecord has a feature called Query Caching (ActiveRecord::QueryCache) which saves the result of SQL query for the life-span of a request. While I'm not very familiar with the internals of the implementation, I think that it saves the query results somewhere in the Rack env, which is discarded in the end of the request.

The Mongoid, unfortunately, doesn't currently provide such feature, and this is exacerbated by the fact, that some queries occur implicitly (references). I'm considering to implement this feature, and I'm curious, where and how Mongoid (or, perhaps, mongo driver?) should be hooked in order to implement this.


Solution

  • The other answer is obviously wrong. Not only mongoid or mongo driver doesn't cache the query, even if mongo would - it still might be on other machine across the network.

    My solution was to wrap the receive_message in Mongo::Connection. Pros: one definite place Cons: deserialization still takes place

    
    require 'mongo'
    module Mongo
      class Connection
        module QueryCache
          extend ActiveSupport::Concern
    
          module InstanceMethods
    
            # Enable the selector cache within the block.
            def cache
              @query_cache ||= {}
              old, @query_cache_enabled = @query_cache_enabled, true
              yield
            ensure
              clear_query_cache
              @query_cache_enabled = old
            end
    
            # Disable the selector cache within the block.
            def uncached
              old, @query_cache_enabled = @query_cache_enabled, false
              yield
            ensure
              @query_cache_enabled = old
            end
    
            def clear_query_cache
              @query_cache.clear
            end
    
            def cache_receive_message(operation, message)
              @query_cache[operation] ||= {}
              key = message.to_s.hash
              log = "[MONGO] CACHE %s"
              if entry = @query_cache[operation][key]
                Mongoid.logger.debug log % 'HIT'
                entry
              else
                Mongoid.logger.debug log % 'MISS'
                @query_cache[operation][key] = yield
              end
            end
    
            def receive_message_with_cache(operation, message, log_message=nil, socket=nil, command=false)
              if query_cache_enabled
                cache_receive_message(operation, message) do
                  receive_message_without_cache(operation, message, log_message, socket, command)
                end
              else
                receive_message_without_cache(operation, message, log_message, socket, command)
              end
            end
          end # module InstanceMethods
    
          included do
            alias_method_chain :receive_message, :cache
            attr_reader :query_cache, :query_cache_enabled
          end
        end # module QueryCache
      end # class Connection
    end
    
    Mongo::Connection.send(:include, Mongo::Connection::QueryCache)