Class: Gitlab::QueryLimiting::Transaction

Inherits:
Object
  • Object
show all
Defined in:
lib/gitlab/query_limiting/transaction.rb

Constant Summary collapse

THREAD_KEY =
:__gitlab_query_counts_transaction
ThresholdExceededError =

Error that is raised whenever exceeding the maximum number of queries.

Class.new(StandardError)
GEO_NODES_LOAD =
'SELECT 1 AS one FROM "geo_nodes" LIMIT 1'
LICENSES_LOAD =
'SELECT "licenses".* FROM "licenses" ORDER BY "licenses"."id"'
SCHEMA_INTROSPECTION =
%r{SELECT.*(FROM|JOIN) (pg_attribute|pg_class)}m
SAVEPOINT =
%r{(RELEASE )?SAVEPOINT}m
SET =
%r{^SET\s}m
SHOW =
%r{^SHOW\s}m

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeTransaction

Returns a new instance of Transaction.



53
54
55
56
57
# File 'lib/gitlab/query_limiting/transaction.rb', line 53

def initialize
  @action = nil
  @count = 0
  @sql_executed = []
end

Instance Attribute Details

#actionObject

The name of the action (e.g. ‘UsersController#show`) that is being executed.



12
13
14
# File 'lib/gitlab/query_limiting/transaction.rb', line 12

def action
  @action
end

#countObject

Returns the value of attribute count.



8
9
10
# File 'lib/gitlab/query_limiting/transaction.rb', line 8

def count
  @count
end

Class Method Details

.currentObject



29
30
31
# File 'lib/gitlab/query_limiting/transaction.rb', line 29

def self.current
  Thread.current[THREAD_KEY]
end

.default_thresholdObject

The maximum number of SQL queries that can be executed in a request. For the sake of keeping things simple we hardcode this value here, it’s not supposed to be changed very often anyway.



17
18
19
# File 'lib/gitlab/query_limiting/transaction.rb', line 17

def self.default_threshold
  100
end

.runObject

Starts a new transaction and returns it and the blocks’ return value.

Example:

transaction, retval = Transaction.run do
  10
end

retval # => 10


42
43
44
45
46
47
48
49
50
51
# File 'lib/gitlab/query_limiting/transaction.rb', line 42

def self.run
  previous_transaction = current

  transaction = new
  Thread.current[THREAD_KEY] = transaction

  [transaction, yield]
ensure
  Thread.current[THREAD_KEY] = previous_transaction
end

.thresholdObject

Deprecated, use default_threshold



22
23
24
# File 'lib/gitlab/query_limiting/transaction.rb', line 22

def self.threshold
  default_threshold
end

Instance Method Details

#act_upon_resultsObject

Sends a notification based on the number of executed SQL queries.



60
61
62
63
64
65
66
# File 'lib/gitlab/query_limiting/transaction.rb', line 60

def act_upon_results
  return unless threshold_exceeded?

  error = ThresholdExceededError.new(error_message)

  raise(error) if raise_error?
end

#enabled?Boolean

Returns:

  • (Boolean)


124
125
126
# File 'lib/gitlab/query_limiting/transaction.rb', line 124

def enabled?
  ::Gitlab::QueryLimiting.enabled?
end

#error_messageObject



114
115
116
117
118
119
120
121
122
# File 'lib/gitlab/query_limiting/transaction.rb', line 114

def error_message
  header = 'Too many SQL queries were executed'
  header = "#{header} in #{action}" if action
  msg = "a maximum of #{threshold} is allowed but #{count} SQL queries were executed"
  log = @sql_executed.each_with_index.map { |sql, i| "#{i}: #{sql}" }.join("\n").presence
  ellipsis = '...' if @count > log_threshold

  ["#{header}: #{msg}", log, ellipsis].compact.join("\n")
end

#executed_sql(sql) ⇒ Object



92
93
94
95
96
# File 'lib/gitlab/query_limiting/transaction.rb', line 92

def executed_sql(sql)
  return if @count > log_threshold || ignorable?(sql)

  @sql_executed << sql
end

#ignorable?(sql) ⇒ Boolean

queries can be safely ignored if they are amoritized in regular usage (i.e. only requested occasionally and otherwise cached).

Returns:

  • (Boolean)


81
82
83
84
85
86
87
88
89
90
# File 'lib/gitlab/query_limiting/transaction.rb', line 81

def ignorable?(sql)
  return true if sql&.include?(GEO_NODES_LOAD)
  return true if sql&.include?(LICENSES_LOAD)
  return true if SCHEMA_INTROSPECTION.match?(sql)
  return true if SAVEPOINT.match?(sql)
  return true if SET.match?(sql)
  return true if SHOW.match?(sql)

  false
end

#increment(sql = nil) ⇒ Object



68
69
70
# File 'lib/gitlab/query_limiting/transaction.rb', line 68

def increment(sql = nil)
  @count += 1 if enabled? && !ignorable?(sql)
end

#log_thresholdObject



106
107
108
# File 'lib/gitlab/query_limiting/transaction.rb', line 106

def log_threshold
  threshold * 1.5
end

#raise_error?Boolean

Returns:

  • (Boolean)


98
99
100
# File 'lib/gitlab/query_limiting/transaction.rb', line 98

def raise_error?
  Rails.env.test?
end

#thresholdObject



102
103
104
# File 'lib/gitlab/query_limiting/transaction.rb', line 102

def threshold
  ::Gitlab::QueryLimiting.threshold || self.class.threshold
end

#threshold_exceeded?Boolean

Returns:

  • (Boolean)


110
111
112
# File 'lib/gitlab/query_limiting/transaction.rb', line 110

def threshold_exceeded?
  count > threshold
end