Uploaded image for project: 'Mongoid'
  1. Mongoid
  2. MONGOID-5840

Inefficient Query Behavior with includes on Inherited Classes in Mongoid 8.0.8

    • Type: Icon: Bug Bug
    • Resolution: Duplicate
    • Priority: Icon: Unknown Unknown
    • None
    • Affects Version/s: None
    • Component/s: None
    • None
    • None
    • Ruby Drivers
    • Hide

      1. What would you like to communicate to the user about this feature?
      2. Would you like the user to see examples of the syntax and/or executable code and its output?
      3. Which versions of the driver/connector does this apply to?

      Show
      1. What would you like to communicate to the user about this feature? 2. Would you like the user to see examples of the syntax and/or executable code and its output? 3. Which versions of the driver/connector does this apply to?
    • None
    • None
    • None
    • None
    • None
    • None

      When includes is called on a relation involving an inherited class, Mongoid 8 performs additional queries when accessing the related objects. This behavior can lead to inefficiencies, as demonstrated in the code example below.

      require 'bundler/inline'
      
      gemfile do
        source 'https://rubygems.org'
      
        gem 'pry'
        gem 'pry-byebug'
      
        gem 'mongoid', '~> 8.0.8'
        # gem 'mongoid', '~> 7.5.4'
      end
      
      Mongoid.configure do |config|
        config.clients.default = {
          hosts: ['172.18.0.4:27017'],
          database: 'mongoid_test',
        }
      end
      Mongoid.purge!
      
      class Channel
        include Mongoid::Document
      
        has_many :tasks
      end
      
      class Task
        include Mongoid::Document
      
        belongs_to :channel
      end
      
      class ProcessingTask < Task
      end
      
      3.times { ProcessingTask.create!(channel: Channel.create!) }
      
      Mongo::Logger.logger = Logger.new($stdout)
      Mongo::Logger.logger.level = Logger::DEBUG
      
      # Observations with Mongoid versions:
      # Mongoid 7.5.4: 2 queries
      # Mongoid 8.0.8: 5 queries
      ProcessingTask.includes(:channel).each do |task|
        pp task.channel&.id
      end
      
      # Consistent behavior (2 queries) across Mongoid versions
      Task.where(_type: 'ProcessingTask').includes(:channel).each do |task|
        pp task.channel&.id
      end
      

      Example Output with Mongoid 8.0.8

      When Calling ProcessingTask.includes(:channel):

      The output shows that Mongoid performs multiple queries for the channel relation of ProcessingTask. Specifically, each ProcessingTask access results in a separate query to fetch the associated channel.

       

      D, [2025-01-15T17:12:03.233685 #1333] DEBUG -- : MONGODB | 172.18.0.4:27017 req:14 conn:1:1 sconn:280 | mongoid_test.find | STARTED | {"find"=>"tasks", "filter"=>{"_type"=>"ProcessingTask"}, "$db"=>"mongoid_test", "lsid"=>{"id"=><BSON::Binary:0x2780 type=uuid data=0x64c54f1c5b454fb
      9...>}}
      D, [2025-01-15T17:12:03.234118 #1333] DEBUG -- : MONGODB | 172.18.0.4:27017 req:14 | mongoid_test.find | SUCCEEDED | 0.000s
      D, [2025-01-15T17:12:03.234902 #1333] DEBUG -- : MONGODB | 172.18.0.4:27017 req:15 conn:1:1 sconn:280 | mongoid_test.find | STARTED | {"find"=>"channels", "filter"=>{"_id"=>BSON::ObjectId('6787de53797b7405357df0f1')}, "limit"=>1, "$db"=>"mongoid_test", "lsid"=>{"id"=><BSON::Binary:0x2780 type=uuid data=0x64c54f1c5b454fb9...>}}
      D, [2025-01-15T17:12:03.235129 #1333] DEBUG -- : MONGODB | 172.18.0.4:27017 req:15 | mongoid_test.find | SUCCEEDED | 0.000s
      BSON::ObjectId('6787de53797b7405357df0f1')
      D, [2025-01-15T17:12:03.235917 #1333] DEBUG -- : MONGODB | 172.18.0.4:27017 req:16 conn:1:1 sconn:280 | mongoid_test.find | STARTED | {"find"=>"channels", "filter"=>{"_id"=>BSON::ObjectId('6787de53797b7405357df0f3')}, "limit"=>1, "$db"=>"mongoid_test", "lsid"=>{"id"=><BSON::Binary:0x2780 type=uuid data=0x64c54f1c5b454fb9...>}}
      D, [2025-01-15T17:12:03.236150 #1333] DEBUG -- : MONGODB | 172.18.0.4:27017 req:16 | mongoid_test.find | SUCCEEDED | 0.000s
      BSON::ObjectId('6787de53797b7405357df0f3')
      D, [2025-01-15T17:12:03.236834 #1333] DEBUG -- : MONGODB | 172.18.0.4:27017 req:17 conn:1:1 sconn:280 | mongoid_test.find | STARTED | {"find"=>"channels", "filter"=>{"_id"=>BSON::ObjectId('6787de53797b7405357df0f5')}, "limit"=>1, "$db"=>"mongoid_test", "lsid"=>{"id"=><BSON::Binary:0x2780 type=uuid data=0x64c54f1c5b454fb9...>}}
      D, [2025-01-15T17:12:03.236990 #1333] DEBUG -- : MONGODB | 172.18.0.4:27017 req:17 | mongoid_test.find | SUCCEEDED | 0.000s
      BSON::ObjectId('6787de53797b7405357df0f5')
      

      When Using Task.where(_type: 'ProcessingTask').includes(:channel):

      In contrast, filtering explicitly by _type produces consistent query behavior across versions. Mongoid executes fewer queries by preloading all necessary relations in a single query.

      D, [2025-01-15T17:12:03.237761 #1333] DEBUG -- : MONGODB | 172.18.0.4:27017 req:18 conn:1:1 sconn:280 | mongoid_test.find | STARTED | {"find"=>"tasks", "filter"=>{"_type"=>"ProcessingTask"}, "$db"=>"mongoid_test", "lsid"=>{"id"=><BSON::Binary:0x2780 type=uuid data=0x64c54f1c5b454fb
      9...>}}
      D, [2025-01-15T17:12:03.237929 #1333] DEBUG -- : MONGODB | 172.18.0.4:27017 req:18 | mongoid_test.find | SUCCEEDED | 0.000s
      D, [2025-01-15T17:12:03.238399 #1333] DEBUG -- : MONGODB | 172.18.0.4:27017 req:19 conn:1:1 sconn:280 | mongoid_test.find | STARTED | {"find"=>"channels", "filter"=>{"_id"=>{"$in"=>[BSON::ObjectId('6787de53797b7405357df0f1'), BSON::ObjectId('6787de53797b7405357df0f3'), BSON::ObjectId('6787de53797b7405357df0f5')]}}, "$db"=>"mongoid_test", "lsid"=>{"id"=><BSON::Binary:0x2780 type=uuid...
      D, [2025-01-15T17:12:03.238609 #1333] DEBUG -- : MONGODB | 172.18.0.4:27017 req:19 | mongoid_test.find | SUCCEEDED | 0.000s
      BSON::ObjectId('6787de53797b7405357df0f1')
      BSON::ObjectId('6787de53797b7405357df0f3')
      BSON::ObjectId('6787de53797b7405357df0f5')
      

      Key Takeaways

      1. Using includes directly with an inherited class like ProcessingTask can lead to additional queries when accessing related objects (channel).
      2. Filtering by _type explicitly (e.g., Task.where(_type: 'ProcessingTask')) ensures efficient preloading of relations.
      3. The behavior has changed between Mongoid versions (7.5.4 vs 8.0.8), with the latter performing more queries for the same operation.

            Assignee:
            Unassigned Unassigned
            Reporter:
            guirec.corbel@gmail.com Guirec Corbel
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved:
              None
              None
              None
              None