Skip to content
This repository was archived by the owner on Apr 3, 2021. It is now read-only.

Commit 5bf26ec

Browse files
committed
large refactor, ship a few columns from the user table into user_stats
1 parent 4613006 commit 5bf26ec

29 files changed

+354
-251
lines changed

app/jobs/scheduled/periodical_updates.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def execute(args)
1717
CategoryFeaturedTopic.feature_topics
1818

1919
# Update view counts for users
20-
User.update_view_counts
20+
UserStat.update_view_counts
2121

2222
# Update the scores of posts
2323
ScoreCalculator.new.calculate

app/models/post_timing.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def self.destroy_for(user_id, topic_id)
6161

6262

6363
def self.process_timings(current_user, topic_id, topic_time, timings)
64-
current_user.update_time_read!
64+
current_user.user_stat.update_time_read!
6565

6666
highest_seen = 1
6767
timings.each do |post_number, time|

app/models/site_customization.rb

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -191,18 +191,21 @@ def mobile_stylesheet_link_tag
191191
#
192192
# Table name: site_customizations
193193
#
194-
# id :integer not null, primary key
195-
# name :string(255) not null
196-
# stylesheet :text
197-
# header :text
198-
# position :integer not null
199-
# user_id :integer not null
200-
# enabled :boolean not null
201-
# key :string(255) not null
202-
# created_at :datetime not null
203-
# updated_at :datetime not null
204-
# override_default_style :boolean default(FALSE), not null
205-
# stylesheet_baked :text default(""), not null
194+
# id :integer not null, primary key
195+
# name :string(255) not null
196+
# stylesheet :text
197+
# header :text
198+
# position :integer not null
199+
# user_id :integer not null
200+
# enabled :boolean not null
201+
# key :string(255) not null
202+
# created_at :datetime not null
203+
# updated_at :datetime not null
204+
# override_default_style :boolean default(FALSE), not null
205+
# stylesheet_baked :text default(""), not null
206+
# mobile_stylesheet :text
207+
# mobile_header :text
208+
# mobile_stylesheet_baked :text
206209
#
207210
# Indexes
208211
#

app/models/topic.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,9 @@ def update_category_topic_count_by(num)
663663
#
664664
# Indexes
665665
#
666-
# idx_topics_user_id_deleted_at (user_id)
667-
# index_forum_threads_on_bumped_at (bumped_at)
666+
# idx_topics_user_id_deleted_at (user_id)
667+
# index_forum_threads_on_bumped_at (bumped_at)
668+
# index_topics_on_deleted_at_and_visible_and_archetype_and_id (deleted_at,visible,archetype,id)
669+
# index_topics_on_id_and_deleted_at (id,deleted_at)
668670
#
669671

app/models/upload.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,9 @@ def self.get_from_url(url)
106106
#
107107
# Indexes
108108
#
109-
# index_uploads_on_sha1 (sha1) UNIQUE
110-
# index_uploads_on_url (url)
111-
# index_uploads_on_user_id (user_id)
109+
# index_uploads_on_id_and_url (id,url)
110+
# index_uploads_on_sha1 (sha1) UNIQUE
111+
# index_uploads_on_url (url)
112+
# index_uploads_on_user_id (user_id)
112113
#
113114

app/models/user.rb

Lines changed: 3 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ def has_visit_record?(date)
259259

260260
def update_visit_record!(date)
261261
unless has_visit_record?(date)
262-
update_column(:days_visited, days_visited + 1)
262+
user_stat.update_column(:days_visited, user_stat.days_visited + 1)
263263
user_visits.create!(visited_at: date)
264264
end
265265
end
@@ -316,42 +316,6 @@ def avatar_template
316316
uploaded_avatar_path || User.gravatar_template(email)
317317
end
318318

319-
# Updates the denormalized view counts for all users
320-
def self.update_view_counts
321-
322-
# NOTE: we only update the counts for users we have seen in the last hour
323-
# this avoids a very expensive query that may run on the entire user base
324-
# we also ensure we only touch the table if data changes
325-
326-
# Update denormalized topics_entered
327-
exec_sql "UPDATE users SET topics_entered = X.c
328-
FROM
329-
(SELECT v.user_id,
330-
COUNT(DISTINCT parent_id) AS c
331-
FROM views AS v
332-
WHERE parent_type = 'Topic' AND v.user_id IN (
333-
SELECT u1.id FROM users u1 where u1.last_seen_at > :seen_at
334-
)
335-
GROUP BY v.user_id) AS X
336-
WHERE
337-
X.user_id = users.id AND
338-
X.c <> topics_entered
339-
", seen_at: 1.hour.ago
340-
341-
# Update denormalzied posts_read_count
342-
exec_sql "UPDATE users SET posts_read_count = X.c
343-
FROM
344-
(SELECT pt.user_id,
345-
COUNT(*) AS c
346-
FROM post_timings AS pt
347-
WHERE pt.user_id IN (
348-
SELECT u1.id FROM users u1 where u1.last_seen_at > :seen_at
349-
)
350-
GROUP BY pt.user_id) AS X
351-
WHERE X.user_id = users.id AND
352-
X.c <> posts_read_count
353-
", seen_at: 1.hour.ago
354-
end
355319

