Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

use id_document_map and id_associations_map to improve performance of…

… eager loading
  • Loading branch information...
commit 2c63d69cb9fe98345ed3e60d5e63b89f9cccc74f 1 parent 85cf957
@flyerhzm authored
View
88 lib/mongoid/criterion/eager_loading.rb
@@ -11,7 +11,7 @@ module EagerLoading
# <tt>criteria.includes(:user, :post)</tt>
#
# Returns: <tt>self</tt>
- attr_accessor :eager_loadings
+ attr_accessor :eager_loadings, :id_document_map, :id_associations_map
def includes(*options)
@eager_loadings = options
@@ -21,46 +21,96 @@ def includes(*options)
def preload(documents)
document_class = documents.first.class
@eager_loadings.each do |eager_loading|
- reflection = association_reflection(document_class, eager_loading)
- setup_associations(documents, reflection)
+ setup_associations(documents, association_reflection(document_class, eager_loading))
end
end
private
+ def ignore_includes
+ @eager_loadings = nil
+ end
+
def association_reflection(document_class, eager_loading)
document_class.reflect_on_association(eager_loading)
end
def setup_associations(documents, reflection)
if reflection.association == Mongoid::Associations::ReferencesOne
- setup_associations_with_ids(documents, reflection, :find)
+ setup_associations_with_ids(documents, reflection, true)
elsif reflection.association == Mongoid::Associations::ReferencesMany
- setup_associations_with_ids(documents, reflection, :find_all)
+ setup_associations_with_ids(documents, reflection, false)
elsif reflection.association == Mongoid::Associations::ReferencesManyAsArray
- setup_associations_with_foreign_keys(documents, reflection, :find_all)
+ setup_associations_with_foreign_keys(documents, reflection, false)
elsif reflection.association == Mongoid::Associations::ReferencedIn
- setup_associations_with_foreign_keys(documents, reflection, :find)
+ setup_associations_with_foreign_keys(documents, reflection, true)
end
end
- def setup_associations_with_ids(documents, reflection, method)
- ids = documents.collect(&:id)
- eager_associations = reflection.name.singularize.camelize.constantize.where(reflection.foreign_key.to_sym.in => ids).to_a
+ def setup_associations_with_ids(documents, reflection, one=true)
+ ids = []
documents.each do |document|
- document.send("#{reflection.name}=", eager_associations.send(method) { |eager_association|
- eager_association.send(reflection.foreign_key) == document.id
- })
+ id_document_map[document.id] = document
+ ids << document.id
+ end
+
+ association_class = reflection.name.singularize.camelize.constantize
+ ignore_includes
+ eager_associations = association_class.where(reflection.foreign_key.to_sym.in => ids).to_a
+ eager_associations.each do |eager_association|
+ add_id_association(eager_association.send(reflection.foreign_key), eager_association)
+ end
+
+ id_document_map.each do |id, document|
+ document.send("#{reflection.name}=", one ? id_associations_map[id].first : id_associations_map[id])
end
end
- def setup_associations_with_foreign_keys(documents, reflection, method)
- ids = documents.collect(&:"#{reflection.foreign_key}").compact.flatten
- eager_associations = reflection.name.singularize.camelize.constantize.find(ids).to_a
+ def setup_associations_with_foreign_keys(documents, reflection, one)
+ ids = []
+ foreign_key_name = reflection.foreign_key
documents.each do |document|
- document.send("#{reflection.name}=", eager_associations.send(method) { |eager_association|
- eager_association.id == document.send(reflection.foreign_key)
- })
+ foreign_key_value = document.send(foreign_key_name)
+ if one
+ id_document_map[foreign_key_value] = document
+ ids << foreign_key_value
+ elsif foreign_key_value
+ foreign_key_value.each do |fkv|
+ id_document_map[fkv] = document
+ ids << fkv
+ end
+ end
+ end
+
+ association_class = reflection.name.singularize.camelize.constantize
+ ignore_includes
+ eager_associations = association_class.find(ids).to_a
+ eager_associations.each do |eager_association|
+ add_id_association(eager_association.id, eager_association)
end
+
+ id_document_map.each do |id, document|
+ foreign_key_value = document.send(foreign_key_name)
+ associations = \
+ if one
+ id_associations_map[foreign_key_value].first
+ else
+ foreign_key_value.collect { |fkv| id_associations_map[fkv] }.flatten.uniq
+ end
+ document.send("#{reflection.name}=", associations)
+ end
+ end
+
+ def id_document_map
+ @id_doccument_map ||= {}
+ end
+
+ def id_associations_map
+ @id_associations_map ||= {}
+ end
+
+ def add_id_association(id, association)
+ id_associations_map[id] ||= []
+ id_associations_map[id] << association
end
end
end
View
56 perf/eager_loading_benchmark.rb
@@ -0,0 +1,56 @@
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
+require "rubygems"
+require "benchmark"
+require "mongoid"
+
+Mongoid.configure do |config|
+ config.master = Mongo::Connection.new.db("mongoid_perf_test", :logger => Logger.new($stdout, :info))
+end
+
+#Mongoid.master.collection("people").drop
+#Mongoid.master.collection("posts").drop
+
+class Person
+ include Mongoid::Document
+ include Mongoid::Timestamps
+ field :name
+ references_many :posts
+end
+
+class Post
+ include Mongoid::Document
+ include Mongoid::Timestamps
+ field :title
+ referenced_in :person
+end
+
+#10000.times do |n|
+ #person = Person.create(:name => "Test_#{n}")
+ #person.posts.create(:title => "Test_#{2*n}")
+ #person.posts.create(:title => "Test_#{2*n+1}")
+#end
+
+puts "Starting benchmark..."
+Benchmark.bm do |bm|
+ bm.report("Finding 10 posts with person, without eager loading") do
+ Post.all.limit(10).each { |p| p.person.name }
+ end
+
+ bm.report("Finding 10 posts with person, with eager loading") do
+ Post.includes(:person).limit(10).each { |p| p.person.name }
+ end
+ bm.report("Finding 50 posts with person, without eager loading") do
+ Post.all.limit(50).each { |p| p.person.name }
+ end
+
+ bm.report("Finding 50 posts with person, with eager loading") do
+ Post.includes(:person).limit(50).each { |p| p.person.name }
+ end
+ bm.report("Finding 100 posts with person, without eager loading") do
+ Post.all.limit(100).each { |p| p.person.name }
+ end
+
+ bm.report("Finding 100 posts with person, with eager loading") do
+ Post.includes(:person).limit(100).each { |p| p.person.name }
+ end
+end
View
49 spec/unit/mongoid/criterion/eager_loading_spec.rb
@@ -17,10 +17,11 @@
end
describe "#preload" do
- let(:person1) { Person.create(:title => "Sir", :age => 100, :aliases => ["D", "Durran"], :ssn => "666666666") }
- let(:person2) { Person.create(:title => "Madam", :age => 1, :ssn => "098-76-5434") }
before do
+ person1 = Person.create(:title => "Sir", :age => 100, :aliases => ["D", "Durran"], :ssn => "666666666")
+ person2 = Person.create(:title => "Madam", :age => 1, :ssn => "098-76-5434")
+
person1.create_game(:score => 10)
person2.create_game(:score => 20)
@@ -36,39 +37,67 @@
end
it "preload references_one association" do
+ people = Person.all.to_a
+ games = Game.all.to_a
+
complex = stub(:key => :person_id, :operator => "in")
Mongoid::Criterion::Complex.expects(:new).with(:key => :person_id, :operator => "in").returns(complex)
- Game.expects(:where).with(complex => [person1.id, person2.id]).returns([person1.game, person2.game])
+ Game.expects(:where).with(complex => people.collect(&:id)).returns(games)
criteria = Mongoid::Criteria.new(Person)
criteria.includes(:game)
- criteria.preload([person1, person2])
+ criteria.preload(people)
+
+ people.first.game.should == games.first
+ people.last.game.should == games.last
end
it "preload references_many association" do
+ people = Person.all.to_a
+ posts = Post.all.to_a
+ person1_posts = Post.where(:person_id => people.first.id).to_a
+ person2_posts = Post.where(:person_id => people.last.id).to_a
+
complex = stub(:key => :person_id, :operator => "in")
Mongoid::Criterion::Complex.expects(:new).with(:key => :person_id, :operator => "in").returns(complex)
- Post.expects(:where).with(complex => [person1.id, person2.id]).returns(person1.posts + person2.posts)
+ Post.expects(:where).with(complex => people.collect(&:id)).returns(posts)
criteria = Mongoid::Criteria.new(Person)
criteria.includes(:posts)
- criteria.preload([person1, person2])
+ criteria.preload(people)
+
+ people.first.posts.should == person1_posts
+ people.last.posts.should == person2_posts
end
it "preload references_many_as_array association" do
- Preference.expects(:find).with((person1.preferences + person2.preferences).collect(&:id)).returns(person1.preferences + person2.preferences)
+ people = Person.all.to_a
+ preferences = Preference.all.to_a
+ person1_preferences = Preference.find(people.first.preference_ids).to_a
+ person2_preferences = Preference.find(people.last.preference_ids).to_a
+
+ Preference.expects(:find).with(preferences.collect(&:id)).returns(preferences)
criteria = Mongoid::Criteria.new(Person)
criteria.includes(:preferences)
- criteria.preload([person1, person2])
+ criteria.preload(people)
+
+ people.first.preferences.should == person1_preferences
+ people.last.preferences.should == person2_preferences
end
it "preload referenced_in association" do
- Person.expects(:find).with([person1.id, person2.id]).returns([person1, person2])
+ people = Person.all.to_a
+ games = Game.all.to_a
+
+ Person.expects(:find).with(people.collect(&:id)).returns(people)
criteria = Mongoid::Criteria.new(Game)
criteria.includes(:person)
- criteria.preload([person1.game, person2.game])
+ criteria.preload(games)
+
+ people.first.game.should == games.first
+ people.last.game.should == games.last
end
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.