Post

Performance Profiling a Mongoid Issue Using AppProfiler

In MONGOID-4889 the claim was made that assignment of a large number of embedded documents to an instance of a model will take increasingly longer as the size of the list of documents to embed grows. This is notable as no database operations are being performed during this process, which points to potential issues with the library itself. The ticket author identified Mongoid::Association::Embedded::EmbedsMany::Proxy#object_already_related? as the likely source of this performance issue, however I wanted to see how best to validate this.

While doing research on Ruby profiling I found Shopify’s blog post on “How to Fix Slow Code in Ruby”. Though the entire post was extremely insightful, it lead me to Shopify’s app_profiler library, which can be used to automatically profile code and redirect the output to a local instance of speedscope. Having worked previously with Flame Graphs of CPU stack traces collected using perf.

Adapting the sample code in the Jira ticket results in the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'

  gem 'mongoid'
  gem 'app_profiler'
end

class Foo
  include Mongoid::Document
  embeds_many :bars
end

class Bar
  include Mongoid::Document
  embedded_in :foo
end

# AppProfiler forms the output filename using Time.zone.now
require 'active_support/core_ext/time/zones'
Time.zone = 'Pacific Time (US & Canada)'
AppProfiler.root = Pathname.new(__dir__)
AppProfiler.profile_root = Pathname.new(__dir__)

arr = Array.new(2000) { Bar.new }
report = AppProfiler.run(mode: :cpu) do
  Foo.new.bars = arr
end
report.view

Running the above code doesn’t require a MongoDB connection string or an active cluster, but will attempt to embed 2000 new instances of Bar into the newly created instance of Foo. Once completed, the following chart is produced that reinforces the initial suspicion that the calls to object_already_related? are a likely candidate for further investigation.

AppProfiler was designed to be injected into a Rails application, however as can be seen above, it can easily be adapted to work on a standalone Ruby script as well.

Cross posted to DEV

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.