356320
# The following count methods are somewhat slow - definitely don't use them in a loop.
357321
# They might need to be denormalized
@@ -458,20 +422,6 @@ def treat_as_new_topic_start_date
458422
end
459423
end
460424

461-
MAX_TIME_READ_DIFF = 100
462-
# attempt to add total read time to user based on previous time this was called
463-
def update_time_read!
464-
last_seen_key = "user-last-seen:#{id}"
465-
last_seen = $redis.get(last_seen_key)
466-
if last_seen.present?
467-
diff = (Time.now.to_f - last_seen.to_f).round
468-
if diff > 0 && diff < MAX_TIME_READ_DIFF
469-
User.where(id: id, time_read: time_read).update_all ["time_read = time_read + ?", diff]
470-
end
471-
end
472-
$redis.set(last_seen_key, Time.now.to_f)
473-
end
474-
475425
def readable_name
476426
return "#{name} (#{username})" if name.present? && name != username
477427
username
@@ -486,18 +436,6 @@ def self.count_by_signup_date(sinceDaysAgo=30)
486436
where('created_at > ?', sinceDaysAgo.days.ago).group('date(created_at)').order('date(created_at)').count
487437
end
488438

489-
def update_topic_reply_count
490-
self.topic_reply_count =
491-
Topic
492-
.where(['id in (
493-
SELECT topic_id FROM posts p
494-
JOIN topics t2 ON t2.id = p.topic_id
495-
WHERE p.deleted_at IS NULL AND
496-
t2.user_id <> p.user_id AND
497-
p.user_id = ?
498-
)', self.id])
499-
.count
500-
end
501439

502440
def secure_category_ids
503441
cats = self.staff? ? Category.where(read_restricted: true) : secure_categories.references(:categories)
@@ -549,7 +487,7 @@ def update_tracked_topics
549487

550488
def create_user_stat
551489
stat = UserStat.new
552-
stat.user_id = self.id
490+
stat.user_id = id
553491
stat.save!
554492
end
555493

@@ -647,8 +585,6 @@ def set_default_email_digest
647585
# approved :boolean default(FALSE), not null
648586
# approved_by_id :integer
649587
# approved_at :datetime
650-
# topics_entered :integer default(0), not null
651-
# posts_read_count :integer default(0), not null
652588
# digest_after_days :integer
653589
# previous_visit_at :datetime
654590
# banned_at :datetime
@@ -657,22 +593,18 @@ def set_default_email_digest
657593
# auto_track_topics_after_msecs :integer
658594
# views :integer default(0), not null
659595
# flag_level :integer default(0), not null
660-
# time_read :integer default(0), not null
661-
# days_visited :integer default(0), not null
662596
# ip_address :string
663597
# new_topic_duration_minutes :integer
664598
# external_links_in_new_tab :boolean default(FALSE), not null
665599
# enable_quoting :boolean default(TRUE), not null
666600
# moderator :boolean default(FALSE)
667-
# likes_given :integer default(0), not null
668-
# likes_received :integer default(0), not null
669-
# topic_reply_count :integer default(0), not null
670601
# blocked :boolean default(FALSE)
671602
# dynamic_favicon :boolean default(FALSE), not null
672603
# title :string(255)
673604
# use_uploaded_avatar :boolean default(FALSE)
674605
# uploaded_avatar_template :string(255)
675606
# uploaded_avatar_id :integer
607+
# email_always :boolean default(FALSE), not null
676608
#
677609
# Indexes
678610
#

app/models/user_action.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,9 @@ def self.ensure_consistency!
254254

255255
def self.update_like_count(user_id, action_type, delta)
256256
if action_type == LIKE
257-
User.where(id: user_id).update_all("likes_given = likes_given + #{delta.to_i}")
257+
UserStat.where(user_id: user_id).update_all("likes_given = likes_given + #{delta.to_i}")
258258
elsif action_type == WAS_LIKED
259-
User.where(id: user_id).update_all("likes_received = likes_received + #{delta.to_i}")
259+
UserStat.where(user_id: user_id).update_all("likes_received = likes_received + #{delta.to_i}")
260260
end
261261
end
262262

app/models/user_history.rb

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ def previous_value_is_json?
6666

6767
# == Schema Information
6868
#
69-
# Table name: staff_action_logs
69+
# Table name: user_histories
7070
#
7171
# id :integer not null, primary key
7272
# action :integer not null
73-
# staff_user_id :integer not null
73+
# acting_user_id :integer
7474
# target_user_id :integer
7575
# details :text
7676
# created_at :datetime not null
@@ -81,12 +81,13 @@ def previous_value_is_json?
8181
# subject :text
8282
# previous_value :text
8383
# new_value :text
84+
# topic_id :integer
8485
#
8586
# Indexes
8687
#
87-
# index_staff_action_logs_on_action_and_id (action,id)
88-
# index_staff_action_logs_on_staff_user_id_and_id (staff_user_id,id)
89-
# index_staff_action_logs_on_subject_and_id (subject,id)
90-
# index_staff_action_logs_on_target_user_id_and_id (target_user_id,id)
88+
# index_staff_action_logs_on_action_and_id (action,id)
89+
# index_staff_action_logs_on_subject_and_id (subject,id)
90+
# index_staff_action_logs_on_target_user_id_and_id (target_user_id,id)
91+
# index_user_histories_on_acting_user_id_and_action_and_id (acting_user_id,action,id)
9192
#
9293

app/models/user_stat.rb

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,84 @@ class UserStat < ActiveRecord::Base
22

33
belongs_to :user
44

5+
# Updates the denormalized view counts for all users
6+
def self.update_view_counts
7+
8+
# NOTE: we only update the counts for users we have seen in the last hour
9+
# this avoids a very expensive query that may run on the entire user base
10+
# we also ensure we only touch the table if data changes
11+
12+
# Update denormalized topics_entered
13+
exec_sql "UPDATE user_stats SET topics_entered = X.c
14+
FROM
15+
(SELECT v.user_id,
16+
COUNT(DISTINCT parent_id) AS c
17+
FROM views AS v
18+
WHERE parent_type = 'Topic' AND v.user_id IN (
19+
SELECT u1.id FROM users u1 where u1.last_seen_at > :seen_at
20+
)
21+
GROUP BY v.user_id) AS X
22+
WHERE
23+
X.user_id = user_stats.user_id AND
24+
X.c <> topics_entered
25+
", seen_at: 1.hour.ago
26+
27+
# Update denormalzied posts_read_count
28+
exec_sql "UPDATE user_stats SET posts_read_count = X.c
29+
FROM
30+
(SELECT pt.user_id,
31+
COUNT(*) AS c
32+
FROM post_timings AS pt
33+
WHERE pt.user_id IN (
34+
SELECT u1.id FROM users u1 where u1.last_seen_at > :seen_at
35+
)
36+
GROUP BY pt.user_id) AS X
37+
WHERE X.user_id = user_stats.user_id AND
38+
X.c <> posts_read_count
39+
", seen_at: 1.hour.ago
40+
end
41+
42+
def update_topic_reply_count
43+
self.topic_reply_count =
44+
Topic
45+
.where(['id in (
46+
SELECT topic_id FROM posts p
47+
JOIN topics t2 ON t2.id = p.topic_id
48+
WHERE p.deleted_at IS NULL AND
49+
t2.user_id <> p.user_id AND
50+
p.user_id = ?
51+
)', self.user_id])
52+
.count
53+
end
54+
55+
MAX_TIME_READ_DIFF = 100
56+
# attempt to add total read time to user based on previous time this was called
57+
def update_time_read!
58+
last_seen_key = "user-last-seen:#{id}"
59+
last_seen = $redis.get(last_seen_key)
60+
if last_seen.present?
61+
diff = (Time.now.to_f - last_seen.to_f).round
62+
if diff > 0 && diff < MAX_TIME_READ_DIFF
63+
UserStat.where(user_id: id, time_read: time_read).update_all ["time_read = time_read + ?", diff]
64+
end
65+
end
66+
$redis.set(last_seen_key, Time.now.to_f)
67+
end
68+
569
end
70+
71+
# == Schema Information
72+
#
73+
# Table name: user_stats
74+
#
75+
# user_id :integer not null, primary key
76+
# has_custom_avatar :boolean default(FALSE), not null
77+
# topics_entered :integer default(0), not null
78+
# time_read :integer default(0), not null
79+
# days_visited :integer default(0), not null
80+
# posts_read_count :integer default(0), not null
81+
# likes_given :integer default(0), not null
82+
# likes_received :integer default(0), not null
83+
# topic_reply_count :integer default(0), not null
84+
#
85+

app/models/user_visit.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ def self.by_day(sinceDaysAgo=30)
77

88
def self.ensure_consistency!
99
exec_sql <<SQL
10-
UPDATE users u set days_visited =
10+
UPDATE user_stats u set days_visited =
1111
(
12-
SELECT COUNT(*) FROM user_visits v WHERE v.user_id = u.id
12+
SELECT COUNT(*) FROM user_visits v WHERE v.user_id = u.user_id
1313
)
1414
WHERE days_visited <>
1515
(
16-
SELECT COUNT(*) FROM user_visits v WHERE v.user_id = u.id
16+
SELECT COUNT(*) FROM user_visits v WHERE v.user_id = u.user_id
1717
)
1818
SQL
1919
end

0 commit comments

Comments
 (0)