Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStan Hu <stanhu@gmail.com>2018-05-16 09:06:55 +0300
committerStan Hu <stanhu@gmail.com>2018-05-16 18:27:48 +0300
commite38938b332ca751dfc5e784f242d620016e8ca43 (patch)
tree439a6672547ba9d50d995e7717df28ab6ded89fd /lib/gitlab/database
parent0288e2525fbe2bc226726b5289fc6e5b3a949da2 (diff)
Fix Error 500 viewing admin page due to statement timeouts
Uses PostgreSQL tuple estimates to provide a much faster yet approximate count. See https://wiki.postgresql.org/wiki/Slow_Counting for more details. We only use this fast method if the table has been analyzed or vacuumed within the last hour. Closes #46255
Diffstat (limited to 'lib/gitlab/database')
-rw-r--r--lib/gitlab/database/count.rb48
1 files changed, 48 insertions, 0 deletions
diff --git a/lib/gitlab/database/count.rb b/lib/gitlab/database/count.rb
new file mode 100644
index 00000000000..3374203960e
--- /dev/null
+++ b/lib/gitlab/database/count.rb
@@ -0,0 +1,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