From 25818bd7ae765422c934d0a32efb4ba353d11183 Mon Sep 17 00:00:00 2001
From: Stan Hu <stanhu@gmail.com>
Date: Mon, 29 Apr 2019 17:07:42 -0700
Subject: [PATCH] Disable method replacement in avatar loading

We've seen a significant performance penalty when using
`BatchLoader#__replace_with!`. This defines methods on the batch loader
that proxy to the 'real' object using send. The alternative is
`method_missing`, which is slower.  However, we've noticed that
`method_missing` can be faster if:

1. The objects being loaded have a large interface.
2. We don't call too many methods on the loaded object.

Avatar uploads meet both criteria above, so let's use the newly-released
feature in https://github.com/exAspArk/batch-loader/pull/45.

Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/60903
---
 Gemfile                                                   | 2 +-
 Gemfile.lock                                              | 4 ++--
 app/models/concerns/avatarable.rb                         | 3 ++-
 .../unreleased/sh-disable-batch-load-replace-methods.yml  | 5 +++++
 spec/uploaders/object_storage_spec.rb                     | 8 ++++++++
 5 files changed, 18 insertions(+), 4 deletions(-)
 create mode 100644 changelogs/unreleased/sh-disable-batch-load-replace-methods.yml

diff --git a/Gemfile b/Gemfile
index 95d65ec30c7ae..e075e2478a8cb 100644
--- a/Gemfile
+++ b/Gemfile
@@ -285,7 +285,7 @@ gem 'gettext_i18n_rails', '~> 1.8.0'
 gem 'gettext_i18n_rails_js', '~> 1.3'
 gem 'gettext', '~> 3.2.2', require: false, group: :development
 
-gem 'batch-loader', '~> 1.2.2'
+gem 'batch-loader', '~> 1.4.0'
 
 # Perf bar
 gem 'peek', '~> 1.0.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index c08f0c24ba6a5..c5ad23574344f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -76,7 +76,7 @@ GEM
       thread_safe (~> 0.3, >= 0.3.1)
     babosa (1.0.2)
     base32 (0.3.2)
-    batch-loader (1.2.2)
+    batch-loader (1.4.0)
     bcrypt (3.1.12)
     bcrypt_pbkdf (1.0.0)
     benchmark-ips (2.3.0)
@@ -999,7 +999,7 @@ DEPENDENCIES
   awesome_print
   babosa (~> 1.0.2)
   base32 (~> 0.3.0)
-  batch-loader (~> 1.2.2)
+  batch-loader (~> 1.4.0)
   bcrypt_pbkdf (~> 1.0)
   benchmark-ips (~> 2.3.0)
   better_errors (~> 2.5.0)
diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb
index 4687ec7d16617..80278e07e652a 100644
--- a/app/models/concerns/avatarable.rb
+++ b/app/models/concerns/avatarable.rb
@@ -91,7 +91,8 @@ def upload_paths(identifier)
   private
 
   def retrieve_upload_from_batch(identifier)
-    BatchLoader.for(identifier: identifier, model: self).batch(key: self.class) do |upload_params, loader, args|
+    BatchLoader.for(identifier: identifier, model: self)
+               .batch(key: self.class, cache: true, replace_methods: false) do |upload_params, loader, args|
       model_class = args[:key]
       paths = upload_params.flat_map do |params|
         params[:model].upload_paths(params[:identifier])
diff --git a/changelogs/unreleased/sh-disable-batch-load-replace-methods.yml b/changelogs/unreleased/sh-disable-batch-load-replace-methods.yml
new file mode 100644
index 0000000000000..00f897ac4b1ad
--- /dev/null
+++ b/changelogs/unreleased/sh-disable-batch-load-replace-methods.yml
@@ -0,0 +1,5 @@
+---
+title: Disable method replacement in avatar loading
+merge_request: 27866
+author:
+type: performance
diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb
index 9ce9a35391320..a62830c35f11a 100644
--- a/spec/uploaders/object_storage_spec.rb
+++ b/spec/uploaders/object_storage_spec.rb
@@ -771,6 +771,14 @@ def when_file_is_in_use
           expect { avatars }.not_to exceed_query_limit(1)
         end
 
+        it 'does not attempt to replace methods' do
+          models.each do |model|
+            expect(model.avatar.upload).to receive(:method_missing).and_call_original
+
+            model.avatar.upload.path
+          end
+        end
+
         it 'fetches a unique upload for each model' do
           expect(avatars.map(&:url).uniq).to eq(avatars.map(&:url))
           expect(avatars.map(&:upload).uniq).to eq(avatars.map(&:upload))
-- 
GitLab