Add counter caches for a large performance increase on API requests

This commit is contained in:
Eugen Rochko 2017-03-30 15:06:59 +02:00
parent cdbfbbc691
commit 96e58cf289
13 changed files with 41 additions and 22 deletions

View file

@ -20,7 +20,7 @@ class Api::V1::AccountsController < ApiController
accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.target_account_id] } @accounts = results.map { |f| accounts[f.target_account_id] }
set_account_counters_maps(@accounts) # set_account_counters_maps(@accounts)
next_path = following_api_v1_account_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) next_path = following_api_v1_account_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = following_api_v1_account_url(since_id: results.first.id) unless results.empty? prev_path = following_api_v1_account_url(since_id: results.first.id) unless results.empty?
@ -35,7 +35,7 @@ class Api::V1::AccountsController < ApiController
accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.account_id] } @accounts = results.map { |f| accounts[f.account_id] }
set_account_counters_maps(@accounts) # set_account_counters_maps(@accounts)
next_path = followers_api_v1_account_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) next_path = followers_api_v1_account_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = followers_api_v1_account_url(since_id: results.first.id) unless results.empty? prev_path = followers_api_v1_account_url(since_id: results.first.id) unless results.empty?
@ -52,8 +52,8 @@ class Api::V1::AccountsController < ApiController
@statuses = cache_collection(@statuses, Status) @statuses = cache_collection(@statuses, Status)
set_maps(@statuses) set_maps(@statuses)
set_counters_maps(@statuses) # set_counters_maps(@statuses)
set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq) # set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq)
next_path = statuses_api_v1_account_url(max_id: @statuses.last.id) unless @statuses.empty? next_path = statuses_api_v1_account_url(max_id: @statuses.last.id) unless @statuses.empty?
prev_path = statuses_api_v1_account_url(since_id: @statuses.first.id) unless @statuses.empty? prev_path = statuses_api_v1_account_url(since_id: @statuses.first.id) unless @statuses.empty?
@ -117,7 +117,7 @@ class Api::V1::AccountsController < ApiController
def search def search
@accounts = AccountSearchService.new.call(params[:q], limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:resolve] == 'true', current_account) @accounts = AccountSearchService.new.call(params[:q], limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:resolve] == 'true', current_account)
set_account_counters_maps(@accounts) unless @accounts.nil? # set_account_counters_maps(@accounts) unless @accounts.nil?
render action: :index render action: :index
end end

View file

@ -11,7 +11,7 @@ class Api::V1::BlocksController < ApiController
accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.target_account_id] }.compact @accounts = results.map { |f| accounts[f.target_account_id] }.compact
set_account_counters_maps(@accounts) # set_account_counters_maps(@accounts)
next_path = api_v1_blocks_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) next_path = api_v1_blocks_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = api_v1_blocks_url(since_id: results.first.id) unless results.empty? prev_path = api_v1_blocks_url(since_id: results.first.id) unless results.empty?

View file

@ -11,7 +11,7 @@ class Api::V1::FavouritesController < ApiController
@statuses = cache_collection(Status.where(id: results.map(&:status_id)), Status) @statuses = cache_collection(Status.where(id: results.map(&:status_id)), Status)
set_maps(@statuses) set_maps(@statuses)
set_counters_maps(@statuses) # set_counters_maps(@statuses)
next_path = api_v1_favourites_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_STATUSES_LIMIT) next_path = api_v1_favourites_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_STATUSES_LIMIT)
prev_path = api_v1_favourites_url(since_id: results.first.id) unless results.empty? prev_path = api_v1_favourites_url(since_id: results.first.id) unless results.empty?

View file

@ -9,7 +9,7 @@ class Api::V1::FollowRequestsController < ApiController
accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.account_id] } @accounts = results.map { |f| accounts[f.account_id] }
set_account_counters_maps(@accounts) # set_account_counters_maps(@accounts)
next_path = api_v1_follow_requests_url(max_id: results.last.id) if results.size == DEFAULT_ACCOUNTS_LIMIT next_path = api_v1_follow_requests_url(max_id: results.last.id) if results.size == DEFAULT_ACCOUNTS_LIMIT
prev_path = api_v1_follow_requests_url(since_id: results.first.id) unless results.empty? prev_path = api_v1_follow_requests_url(since_id: results.first.id) unless results.empty?

