Commit 42d2168b authored by Andreas Brandl's avatar Andreas Brandl
Browse files

Extract ReltuplesCountStrategy.

parent fd7081c03856
...@@ -54,34 +54,51 @@ def self.approximate_counts(models) ...@@ -54,34 +54,51 @@ def self.approximate_counts(models)
# @param [Array] table names # @param [Array] table names
# @returns [Hash] Table name to count mapping (e.g. { 'projects' => 5, 'users' => 100 }) # @returns [Hash] Table name to count mapping (e.g. { 'projects' => 5, 'users' => 100 })
def self.reltuples_from_recently_updated(table_names) def self.reltuples_from_recently_updated(table_names)
query = postgresql_estimate_query(table_names) ReltuplesCountStrategy.new(table_names).count
rows = [] end
class ReltuplesCountStrategy
attr_reader :table_names
# Querying tuple stats only works on the primary. Due to load # @param [Array] table names
# balancing, we need to ensure this query hits the load balancer. The def initialize(table_names)
# easiest way to do this is to start a transaction. @table_names = table_names
ActiveRecord::Base.transaction do
rows = ActiveRecord::Base.connection.select_all(query)
end end
rows.each_with_object({}) { |row, data| data[row['table_name']] = row['estimate'].to_i } # Returns a hash of the table names that have recently updated tuples.
rescue *CONNECTION_ERRORS #
{} # @returns [Hash] Table name to count mapping (e.g. { 'projects' => 5, 'users' => 100 })
end def count
query = postgresql_estimate_query(table_names)
rows = []
# Generates the PostgreSQL query to return the tuples for tables # Querying tuple stats only works on the primary. Due to load
# that have been vacuumed or analyzed in the last hour. # easiest way to do this is to start a transaction.
# ActiveRecord::Base.transaction do
# @param [Array] table names rows = ActiveRecord::Base.connection.select_all(query)
# @returns [Hash] Table name to count mapping (e.g. { 'projects' => 5, 'users' => 100 }) end
def self.postgresql_estimate_query(table_names)
time = "to_timestamp(#{1.hour.ago.to_i})" rows.each_with_object({}) { |row, data| data[row['table_name']] = row['estimate'].to_i }
<<~SQL rescue *CONNECTION_ERRORS => e
{}
end
private
# Generates the PostgreSQL query to return the tuples for tables
# that have been vacuumed or analyzed in the last hour.
#
# @param [Array] table names
# @returns [Hash] Table name to count mapping (e.g. { 'projects' => 5, 'users' => 100 })
def postgresql_estimate_query(table_names)
time = "to_timestamp(#{1.hour.ago.to_i})"
<<~SQL
SELECT pg_class.relname AS table_name, reltuples::bigint AS estimate FROM pg_class SELECT pg_class.relname AS table_name, reltuples::bigint AS estimate FROM pg_class
LEFT JOIN pg_stat_user_tables ON pg_class.relname = pg_stat_user_tables.relname LEFT JOIN pg_stat_user_tables ON pg_class.relname = pg_stat_user_tables.relname
WHERE pg_class.relname IN (#{table_names.map { |table| "'#{table}'" }.join(',')}) WHERE pg_class.relname IN (#{table_names.map { |table| "'#{table}'" }.join(',')})
AND (last_vacuum > #{time} OR last_autovacuum > #{time} OR last_analyze > #{time} OR last_autoanalyze > #{time}) AND (last_vacuum > #{time} OR last_autovacuum > #{time} OR last_analyze > #{time} OR last_autoanalyze > #{time})
SQL SQL
end
end end
end end
end end
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
describe 'no permission' do describe 'no permission' do
it 'falls back to standard query' do it 'falls back to standard query' do
allow(described_class).to receive(:postgresql_estimate_query).and_raise(PG::InsufficientPrivilege) allow(ActiveRecord::Base).to receive(:transaction).and_raise(PG::InsufficientPrivilege)
expect(Project).to receive(:count).and_call_original expect(Project).to receive(:count).and_call_original
expect(Identity).to receive(:count).and_call_original expect(Identity).to receive(:count).and_call_original
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment