The Performance Cost of MongoDB Safe Inserts and Updates

We have been using mongodb and mongomapper for a large scale project that deals with a lot of data. Due to concerns about data integrity we decided to use ‘safe’ inserts and updates with mongo. However this comes at a cost. The bellow paragraph explains what its all about.

Quoting mongomapper documentation:

In MongoDB, database writes/updates follow the “fire and forget” pattern. This means that the database will not wait for a response when you perform an update operation on a document. MongoMapper follows this same convention by default.

The down side of this strategy is if something goes wrong–such as a unique index constraint failing or an internal MongoDB error–an error won’t be raised. You may never know that your data isn’t being saved, until you try to read it and it’s not there.

Instead of just using the default “fire and forget” behavior on our save operation, passing the :safe option to save will force the driver to make sure the save succeeds and raise an error if it doesn’t.

So I decided to do some SIMPLE tests to get a rough idea of how much performance we loose when we do safe inserts/updates over ‘unsafe’ ones.

Here I am using two classes:

TestUser: Mongo document without the safe parameter

[test_user.rb]
1
2
3
4
5
6
class TestUser
  include MongoMapper::Document

  key :name, String
  key :age,  Integer
end

SafeTestUser: Mongo document with the safe parameter

[safe_test_user.rb]
1
2
3
4
5
6
7
class SafeTestUser
  include MongoMapper::Document
  safe

  key :name, String
  key :age,  Integer
end

All tests were run on IRB in my local machine and the mongodb instace was a ec2 server hosted remotely(duh). Used MRI Ruby 1.9.2

100000 inserts were done twise.

[Inserts - Unsafe]
1
2
3
4
5
6
7
8
9
10
11
12
13
1.9.2p290 :035 > Benchmark.measure {
1.9.2p290 :036 >     100000.times {
1.9.2p290 :037 >       SafeTestUser.create(:name => "test_name", :age => "21")
1.9.2p290 :038?>     }
1.9.2p290 :039?>   }
 =>  32.970000   2.090000  35.060000 ( 35.533769)

1.9.2p290 :062 > Benchmark.measure {
1.9.2p290 :063 >          100000.times {
1.9.2p290 :064 >              TestUser.create(:name => "test_name", :age => "21")
1.9.2p290 :065?>          }
1.9.2p290 :066?>      }
 =>  34.060000   2.130000  36.190000 ( 41.372273)
[Inserts - Safe]
1
2
3
4
5
6
7
8
9
10
11
12
13
1.9.2p290 :048 > Benchmark.measure {
1.9.2p290 :049 >          100000.times {
1.9.2p290 :050 >              SafeTestUser.create(:name => "test_name", :age => "21")
1.9.2p290 :051?>          }
1.9.2p290 :052?>      }
 =>  88.620000  13.080000 101.700000 (744.424769)

1.9.2p290 :082 > Benchmark.measure {
1.9.2p290 :083 >          100000.times {
1.9.2p290 :084 >              SafeTestUser.create(:name => "test_name", :age => "21")
1.9.2p290 :085?>          }
1.9.2p290 :086?>      }
 =>  62.410000   9.410000  71.820000 (746.311413)

100000 Updates were done using ‘set’ and then using basic iteration

[Updates using ‘set’ - Unsafe]
1
2
3
4
1.9.2p290 :077 > Benchmark.measure {
1.9.2p290 :078 >          TestUser.set({}, :name => "updatetest")
1.9.2p290 :079?>      }
 =>   0.000000   0.000000   0.000000 (  0.000494)
[Updates using ‘set’ - Safe]
1
2
3
4
1.9.2p290 :088 > Benchmark.measure {
1.9.2p290 :089 >          SafeTestUser.set({}, :name => "updatetest")
1.9.2p290 :090?>      }
 =>   0.000000   0.000000   0.000000 (  0.000396)
[Updates using basic iteration - Unsafe]
1
2
3
4
5
6
7
8
1.9.2p290 :091 > Benchmark.measure {
1.9.2p290 :092 >          TestUser.all.each { |u|
1.9.2p290 :093 >            u.name = "testtesttestname"
1.9.2p290 :094?>     u.age = "251"
1.9.2p290 :095?>     u.save
1.9.2p290 :096?>          }
1.9.2p290 :097?>      }
 => 160.280000   5.940000 166.220000 (168.674940)
[Updates using basic iteration - Safe]
1
2
3
4
5
6
7
8
1.9.2p290 :098 > Benchmark.measure {
1.9.2p290 :099 >          SafeTestUser.all.each { |u|
1.9.2p290 :100 >            u.name = "testtesttestname"
1.9.2p290 :101?>     u.age = "251"
1.9.2p290 :102?>     u.save
1.9.2p290 :103?>          }
1.9.2p290 :104?>      }
 => 104.810000  10.010000 114.820000 (805.641090)

The tests I just did are not the most accurate way of judging the performance of mongo. But I think they give a rough indication of how much of a performance penalty you pay when you opt for safe inserts and updates.

According to what I see above the inserts are 19(19.38) times slower! And the updates are (ignoring the ‘set’ updates) around 5(4.7) times slower.

So i think if you want to use safe updates and inserts you really need to be sure that its worth the performance penalty you have to pay.

Comments