From 6a915d6f2d462a376d8cecc062dd58e520339b5e Mon Sep 17 00:00:00 2001
From: Toon Claes <toon@gitlab.com>
Date: Tue, 2 May 2017 22:52:14 +0200
Subject: [PATCH] Limit `update_tracked_fields` to write to database once/hour

Every time a user logs in or out, the Trackable attributes are written to the
database. This is causing a lot of load on the database, for data that isn't
really critical.

So to avoid the database being hammered, add a Gitlab::ExclusiveLease before
writing trackable attributes to the database. This lease expires after an hour,
so only when the attributes were written more than an hour ago, they can be
written again. Otherwise they are ignored.
---
 app/models/user.rb                            | 10 ++++++++++
 .../tc-cache-trackable-attributes.yml         |  4 ++++
 spec/models/user_spec.rb                      | 19 +++++++++++++++++++
 3 files changed, 33 insertions(+)
 create mode 100644 changelogs/unreleased/tc-cache-trackable-attributes.yml

diff --git a/app/models/user.rb b/app/models/user.rb
index accaa91b805..05f636c020a 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -40,6 +40,16 @@ class User < ActiveRecord::Base
   devise :lockable, :recoverable, :rememberable, :trackable,
     :validatable, :omniauthable, :confirmable, :registerable
 
+  # Limit trackable fields to update at most once every hour
+  alias_method :devise_update_tracked_fields!, :update_tracked_fields!
+
+  def update_tracked_fields!(request)
+    lease = Gitlab::ExclusiveLease.new("user_update_tracked_fields:#{id}", timeout: 1.hour.to_i)
+    return unless lease.try_obtain
+
+    devise_update_tracked_fields!(request)
+  end
+
   attr_accessor :force_random_password
 
   # Virtual attribute for authenticating by either username or email
diff --git a/changelogs/unreleased/tc-cache-trackable-attributes.yml b/changelogs/unreleased/tc-cache-trackable-attributes.yml
new file mode 100644
index 00000000000..4a2cf50893a
--- /dev/null
+++ b/changelogs/unreleased/tc-cache-trackable-attributes.yml
@@ -0,0 +1,4 @@
+---
+title: "Limit User's trackable attributes, like `current_sign_in_at`, to update at most once/hour"
+merge_request: 11053
+author:
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 63e71f5ff2f..0b59916342e 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -344,6 +344,25 @@ describe User, models: true do
     end
   end
 
+  describe '#update_tracked_fields!', :redis do
+    let(:request) { OpenStruct.new(remote_ip: "127.0.0.1") }
+    let(:user) { create(:user) }
+
+    it 'writes trackable attributes' do
+      expect do
+        user.update_tracked_fields!(request)
+      end.to change { user.reload.current_sign_in_at }
+    end
+
+    it 'does not write trackable attributes when called a second time within the hour' do
+      user.update_tracked_fields!(request)
+
+      expect do
+        user.update_tracked_fields!(request)
+      end.not_to change { user.current_sign_in_at }
+    end
+  end
+
   shared_context 'user keys' do
     let(:user) { create(:user) }
     let!(:key) { create(:key, user: user) }
-- 
2.30.9