Updating Embedded Documents in Mongoid

I faced a situation in my project where I need to update all the embedded documents. But as we generally know about the mongoid embedded documents we can’t access directly, we must access them through parent documents.

I have millions of such parent documents and they embed many child documents. So in the general solution, to update the child documents I have to access the parent documents and then I need to use the loop on the parent documents to access the child, and then I will use an update query.

Using the loop on millions of documents is bad practice and it is causing slow performance. I used the below data model and queries to understand the problem in detail.

I have one vehicle master model which embeds_many insurances codes

class VehicleMaster
  include Mongoid::Document

  field :model,                 type: String
  field :submodel,              type: String
  field :fuel_type,             type: String
  field :seating_capacity,      type: Integer
  field :carrying_capacity,     type: Integer
  
  embeds_many :insurance_codes
end

I have another model insurance code which is embedded in vehicle_master model

class InsuranceCode
  include Mongoid::Document

  field :category_code,    type: String, default: ''
  field :bodytype_id,      type: String, default: ''
  field :segment_type,     type: String, default: ''
  field :category_type,    type: String, default: ''

  embedded_in :vehicle_master
  belongs_to :insurance_vendorend
end

General Solution:

I need to update the category_code of the particular vendor with some value. To do that the general solution is given below.

#select the vehicle master documents of a insurance vendor
vehicle_master = VehicleMaster.where('insurance_codes.insurance_vendor_id': InsuranceVendor.tata_aig)

#to update the category code in child element

vehicle_master.each do |vm|
  #access the child element and update with value.
  ic = vm.insurance_codes.where(insurance_vendor: InsuranceVendor.tata_aig).
  ic.update(category_code: 45)
end

As we see here, we are iterating on the records vehicle_master documents, suppose we have such millions of the document then it will take so much time and it is bad practice also. I have run this code on the rails console and calculated the time required to execute.

2.4.2 :070 > time = Benchmark.measure {
2.4.2 :071 >     vehicle_master.each do |vm|
2.4.2 :072 >       ic = vm.insurance_codes.where(insurance_vendor: InsuranceVendor.tata_aig)
2.4.2 :073?>       ic.update(category_code: 45)
2.4.2 :074?>     end
2.4.2 :075?>   }
 => #<Benchmark::Tms:0x0000561c1ed53730 @label="", @real=9.474939513980644, @cstime=0.0, @cutime=0.0, @stime=0.25, @utime=7.170000000000002, @total=7.420000000000002> 
2.4.2 :076 > time
 => #<Benchmark::Tms:0x0000561c1ed53730 @label="", @real=9.474939513980644, @cstime=0.0, @cutime=0.0, @stime=0.25, @utime=7.170000000000002, @total=7.420000000000002> 
2.4.2 :077 > time.total
 => 7.420000000000002

Here we required 7.42 seconds to update 3226 documents. Suppose if we have millions of documents, how much time it will take? Now we will see the optimized solution.

Optimized solution:

Rather than iterating on the vehicle_master documents, we can use the update_all methods on vehicle_master documents.

vehicle_master.update_all('$set' => { 'insurance_codes.$.category_code': '45'})

Here we can see the difference we are not iterating on vehicle_master documents. We are directly updating the insurance_codes.

time = Benchmark.measure { vehicle_master.update_all('$set' => { 'insurance_codes.$.category_code': '45'}) }
=> #<Benchmark::Tms:0x0000561c178196e0 @label="", @real=0.19454347199643962, @cstime=0.0, @cutime=0.0, @stime=0.0, @utime=0.00999999999999801, @total=0.00999999999999801>
2.4.2 :050 > time.total
=> 0.00999999999999801

Here We can see we have updated the same records with the same value only in 9ms. So we have reduced the query execution time from 7.42 seconds to 9ms.

Thanks for reading! I hope this article was helpful.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.