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

log_large_in_lists.rb « query_analyzers « database « gitlab « lib - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 6cf82e0e3cd940ee111690c37f7df2585f76417c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
# frozen_string_literal: true

module Gitlab
  module Database
    module QueryAnalyzers
      # The purpose of this analyzer is to log query activity that contains `IN` clauses having more that 2500 items
      # as this type of query can cause performance degradation in the database.
      #
      # The feature flag should prevent sampling going above 1% or 0.01% of queries hitting
      # to avoid performance issues
      class LogLargeInLists < Base
        REGEX = /\bIN\s*\(([$?\d\s*,]*)\)+/i
        MIN_QUERY_SIZE = 10_000
        IN_SIZE_LIMIT = 2_500
        EVENT_NAMES = %w[load pluck].freeze

        EXCLUDE_FROM_TRACE = %w[
          lib/gitlab/database/query_analyzer.rb
          lib/gitlab/database/query_analyzers/log_large_in_lists.rb
        ].freeze

        class << self
          def enabled?
            ::Feature::FlipperFeature.table_exists? &&
              Feature.enabled?(:log_large_in_list_queries, type: :ops)
          end

          # Skips queries containing less than 10000 chars or any other events than +load+ and +pluck+
          def requires_tracking?(parsed)
            return false if parsed.sql.size < MIN_QUERY_SIZE

            EVENT_NAMES.include?(parsed.event_name)
          end

          def analyze(parsed)
            result = check_argument_size(parsed.sql)

            log(result, parsed.event_name) if result.any?
          end

          private

          def check_argument_size(sql)
            matches = sql.scan(REGEX).flatten

            return [] if matches.empty?

            matches.filter_map do |match|
              match_size = match.split(',').size

              match_size if match_size > IN_SIZE_LIMIT
            end
          end

          def log(result, event_name)
            Gitlab::AppLogger.warn(
              message: 'large_in_list_found',
              matches: result.size,
              event_name: event_name,
              in_list_size: result.join(', '),
              stacktrace: backtrace.first(5)
            )
          end

          def backtrace
            Gitlab::BacktraceCleaner.clean_backtrace(caller).reject do |line|
              EXCLUDE_FROM_TRACE.any? { |exclusion| line.include?(exclusion) }
            end
          end
        end
      end
    end
  end
end