View file

@ -11,7 +11,7 @@ class Api::V1::MutesController < ApiController
accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.target_account_id] } @accounts = results.map { |f| accounts[f.target_account_id] }
set_account_counters_maps(@accounts) # set_account_counters_maps(@accounts)
next_path = api_v1_mutes_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) next_path = api_v1_mutes_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = api_v1_mutes_url(since_id: results.first.id) unless results.empty? prev_path = api_v1_mutes_url(since_id: results.first.id) unless results.empty?

View file

@ -14,8 +14,8 @@ class Api::V1::NotificationsController < ApiController
statuses = @notifications.select { |n| !n.target_status.nil? }.map(&:target_status) statuses = @notifications.select { |n| !n.target_status.nil? }.map(&:target_status)
set_maps(statuses) set_maps(statuses)
set_counters_maps(statuses) # set_counters_maps(statuses)
set_account_counters_maps(@notifications.map(&:from_account)) # set_account_counters_maps(@notifications.map(&:from_account))
next_path = api_v1_notifications_url(max_id: @notifications.last.id) unless @notifications.empty? next_path = api_v1_notifications_url(max_id: @notifications.last.id) unless @notifications.empty?
prev_path = api_v1_notifications_url(since_id: @notifications.first.id) unless @notifications.empty? prev_path = api_v1_notifications_url(since_id: @notifications.first.id) unless @notifications.empty?

View file

@ -23,7 +23,7 @@ class Api::V1::StatusesController < ApiController
statuses = [@status] + @context[:ancestors] + @context[:descendants] statuses = [@status] + @context[:ancestors] + @context[:descendants]
set_maps(statuses) set_maps(statuses)
set_counters_maps(statuses) # set_counters_maps(statuses)
end end
def card def card
@ -36,7 +36,7 @@ class Api::V1::StatusesController < ApiController
accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |r| accounts[r.account_id] } @accounts = results.map { |r| accounts[r.account_id] }
set_account_counters_maps(@accounts) # set_account_counters_maps(@accounts)
next_path = reblogged_by_api_v1_status_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) next_path = reblogged_by_api_v1_status_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = reblogged_by_api_v1_status_url(since_id: results.first.id) unless results.empty? prev_path = reblogged_by_api_v1_status_url(since_id: results.first.id) unless results.empty?
@ -51,7 +51,7 @@ class Api::V1::StatusesController < ApiController
accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.account_id] } @accounts = results.map { |f| accounts[f.account_id] }
set_account_counters_maps(@accounts) # set_account_counters_maps(@accounts)
next_path = favourited_by_api_v1_status_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) next_path = favourited_by_api_v1_status_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = favourited_by_api_v1_status_url(since_id: results.first.id) unless results.empty? prev_path = favourited_by_api_v1_status_url(since_id: results.first.id) unless results.empty?

View file

@ -4,7 +4,7 @@ class Favourite < ApplicationRecord
include Paginable include Paginable
belongs_to :account, inverse_of: :favourites belongs_to :account, inverse_of: :favourites
belongs_to :status, inverse_of: :favourites belongs_to :status, inverse_of: :favourites, counter_cache: true
has_one :notification, as: :activity, dependent: :destroy has_one :notification, as: :activity, dependent: :destroy

View file

@ -3,8 +3,8 @@
class Follow < ApplicationRecord class Follow < ApplicationRecord
include Paginable include Paginable
belongs_to :account belongs_to :account, counter_cache: :following_count
belongs_to :target_account, class_name: 'Account' belongs_to :target_account, class_name: 'Account', counter_cache: :followers_count
has_one :notification, as: :activity, dependent: :destroy has_one :notification, as: :activity, dependent: :destroy

View file

