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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeTransaction

Returns a new instance of Transaction.



50
51
52
53
54
# File 'lib/gitlab/query_limiting/transaction.rb', line 50

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



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

def self.current
  Thread.current[THREAD_KEY]
end

.log_thresholdObject



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

def self.log_threshold
  threshold * 1.5
end

.runObject

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

Example:

transaction, retval = Transaction.run do
  10
end

retval # => 10


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

def self.run
  transaction = new
  Thread.current[THREAD_KEY] = transaction

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

.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.threshold
  100
end

Instance Method Details

#act_upon_resultsObject

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



57
58
59
60
61
62
63
# File 'lib/gitlab/query_limiting/transaction.rb', line 57

def act_upon_results
  return unless threshold_exceeded?

  error = ThresholdExceededError.new(error_message)

  raise(error) if raise_error?
end

#enabled?Boolean

Returns:

  • (Boolean)


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

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

#error_messageObject



97
98
99
100
101
102
103
104
105
# File 'lib/gitlab/query_limiting/transaction.rb', line 97

def error_message
  header = 'Too many SQL queries were executed'
  header = "#{header} in #{action}" if action
  msg = "a maximum of #{self.class.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 > self.class.log_threshold

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

#executed_sql(sql) ⇒ Object



83
84
85
86
87
# File 'lib/gitlab/query_limiting/transaction.rb', line 83

def executed_sql(sql)
  return if @count > self.class.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)


75
76
77
78
79
80
81
# File 'lib/gitlab/query_limiting/transaction.rb', line 75

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)

  false
end

#increment(sql = nil) ⇒ Object



65
66
67
# File 'lib/gitlab/query_limiting/transaction.rb', line 65

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

#raise_error?Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/gitlab/query_limiting/transaction.rb', line 89

def raise_error?
  Rails.env.test?
end

#threshold_exceeded?Boolean

Returns:

  • (Boolean)


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

def threshold_exceeded?
  count > self.class.threshold
end