Class: Gitlab::Metrics::Subscribers::ActiveRecord

Inherits:
ActiveSupport::Subscriber
  • Object
show all
Extended by:
Utils::StrongMemoize
Defined in:
lib/gitlab/metrics/subscribers/active_record.rb

Overview

Class for tracking the total query duration of a transaction.

Constant Summary collapse

DB_COUNTERS =
i[count write_count cached_count txn_count].freeze
SQL_COMMANDS_WITH_COMMENTS_REGEX =
%r{\A(?>/\*.*?\*/\s)?(?!.*[^\w'"](?:DELETE|UPDATE|INSERT INTO)[^\w'"])(?:WITH.*)?SELECT(?!.*(?:FOR UPDATE|FOR SHARE))}i
SQL_DURATION_BUCKET =
[0.05, 0.1, 0.25].freeze
TRANSACTION_DURATION_BUCKET =
[0.1, 0.25, 1].freeze
DB_LOAD_BALANCING_ROLES =
i[replica primary].freeze
DB_LOAD_BALANCING_COUNTERS =
i[txn_count count write_count cached_count wal_count wal_cached_count].freeze
DB_LOAD_BALANCING_DURATIONS =
i[txn_max_duration_s txn_duration_s duration_s].freeze
SQL_WAL_LOCATION_REGEX =
/pg_current_wal_insert_lsn\(\)::text|pg_last_wal_replay_lsn\(\)::text/

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.compose_metric_key(metric, db_role = nil, db_config_name = nil) ⇒ Object



251
252
253
# File 'lib/gitlab/metrics/subscribers/active_record.rb', line 251

def self.compose_metric_key(metric, db_role = nil, db_config_name = nil)
  [:db, db_role, db_config_name, metric].compact.join("_").to_sym
end

.db_counter_keysObject



189
190
191
# File 'lib/gitlab/metrics/subscribers/active_record.rb', line 189

def self.db_counter_keys
  DB_COUNTERS.map { |c| compose_metric_key(c) }
end

.db_counter_payloadObject



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/gitlab/metrics/subscribers/active_record.rb', line 81

def self.db_counter_payload
  return {} unless Gitlab::SafeRequestStore.active?

  {}.tap do |payload|
    if Feature.disabled?(:omit_aggregated_db_log_fields, :current_request, type: :ops)
      db_counter_keys.each do |key|
        payload[key] = Gitlab::SafeRequestStore[key].to_i
      end

      load_balancing_roles_metric_counter_keys.each do |counter|
        payload[counter] = ::Gitlab::SafeRequestStore[counter].to_i
      end

      load_balancing_roles_metric_duration_keys.each do |duration|
        payload[duration] = ::Gitlab::SafeRequestStore[duration].to_f.round(3)
      end
    end

    load_balancing_metric_counter_keys.each do |counter|
      payload[counter] = ::Gitlab::SafeRequestStore[counter].to_i
    end

    load_balancing_metric_duration_keys.each do |duration|
      payload[duration] = ::Gitlab::SafeRequestStore[duration].to_f.round(3)
    end
  end
end

.load_balancing_metric_counter_keysObject



193
194
195
196
197
# File 'lib/gitlab/metrics/subscribers/active_record.rb', line 193

def self.load_balancing_metric_counter_keys
  strong_memoize(:load_balancing_metric_counter_keys) do
    load_balancing_metric_keys(DB_LOAD_BALANCING_COUNTERS)
  end
end

.load_balancing_metric_duration_keysObject



199
200
201
202
203
# File 'lib/gitlab/metrics/subscribers/active_record.rb', line 199

def self.load_balancing_metric_duration_keys
  strong_memoize(:load_balancing_metric_duration_keys) do
    load_balancing_metric_keys(DB_LOAD_BALANCING_DURATIONS)
  end
end

.load_balancing_roles_metric_counter_keysObject



205
206
207
208
209
# File 'lib/gitlab/metrics/subscribers/active_record.rb', line 205

def self.load_balancing_roles_metric_counter_keys
  strong_memoize(:load_balancing_roles_metric_counter_keys) do
    load_balancing_roles_metric_keys(DB_LOAD_BALANCING_COUNTERS)
  end
end

.load_balancing_roles_metric_duration_keysObject



211
212
213
214
215
# File 'lib/gitlab/metrics/subscribers/active_record.rb', line 211

def self.load_balancing_roles_metric_duration_keys
  strong_memoize(:load_balancing_roles_metric_duration_keys) do
    load_balancing_roles_metric_keys(DB_LOAD_BALANCING_DURATIONS)
  end
end

.load_balancing_roles_metric_keys(metrics) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
# File 'lib/gitlab/metrics/subscribers/active_record.rb', line 217

def self.load_balancing_roles_metric_keys(metrics)
  counters = []

  metrics.each do |metric|
    DB_LOAD_BALANCING_ROLES.each do |role|
      counters << compose_metric_key(metric, role)
    end
  end

  counters
end

Instance Method Details

#sql(event) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/gitlab/metrics/subscribers/active_record.rb', line 47

def sql(event)
  # Mark this thread as requiring a database connection. This is used
  # by the Gitlab::Metrics::Samplers::ThreadsSampler to count threads
  # using a connection.
  Thread.current[:uses_db_connection] = true

  payload = event.payload
  return if ignored_query?(payload)

  db_role = ::Gitlab::Database::LoadBalancing.db_role_for_connection(payload[:connection])
  db_config_name = db_config_name(event.payload)
  cached_query = cached_query?(payload)

  # 1. Queries executed on replicas are always SELECT queries without the`FOR UPDATE` or the `FOR SHARE` modifier
  # 2. Cached query is always a SELECT without the`FOR UPDATE` or the `FOR SHARE` modifier
  # 3. Parse the query and check if it's SELECT
  select_sql_command = db_role == Gitlab::Database::LoadBalancing::ROLE_REPLICA ||
    cached_query ||
    select_sql_command?(payload)

  increment(:count, db_config_name: db_config_name)
  increment(:cached_count, db_config_name: db_config_name) if cached_query
  increment(:write_count, db_config_name: db_config_name) unless select_sql_command

  observe(:gitlab_sql_duration_seconds, event) do
    buckets SQL_DURATION_BUCKET
  end

  return if db_role.blank?

  increment_db_role_counters(db_role, payload, cached_query: cached_query, select_sql_command: select_sql_command)
  observe_db_role_duration(db_role, event)
end

#transaction(event) ⇒ Object

This event is published from ActiveRecordBaseTransactionMetrics and used to record a database transaction duration when calling ApplicationRecord.transaction {} block.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/gitlab/metrics/subscribers/active_record.rb', line 27

def transaction(event)
  observe(:gitlab_database_transaction_seconds, event) do
    buckets TRANSACTION_DURATION_BUCKET
  end

  return unless ::Gitlab::SafeRequestStore.active?

  # transactions uses a Gitlab::Database::Loadbalancing::ConnectionProxy which has a :unknown role
  # so we only track the overall and per-config txn duration
  db_config_name = db_config_name(event.payload)
  increment_log_key(compose_metric_key(:txn_count))
  increment_log_key(compose_metric_key(:txn_count, nil, db_config_name))

  duration = convert_ms_to_s(event.duration)
  increment_duration_key(compose_metric_key(:txn_duration_s), duration)
  increment_duration_key(compose_metric_key(:txn_duration_s, nil, db_config_name), duration)
  update_max_duration_key(:txn_max_duration_s, duration)
  update_max_duration_key(:txn_max_duration_s, duration, db_config_name)
end