mirror of
https://github.com/discourse/discourse.git
synced 2025-06-03 19:39:30 +08:00
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:
@ -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
|
||||
|
@ -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
|
||||
#
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
||||
|
50
app/models/topic_view_item.rb
Normal file
50
app/models/topic_view_item.rb
Normal 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
|
||||
#
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
#
|
Reference in New Issue
Block a user