summaryrefslogtreecommitdiff
path: root/lib/gitlab/database/count.rb
blob: 3374203960e7e769d0250e082bde2fcbdc5bc545 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# For large tables, PostgreSQL can take a long time to count rows due to MVCC.
# We can optimize this by using the reltuples count as described in https://wiki.postgresql.org/wiki/Slow_Counting.
module Gitlab
  module Database
    module Count
      CONNECTION_ERRORS =
        if defined?(PG)
          [
            ActionView::Template::Error,
            ActiveRecord::StatementInvalid,
            PG::Error
          ].freeze
        else
          [
            ActionView::Template::Error,
            ActiveRecord::StatementInvalid
          ].freeze
        end

      def self.approximate_count(model)
        return model.count unless Gitlab::Database.postgresql?

        execute_estimate_if_updated_recently(model) || model.count
      end

      def self.execute_estimate_if_updated_recently(model)
        ActiveRecord::Base.connection.select_value(postgresql_estimate_query(model)).to_i if reltuples_updated_recently?(model)
      rescue *CONNECTION_ERRORS
      end

      def self.reltuples_updated_recently?(model)
        time = "to_timestamp(#{1.hour.ago.to_i})"
        query = <<~SQL
          SELECT 1 FROM pg_stat_user_tables WHERE relname = '#{model.table_name}' AND
          (last_vacuum > #{time} OR last_autovacuum > #{time} OR last_analyze > #{time} OR last_autoanalyze > #{time})
        SQL

        ActiveRecord::Base.connection.select_all(query).count > 0
      rescue *CONNECTION_ERRORS
        false
      end

      def self.postgresql_estimate_query(model)
        "SELECT reltuples::bigint AS estimate FROM pg_class where relname = '#{model.table_name}'"
      end
    end
  end
end