summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorYorick Peterse <yorickpeterse@gmail.com>2017-12-01 14:52:16 +0100
committerYorick Peterse <yorickpeterse@gmail.com>2017-12-06 15:59:37 +0100
commit856447ccd305832c4e9421ab9e81d63eea348203 (patch)
tree99df49e2050a3dcb2f2d289a8a728fa5b28f83be /app
parent483b5f1bfac5e338fab0e11f2045254f70df8fc1 (diff)
downloadgitlab-ce-856447ccd305832c4e9421ab9e81d63eea348203.tar.gz
Throttle the number of UPDATEs triggered by touch
This throttles the number of UPDATE queries that can be triggered by calling "touch" on a Note, Issue, or MergeRequest. For Note objects we also take care of updating the associated "noteable" relation in a smarter way than Rails does by default.
Diffstat (limited to 'app')
-rw-r--r--app/models/concerns/throttled_touch.rb10
-rw-r--r--app/models/issue.rb1
-rw-r--r--app/models/merge_request.rb1
-rw-r--r--app/models/note.rb36
4 files changed, 47 insertions, 1 deletions
diff --git a/app/models/concerns/throttled_touch.rb b/app/models/concerns/throttled_touch.rb
new file mode 100644
index 00000000000..ad0ff0f20d4
--- /dev/null
+++ b/app/models/concerns/throttled_touch.rb
@@ -0,0 +1,10 @@
+# ThrottledTouch can be used to throttle the number of updates triggered by
+# calling "touch" on an ActiveRecord model.
+module ThrottledTouch
+ # The amount of time to wait before "touch" can update a record again.
+ TOUCH_INTERVAL = 1.minute
+
+ def touch(*args)
+ super if (Time.zone.now - updated_at) > TOUCH_INTERVAL
+ end
+end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index d6ef58d150b..33db197e612 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -9,6 +9,7 @@ class Issue < ActiveRecord::Base
include FasterCacheKeys
include RelativePositioning
include TimeTrackable
+ include ThrottledTouch
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index f2d639a3382..949d42f865c 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -7,6 +7,7 @@ class MergeRequest < ActiveRecord::Base
include TimeTrackable
include ManualInverseAssociation
include EachBatch
+ include ThrottledTouch
ignore_column :locked_at,
:ref_fetched
diff --git a/app/models/note.rb b/app/models/note.rb
index 340fe087f82..733bbbc013f 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -15,6 +15,7 @@ class Note < ActiveRecord::Base
include IgnorableColumn
include Editable
include Gitlab::SQL::Pattern
+ include ThrottledTouch
module SpecialRole
FIRST_TIME_CONTRIBUTOR = :first_time_contributor
@@ -55,7 +56,7 @@ class Note < ActiveRecord::Base
participant :author
belongs_to :project
- belongs_to :noteable, polymorphic: true, touch: true # rubocop:disable Cop/PolymorphicAssociations
+ belongs_to :noteable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
belongs_to :author, class_name: "User"
belongs_to :updated_by, class_name: "User"
belongs_to :last_edited_by, class_name: 'User'
@@ -118,6 +119,7 @@ class Note < ActiveRecord::Base
before_validation :set_discussion_id, on: :create
after_save :keep_around_commit, if: :for_project_noteable?
after_save :expire_etag_cache
+ after_save :touch_noteable
after_destroy :expire_etag_cache
class << self
@@ -367,6 +369,38 @@ class Note < ActiveRecord::Base
Gitlab::EtagCaching::Store.new.touch(key)
end
+ def touch(*args)
+ # We're not using an explicit transaction here because this would in all
+ # cases result in all future queries going to the primary, even if no writes
+ # are performed.
+ #
+ # We touch the noteable first so its SELECT query can run before our writes,
+ # ensuring it runs on a secondary (if no prior write took place).
+ touch_noteable
+ super
+ end
+
+ # By default Rails will issue an "SELECT *" for the relation, which is
+ # overkill for just updating the timestamps. To work around this we manually
+ # touch the data so we can SELECT only the columns we need.
+ def touch_noteable
+ # Commits are not stored in the DB so we can't touch them.
+ return if for_commit?
+
+ assoc = association(:noteable)
+
+ noteable_object =
+ if assoc.loaded?
+ noteable
+ else
+ # If the object is not loaded (e.g. when notes are loaded async) we
+ # _only_ want the data we actually need.
+ assoc.scope.select(:id, :updated_at).take
+ end
+
+ noteable_object&.touch
+ end
+
private
def keep_around_commit