PERF: move 3 more option columns out of the user table

This commit is contained in:
Sam
2016-02-18 16:57:22 +11:00
parent f9c5cded6f
commit f0e942f647
22 changed files with 278 additions and 250 deletions

View File

@@ -141,13 +141,11 @@ const User = RestModel.extend({
save() {
const data = this.getProperties(
'auto_track_topics_after_msecs',
'bio_raw',
'website',
'location',
'name',
'locale',
'new_topic_duration_minutes',
'custom_fields',
'user_fields',
'muted_usernames',
@@ -165,7 +163,9 @@ const User = RestModel.extend({
'enable_quoting',
'disable_jump_reply',
'automatically_unpin_topics',
'digest_after_days'
'digest_after_days',
'new_topic_duration_minutes',
'auto_track_topics_after_msecs'
].forEach(s => {
data[s] = this.get(`user_option.${s}`);
});

View File

@@ -201,12 +201,12 @@
<div class="controls controls-dropdown">
<label>{{i18n 'user.new_topic_duration.label'}}</label>
{{combo-box valueAttribute="value" content=considerNewTopicOptions value=model.new_topic_duration_minutes}}
{{combo-box valueAttribute="value" content=considerNewTopicOptions value=model.user_option.new_topic_duration_minutes}}
</div>
<div class="controls controls-dropdown">
<label>{{i18n 'user.auto_track_topics'}}</label>
{{combo-box valueAttribute="value" content=autoTrackDurations value=model.auto_track_topics_after_msecs}}
{{combo-box valueAttribute="value" content=autoTrackDurations value=model.user_option.auto_track_topics_after_msecs}}
</div>
{{preference-checkbox labelKey="user.external_links_in_new_tab" checked=model.user_option.external_links_in_new_tab}}

View File

@@ -4,7 +4,7 @@ module Jobs
def execute(args)
if user = User.find_by(id: args[:user_id])
user.update_column(:last_redirected_to_top_at, args[:redirected_at])
user.user_option.update_column(:last_redirected_to_top_at, args[:redirected_at])
end
end
end

View File

@@ -104,9 +104,9 @@ class TopicTrackingState
def self.treat_as_new_topic_clause
User.where("GREATEST(CASE
WHEN COALESCE(u.new_topic_duration_minutes, :default_duration) = :always THEN u.created_at
WHEN COALESCE(u.new_topic_duration_minutes, :default_duration) = :last_visit THEN COALESCE(u.previous_visit_at,u.created_at)
ELSE (:now::timestamp - INTERVAL '1 MINUTE' * COALESCE(u.new_topic_duration_minutes, :default_duration))
WHEN COALESCE(uo.new_topic_duration_minutes, :default_duration) = :always THEN u.created_at
WHEN COALESCE(uo.new_topic_duration_minutes, :default_duration) = :last_visit THEN COALESCE(u.previous_visit_at,u.created_at)
ELSE (:now::timestamp - INTERVAL '1 MINUTE' * COALESCE(uo.new_topic_duration_minutes, :default_duration))
END, us.new_since, :min_date)",
now: DateTime.now,
last_visit: User::NewTopicDuration::LAST_VISIT,
@@ -169,6 +169,7 @@ class TopicTrackingState
FROM topics
JOIN users u on u.id = :user_id
JOIN user_stats AS us ON us.user_id = u.id
JOIN user_options AS uo ON uo.user_id = u.id
JOIN categories c ON c.id = topics.category_id
LEFT JOIN topic_users tu ON tu.topic_id = topics.id AND tu.user_id = u.id
WHERE u.id = :user_id AND

View File

@@ -104,7 +104,7 @@ class TopicUser < ActiveRecord::Base
if rows == 0
now = DateTime.now
auto_track_after = User.select(:auto_track_topics_after_msecs).find_by(id: user_id).try(:auto_track_topics_after_msecs)
auto_track_after = UserOption.where(user_id: user_id).pluck(:auto_track_topics_after_msecs).first
auto_track_after ||= SiteSetting.default_other_auto_track_topics_after_msecs
if auto_track_after >= 0 && auto_track_after <= (attrs[:total_msecs_viewed].to_i || 0)
@@ -140,6 +140,31 @@ class TopicUser < ActiveRecord::Base
# Update the last read and the last seen post count, but only if it doesn't exist.
# This would be a lot easier if psql supported some kind of upsert
UPDATE_TOPIC_USER_SQL = "UPDATE topic_users
SET
last_read_post_number = GREATEST(:post_number, tu.last_read_post_number),
highest_seen_post_number = t.highest_post_number,
total_msecs_viewed = LEAST(tu.total_msecs_viewed + :msecs,86400000),
notification_level =
case when tu.notifications_reason_id is null and (tu.total_msecs_viewed + :msecs) >
coalesce(uo.auto_track_topics_after_msecs,:threshold) and
coalesce(uo.auto_track_topics_after_msecs, :threshold) >= 0 then
:tracking
else
tu.notification_level
end
FROM topic_users tu
join topics t on t.id = tu.topic_id
join users u on u.id = :user_id
join user_options uo on uo.user_id = :user_id
WHERE
tu.topic_id = topic_users.topic_id AND
tu.user_id = topic_users.user_id AND
tu.topic_id = :topic_id AND
tu.user_id = :user_id
RETURNING
topic_users.notification_level, tu.notification_level old_level, tu.last_read_post_number
"
def update_last_read(user, topic_id, post_number, msecs, opts={})
return if post_number.blank?
msecs = 0 if msecs.to_i < 0
@@ -160,31 +185,7 @@ class TopicUser < ActiveRecord::Base
# ... user visited the topic but did not read the posts
#
# 86400000 = 1 day
rows = exec_sql("UPDATE topic_users
SET
last_read_post_number = GREATEST(:post_number, tu.last_read_post_number),
highest_seen_post_number = t.highest_post_number,
total_msecs_viewed = LEAST(tu.total_msecs_viewed + :msecs,86400000),
notification_level =
case when tu.notifications_reason_id is null and (tu.total_msecs_viewed + :msecs) >
coalesce(u.auto_track_topics_after_msecs,:threshold) and
coalesce(u.auto_track_topics_after_msecs, :threshold) >= 0 then
:tracking
else
tu.notification_level
end
FROM topic_users tu
join topics t on t.id = tu.topic_id
join users u on u.id = :user_id
WHERE
tu.topic_id = topic_users.topic_id AND
tu.user_id = topic_users.user_id AND
tu.topic_id = :topic_id AND
tu.user_id = :user_id
RETURNING
topic_users.notification_level, tu.notification_level old_level, tu.last_read_post_number
",
args).values
rows = exec_sql(UPDATE_TOPIC_USER_SQL,args).values
if rows.length == 1
before = rows[0][1].to_i
@@ -206,7 +207,7 @@ class TopicUser < ActiveRecord::Base
if rows.length == 0
# The user read at least one post in a topic that they haven't viewed before.
args[:new_status] = notification_levels[:regular]
if (user.auto_track_topics_after_msecs || SiteSetting.default_other_auto_track_topics_after_msecs) == 0
if (user.user_option.auto_track_topics_after_msecs || SiteSetting.default_other_auto_track_topics_after_msecs) == 0
args[:new_status] = notification_levels[:tracking]
end
TopicTrackingState.publish_read(topic_id, post_number, user.id, args[:new_status])

View File

@@ -77,8 +77,6 @@ class User < ActiveRecord::Base
after_initialize :add_trust_level
before_create :set_default_user_preferences
after_create :create_email_token
after_create :create_user_stat
after_create :create_user_option
@@ -90,7 +88,6 @@ class User < ActiveRecord::Base
before_save :update_username_lower
before_save :ensure_password_is_hashed
after_save :update_tracked_topics
after_save :clear_global_notice_if_needed
after_save :refresh_avatar
after_save :badge_grant
@@ -626,20 +623,6 @@ class User < ActiveRecord::Base
Promotion.new(self).change_trust_level!(level, opts)
end
def treat_as_new_topic_start_date
duration = new_topic_duration_minutes || SiteSetting.default_other_new_topic_duration_minutes.to_i
times = [case duration
when User::NewTopicDuration::ALWAYS
created_at
when User::NewTopicDuration::LAST_VISIT
previous_visit_at || user_stat.new_since
else
duration.minutes.ago
end, user_stat.new_since, Time.at(SiteSetting.min_new_topics_time).to_datetime]
times.max
end
def readable_name
return "#{name} (#{username})" if name.present? && name != username
username
@@ -730,51 +713,6 @@ class User < ActiveRecord::Base
.exists?
end
def should_be_redirected_to_top
redirected_to_top.present?
end
def redirected_to_top
# redirect is enabled
return unless SiteSetting.redirect_users_to_top_page
# top must be in the top_menu
return unless SiteSetting.top_menu =~ /(^|\|)top(\||$)/i
# not enough topics
return unless period = SiteSetting.min_redirected_to_top_period
if !seen_before? || (trust_level == 0 && !redirected_to_top_yet?)
update_last_redirected_to_top!
return {
reason: I18n.t('redirected_to_top_reasons.new_user'),
period: period
}
elsif last_seen_at < 1.month.ago
update_last_redirected_to_top!
return {
reason: I18n.t('redirected_to_top_reasons.not_seen_in_a_month'),
period: period
}
end
# don't redirect to top
nil
end
def redirected_to_top_yet?
last_redirected_to_top_at.present?
end
def update_last_redirected_to_top!
key = "user:#{id}:update_last_redirected_to_top"
delay = SiteSetting.active_user_rate_limit_secs
# only update last_redirected_to_top_at once every minute
return unless $redis.setnx(key, "1")
$redis.expire(key, delay)
# delay the update
Jobs.enqueue_in(delay / 2, :update_top_redirection, user_id: self.id, redirected_at: Time.zone.now)
end
def refresh_avatar
return if @import_mode
@@ -880,11 +818,6 @@ class User < ActiveRecord::Base
end
end
def update_tracked_topics
return unless auto_track_topics_after_msecs_changed?
TrackedTopicsUpdater.new(id, auto_track_topics_after_msecs).call
end
def clear_global_notice_if_needed
if admin && SiteSetting.has_login_hint
SiteSetting.has_login_hint = false
@@ -970,14 +903,6 @@ class User < ActiveRecord::Base
end
end
def set_default_user_preferences
set_default_other_new_topic_duration_minutes
set_default_other_auto_track_topics_after_msecs
# needed, otherwise the callback chain is broken...
true
end
def set_default_categories_preferences
values = []
@@ -1025,12 +950,6 @@ class User < ActiveRecord::Base
end
%w{new_topic_duration_minutes auto_track_topics_after_msecs}.each do |s|
define_method("set_default_other_#{s}") do
self.send("#{s}=", SiteSetting.send("default_other_#{s}").to_i) if has_attribute?(s)
end
end
end
# == Schema Information
@@ -1054,7 +973,6 @@ end
# admin :boolean default(FALSE), not null
# last_emailed_at :datetime
# trust_level :integer not null
# email_private_messages :boolean default(TRUE)
# approved :boolean default(FALSE), not null
# approved_by_id :integer
# approved_at :datetime
@@ -1062,11 +980,9 @@ end
# suspended_at :datetime
# suspended_till :datetime
# date_of_birth :date
# auto_track_topics_after_msecs :integer
# views :integer default(0), not null
# flag_level :integer default(0), not null
# ip_address :inet
# new_topic_duration_minutes :integer
# moderator :boolean default(FALSE)
# blocked :boolean default(FALSE)
# title :string(255)
@@ -1074,7 +990,6 @@ end
# primary_group_id :integer
# locale :string(10)
# registration_ip_address :inet
# last_redirected_to_top_at :datetime
# trust_level_locked :boolean default(FALSE), not null
# staged :boolean default(FALSE), not null
#

View File

@@ -3,6 +3,8 @@ class UserOption < ActiveRecord::Base
belongs_to :user
before_create :set_defaults
after_save :update_tracked_topics
def set_defaults
self.email_always = SiteSetting.default_email_always
self.mailing_list_mode = SiteSetting.default_email_mailing_list_mode
@@ -16,6 +18,9 @@ class UserOption < ActiveRecord::Base
self.disable_jump_reply = SiteSetting.default_other_disable_jump_reply
self.edit_history_public = SiteSetting.default_other_edit_history_public
self.new_topic_duration_minutes = SiteSetting.default_other_new_topic_duration_minutes
self.auto_track_topics_after_msecs = SiteSetting.default_other_auto_track_topics_after_msecs
if SiteSetting.default_email_digest_frequency.to_i <= 0
self.email_digests = false
@@ -26,4 +31,70 @@ class UserOption < ActiveRecord::Base
true
end
def update_tracked_topics
return unless auto_track_topics_after_msecs_changed?
TrackedTopicsUpdater.new(id, auto_track_topics_after_msecs).call
end
def redirected_to_top_yet?
last_redirected_to_top_at.present?
end
def update_last_redirected_to_top!
key = "user:#{id}:update_last_redirected_to_top"
delay = SiteSetting.active_user_rate_limit_secs
# only update last_redirected_to_top_at once every minute
return unless $redis.setnx(key, "1")
$redis.expire(key, delay)
# delay the update
Jobs.enqueue_in(delay / 2, :update_top_redirection, user_id: self.id, redirected_at: Time.zone.now)
end
def should_be_redirected_to_top
redirected_to_top.present?
end
def redirected_to_top
# redirect is enabled
return unless SiteSetting.redirect_users_to_top_page
# top must be in the top_menu
return unless SiteSetting.top_menu =~ /(^|\|)top(\||$)/i
# not enough topics
return unless period = SiteSetting.min_redirected_to_top_period
if !user.seen_before? || (user.trust_level == 0 && !redirected_to_top_yet?)
update_last_redirected_to_top!
return {
reason: I18n.t('redirected_to_top_reasons.new_user'),
period: period
}
elsif user.last_seen_at < 1.month.ago
update_last_redirected_to_top!
return {
reason: I18n.t('redirected_to_top_reasons.not_seen_in_a_month'),
period: period
}
end
# don't redirect to top
nil
end
def treat_as_new_topic_start_date
duration = new_topic_duration_minutes || SiteSetting.default_other_new_topic_duration_minutes.to_i
times = [case duration
when User::NewTopicDuration::ALWAYS
user.created_at
when User::NewTopicDuration::LAST_VISIT
user.previous_visit_at || user.user_stat.new_since
else
duration.minutes.ago
end, user.user_stat.new_since, Time.at(SiteSetting.min_new_topics_time).to_datetime]
times.max
end
end

View File

@@ -70,6 +70,14 @@ class CurrentUserSerializer < BasicUserSerializer
object.user_option.automatically_unpin_topics
end
def should_be_redirected_to_top
object.user_option.should_be_redirected_to_top
end
def redirected_to_top
object.user_option.redirected_to_top
end
def site_flagged_posts_count
PostAction.flagged_posts_count
end
@@ -103,7 +111,7 @@ class CurrentUserSerializer < BasicUserSerializer
end
def include_redirected_to_top?
object.redirected_to_top.present?
object.user_option.redirected_to_top.present?
end
def custom_fields

View File

@@ -45,7 +45,7 @@ class ListableTopicSerializer < BasicTopicSerializer
def seen
return true if !scope || !scope.user
return true if object.user_data && !object.user_data.last_read_post_number.nil?
return true if object.created_at < scope.user.treat_as_new_topic_start_date
return true if object.created_at < scope.user.user_option.treat_as_new_topic_start_date
false
end

View File

@@ -11,10 +11,21 @@ class UserOptionSerializer < ApplicationSerializer
:disable_jump_reply,
:digest_after_days,
:automatically_unpin_topics,
:edit_history_public
:edit_history_public,
:auto_track_topics_after_msecs,
:new_topic_duration_minutes
def include_edit_history_public?
!SiteSetting.edit_history_visible_to_public
end
def auto_track_topics_after_msecs
object.auto_track_topics_after_msecs || SiteSetting.default_other_auto_track_topics_after_msecs
end
def new_topic_duration_minutes
object.new_topic_duration_minutes || SiteSetting.default_other_new_topic_duration_minutes
end
end

View File

@@ -82,8 +82,6 @@ class UserSerializer < BasicUserSerializer
:can_delete_all_posts
private_attributes :locale,
:auto_track_topics_after_msecs,
:new_topic_duration_minutes,
:muted_category_ids,
:tracked_category_ids,
:watched_category_ids,
@@ -253,14 +251,6 @@ class UserSerializer < BasicUserSerializer
### PRIVATE ATTRIBUTES
###
def auto_track_topics_after_msecs
object.auto_track_topics_after_msecs || SiteSetting.default_other_auto_track_topics_after_msecs
end
def new_topic_duration_minutes
object.new_topic_duration_minutes || SiteSetting.default_other_new_topic_duration_minutes
end
def muted_category_ids
CategoryUser.lookup(object, :muted).pluck(:category_id)
end

View File

@@ -18,7 +18,9 @@ class UserUpdater
:disable_jump_reply,
:edit_history_public,
:automatically_unpin_topics,
:digest_after_days
:digest_after_days,
:new_topic_duration_minutes,
:auto_track_topics_after_msecs
]
def initialize(actor, user)
@@ -38,14 +40,6 @@ class UserUpdater
user.name = attributes.fetch(:name) { user.name }
user.locale = attributes.fetch(:locale) { user.locale }
if attributes[:auto_track_topics_after_msecs]
user.auto_track_topics_after_msecs = attributes[:auto_track_topics_after_msecs].to_i
end
if attributes[:new_topic_duration_minutes]
user.new_topic_duration_minutes = attributes[:new_topic_duration_minutes].to_i
end
if guardian.can_grant_title?(user)
user.title = attributes.fetch(:title) { user.title }
end