PERF: store topic views in a topic view table

* cut down on storage of the work Topic, 3 times per row (in 2 indexes)
* only store one view per user per topic
* only store one view per ip per topic
This commit is contained in:
Sam
2014-08-04 19:07:55 +10:00
parent 0ccb8e17cb
commit cb0ecd9ff1
16 changed files with 117 additions and 79 deletions

View File

@ -115,7 +115,6 @@ end
# Table name: incoming_links
#
# id :integer not null, primary key
# topic_id :integer
# created_at :datetime
# user_id :integer
# ip_address :inet

View File

@ -24,7 +24,6 @@ end
# Table name: incoming_referers
#
# id :integer not null, primary key
# url :string(1000) not null
# path :string(1000) not null
# incoming_domain_id :integer not null
#

View File

@ -59,7 +59,7 @@ class LeaderRequirements
end
def topics_viewed_query
View.where(user_id: @user.id, parent_type: 'Topic').select('distinct(parent_id)')
TopicViewItem.where(user_id: @user.id).select('topic_id')
end
def topics_viewed

View File

@ -92,8 +92,8 @@ class TopTopic < ActiveRecord::Base
end
def self.update_views_count_for(period)
sql = "SELECT parent_id as topic_id, COUNT(*) AS count
FROM views
sql = "SELECT topic_id, COUNT(*) AS count
FROM topic_views
WHERE viewed_at >= :from
GROUP BY topic_id"

View File

@ -0,0 +1,50 @@
require 'ipaddr'
# awkward TopicView is taken
class TopicViewItem < ActiveRecord::Base
self.table_name = 'topic_views'
belongs_to :user
validates_presence_of :topic_id, :ip_address, :viewed_at
def self.add(topic_id, ip, user_id, at=nil)
# Only store a view once per day per thing per user per ip
redis_key = "view:#{topic_id}:#{Date.today.to_s}"
if user_id
redis_key << ":user-#{user_id}"
else
redis_key << ":ip-#{ip}"
end
if $redis.setnx(redis_key, "1")
$redis.expire(redis_key, 1.day.to_i)
TopicViewItem.transaction do
at ||= Date.today
TopicViewItem.create!(topic_id: topic_id, ip_address: ip, viewed_at: at, user_id: user_id)
# Update the views count in the parent, if it exists.
Topic.where(id: topic_id).update_all 'views = views + 1'
end
end
rescue ActiveRecord::RecordNotUnique
# don't care, skip
end
end
# == Schema Information
#
# Table name: topic_views
#
# topic_id :integer not null
# viewed_at :date not null
# user_id :integer
# ip_address :inet not null
#
# Indexes
#
# index_topic_views_on_topic_id_and_viewed_at (topic_id,viewed_at)
# index_topic_views_on_viewed_at_and_topic_id (viewed_at,topic_id)
# ip_address_topic_id_topic_views (ip_address,topic_id) UNIQUE
# user_id_topic_id_topic_views (user_id,topic_id) UNIQUE
#

View File

@ -86,7 +86,7 @@ class User < ActiveRecord::Base
before_destroy do
# These tables don't have primary keys, so destroying them with activerecord is tricky:
PostTiming.delete_all(user_id: self.id)
View.delete_all(user_id: self.id)
TopicViewItem.delete_all(user_id: self.id)
end
# Whether we need to be sending a system message after creation

View File

@ -13,10 +13,9 @@ class UserStat < ActiveRecord::Base
# Update denormalized topics_entered
exec_sql "UPDATE user_stats SET topics_entered = X.c
FROM
(SELECT v.user_id,
COUNT(DISTINCT parent_id) AS c
FROM views AS v
WHERE parent_type = 'Topic' AND v.user_id IN (
(SELECT v.user_id, COUNT(topic_id) AS c
FROM topic_views AS v
WHERE v.user_id IN (
SELECT u1.id FROM users u1 where u1.last_seen_at > :seen_at
)
GROUP BY v.user_id) AS X

View File

@ -1,51 +0,0 @@
require 'ipaddr'
class View < ActiveRecord::Base
belongs_to :parent, polymorphic: true
belongs_to :user
validates_presence_of :parent_type, :parent_id, :ip_address, :viewed_at
def self.create_for_parent(parent_class, parent_id, ip, user_id)
# Only store a view once per day per thing per user per ip
redis_key = "view:#{parent_class.name}:#{parent_id}:#{Date.today.to_s}"
if user_id
redis_key << ":user-#{user_id}"
else
redis_key << ":ip-#{ip}"
end
if $redis.setnx(redis_key, "1")
$redis.expire(redis_key, 1.day.to_i)
View.transaction do
View.create!(parent_id: parent_id, parent_type: parent_class.to_s, ip_address: ip, viewed_at: Date.today, user_id: user_id)
# Update the views count in the parent, if it exists.
if parent_class.columns_hash["views"]
parent_class.where(id: parent_id).update_all 'views = views + 1'
end
end
end
end
def self.create_for(parent, ip, user=nil)
user_id = user.id if user
create_for_parent(parent.class, parent.id, ip, user_id)
end
end
# == Schema Information
#
# Table name: views
#
# parent_id :integer not null
# parent_type :string(50) not null
# viewed_at :date not null
# user_id :integer
# ip_address :inet not null
#
# Indexes
#
# index_views_on_parent_id_and_parent_type (parent_id,parent_type)
# index_views_on_user_id_and_parent_type_and_parent_id (user_id,parent_type,parent_id)
#