@ -10,11 +10,11 @@ class Status < ApplicationRecord
belongs_to :application, class_name: 'Doorkeeper::Application' belongs_to :application, class_name: 'Doorkeeper::Application'
belongs_to :account, inverse_of: :statuses belongs_to :account, inverse_of: :statuses, counter_cache: true
belongs_to :in_reply_to_account, foreign_key: 'in_reply_to_account_id', class_name: 'Account' belongs_to :in_reply_to_account, foreign_key: 'in_reply_to_account_id', class_name: 'Account'
belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies
belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs, counter_cache: :reblogs_count
has_many :favourites, inverse_of: :status, dependent: :destroy has_many :favourites, inverse_of: :status, dependent: :destroy
has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy

View file

@ -3,8 +3,8 @@ attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id, :sensitiv
node(:uri) { |status| TagManager.instance.uri_for(status) } node(:uri) { |status| TagManager.instance.uri_for(status) }
node(:content) { |status| Formatter.instance.format(status) } node(:content) { |status| Formatter.instance.format(status) }
node(:url) { |status| TagManager.instance.url_for(status) } node(:url) { |status| TagManager.instance.url_for(status) }
node(:reblogs_count) { |status| defined?(@reblogs_counts_map) ? (@reblogs_counts_map[status.id] || 0) : status.reblogs.count } node(:reblogs_count) { |status| defined?(@reblogs_counts_map) ? (@reblogs_counts_map[status.id] || 0) : (status.try(:reblogs_count) || status.reblogs.count) }
node(:favourites_count) { |status| defined?(@favourites_counts_map) ? (@favourites_counts_map[status.id] || 0) : status.favourites.count } node(:favourites_count) { |status| defined?(@favourites_counts_map) ? (@favourites_counts_map[status.id] || 0) : (status.try(:favourites_count) || status.favourites.count) }
child :application do child :application do
extends 'api/v1/apps/show' extends 'api/v1/apps/show'

View file

@ -0,0 +1,14 @@
class AddCounterCaches < ActiveRecord::Migration[5.0]
def change
add_column :statuses, :favourites_count, :integer
add_column :statuses, :reblogs_count, :integer
execute('update statuses set favourites_count = (select count(*) from favourites where favourites.status_id = statuses.id), reblogs_count = (select count(*) from statuses as reblogs where reblogs.reblog_of_id = statuses.id)')
add_column :accounts, :statuses_count, :integer
add_column :accounts, :followers_count, :integer
add_column :accounts, :following_count, :integer
execute('update accounts set statuses_count = (select count(*) from statuses where account_id = accounts.id), followers_count = (select count(*) from follows where target_account_id = accounts.id), following_count = (select count(*) from follows where account_id = accounts.id)')
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170322162804) do ActiveRecord::Schema.define(version: 20170330021336) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -44,6 +44,9 @@ ActiveRecord::Schema.define(version: 20170322162804) do
t.boolean "suspended", default: false, null: false t.boolean "suspended", default: false, null: false
t.boolean "locked", default: false, null: false t.boolean "locked", default: false, null: false
t.string "header_remote_url", default: "", null: false t.string "header_remote_url", default: "", null: false
t.integer "statuses_count"
t.integer "followers_count"
t.integer "following_count"
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", using: :btree t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", using: :btree
t.index ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true, using: :btree t.index ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true, using: :btree
@ -220,6 +223,8 @@ ActiveRecord::Schema.define(version: 20170322162804) do
t.integer "application_id" t.integer "application_id"
t.text "spoiler_text", default: "", null: false t.text "spoiler_text", default: "", null: false
t.boolean "reply", default: false t.boolean "reply", default: false
t.integer "favourites_count"
t.integer "reblogs_count"
t.index ["account_id"], name: "index_statuses_on_account_id", using: :btree t.index ["account_id"], name: "index_statuses_on_account_id", using: :btree
t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id", using: :btree t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id", using: :btree
t.index ["reblog_of_id"], name: "index_statuses_on_reblog_of_id", using: :btree t.index ["reblog_of_id"], name: "index_statuses_on_reblog_of_id", using: :btree