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
THRESHOLD =

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.

100
LOG_THRESHOLD =
THRESHOLD * 1.5
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"'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeTransaction

Returns a new instance of Transaction.


45
46
47
48
49
# File 'lib/gitlab/query_limiting/transaction.rb', line 45

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


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

def self.current
  Thread.current[THREAD_KEY]
end

.runObject

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

Example:

transaction, retval = Transaction.run do
  10
end

retval # => 10

36
37
38
39
40
41
42
43
# File 'lib/gitlab/query_limiting/transaction.rb', line 36

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

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

Instance Method Details

#act_upon_resultsObject

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


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

def act_upon_results
  return unless threshold_exceeded?

  error = ThresholdExceededError.new(error_message)

  raise(error) if raise_error?
end

#enabled?Boolean

Returns:

  • (Boolean)

100
101
102
# File 'lib/gitlab/query_limiting/transaction.rb', line 100

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

#error_messageObject


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

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


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

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)

69
70
71
72
73
74
# File 'lib/gitlab/query_limiting/transaction.rb', line 69

def ignorable?(sql)
  return true if sql&.include?(GEO_NODES_LOAD)
  return true if sql&.include?(LICENSES_LOAD)

  false
end

#increment(sql = nil) ⇒ Object


60
61
62
# File 'lib/gitlab/query_limiting/transaction.rb', line 60

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

#raise_error?Boolean

Returns:

  • (Boolean)

82
83
84
# File 'lib/gitlab/query_limiting/transaction.rb', line 82

def raise_error?
  Rails.env.test?
end

#threshold_exceeded?Boolean

Returns:

  • (Boolean)

86
87
88
# File 'lib/gitlab/query_limiting/transaction.rb', line 86

def threshold_exceeded?
  count > THRESHOLD
end