Class: Gitlab::Database::Partitioning::SlidingListStrategy

Inherits:
BaseStrategy
  • Object
show all
Defined in:
lib/gitlab/database/partitioning/sliding_list_strategy.rb

Direct Known Subclasses

CiSlidingListStrategy

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model, partitioning_key, next_partition_if:, detach_partition_if:, analyze_interval: nil) ⇒ SlidingListStrategy

Returns a new instance of SlidingListStrategy.



11
12
13
14
15
16
17
18
19
# File 'lib/gitlab/database/partitioning/sliding_list_strategy.rb', line 11

def initialize(model, partitioning_key, next_partition_if:, detach_partition_if:, analyze_interval: nil)
  @model = model
  @partitioning_key = partitioning_key
  @next_partition_if = next_partition_if
  @detach_partition_if = detach_partition_if
  @analyze_interval = analyze_interval

  ensure_partitioning_column_ignored_or_readonly!
end

Instance Attribute Details

#analyze_intervalObject (readonly)

Returns the value of attribute analyze_interval.



7
8
9
# File 'lib/gitlab/database/partitioning/sliding_list_strategy.rb', line 7

def analyze_interval
  @analyze_interval
end

#detach_partition_ifObject (readonly)

Returns the value of attribute detach_partition_if.



7
8
9
# File 'lib/gitlab/database/partitioning/sliding_list_strategy.rb', line 7

def detach_partition_if
  @detach_partition_if
end

#modelObject (readonly)

Returns the value of attribute model.



7
8
9
# File 'lib/gitlab/database/partitioning/sliding_list_strategy.rb', line 7

def model
  @model
end

#next_partition_ifObject (readonly)

Returns the value of attribute next_partition_if.



7
8
9
# File 'lib/gitlab/database/partitioning/sliding_list_strategy.rb', line 7

def next_partition_if
  @next_partition_if
end

#partitioning_keyObject (readonly)

Returns the value of attribute partitioning_key.



7
8
9
# File 'lib/gitlab/database/partitioning/sliding_list_strategy.rb', line 7

def partitioning_key
  @partitioning_key
end

Instance Method Details

#active_partitionObject



101
102
103
104
105
106
107
# File 'lib/gitlab/database/partitioning/sliding_list_strategy.rb', line 101

def active_partition
  ensure_connection_set

  # The current partitions list is sorted, so the last partition has the highest value
  # This is the only partition that receives inserts.
  current_partitions.last
end

#after_adding_partitionsObject

The partition manager is initialized with both connections and creates partitions in both databases, but here we change the default on the model’s connection, meaning that it will not respect the manager’s connection config so we need to check that it’s changing the default only when called with the model’s connection. Also since we prevent writes in the other database, we should not change the default there.



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/gitlab/database/partitioning/sliding_list_strategy.rb', line 82

def after_adding_partitions
  ensure_connection_set

  if different_connection_names?
    Gitlab::AppLogger.warn(
      message: 'Skipping changing column default because connections mismatch',
      event: :partition_manager_after_adding_partitions_connection_mismatch,
      model_connection_name: Gitlab::Database.db_config_name(model.connection),
      shared_connection_name: Gitlab::Database.db_config_name(Gitlab::Database::SharedModel.connection),
      table_name: model.table_name
    )

    return
  end

  active_value = active_partition.value
  model.connection.change_column_default(model.table_name, partitioning_key, active_value)
end

#current_partitionsObject



21
22
23
24
25
26
27
# File 'lib/gitlab/database/partitioning/sliding_list_strategy.rb', line 21

def current_partitions
  ensure_connection_set

  Gitlab::Database::PostgresPartition.for_parent_table(table_name).map do |partition|
    SingleNumericListPartition.from_sql(table_name, partition.name, partition.condition)
  end.sort
end

#extra_partitionsObject



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/gitlab/database/partitioning/sliding_list_strategy.rb', line 53

def extra_partitions
  ensure_connection_set

  possibly_extra = current_partitions[0...-1] # Never consider the most recent partition

  extra = possibly_extra.take_while { |p| detach_partition_if.call(p) }

  default_value = current_default_value
  if extra.any? { |p| p.value == default_value }
    Gitlab::AppLogger.error(
      message: "Inconsistent partition detected: partition with value #{current_default_value} should " \
                "not be deleted because it's used as the default value.",
      partition_number: current_default_value,
      table_name: model.table_name
    )

    extra = extra.reject { |p| p.value == default_value }
  end

  extra
end

#initial_partitionObject



41
42
43
44
45
# File 'lib/gitlab/database/partitioning/sliding_list_strategy.rb', line 41

def initial_partition
  ensure_connection_set

  SingleNumericListPartition.new(table_name, 1)
end

#missing_partitionsObject



29
30
31
32
33
34
35
36
37
38
39
# File 'lib/gitlab/database/partitioning/sliding_list_strategy.rb', line 29

def missing_partitions
  ensure_connection_set

  if no_partitions_exist?
    [initial_partition]
  elsif next_partition_if.call(active_partition)
    [next_partition]
  else
    []
  end
end

#next_partitionObject



47
48
49
50
51
# File 'lib/gitlab/database/partitioning/sliding_list_strategy.rb', line 47

def next_partition
  ensure_connection_set

  SingleNumericListPartition.new(table_name, active_partition.value + 1)
end

#no_partitions_exist?Boolean

Returns:

  • (Boolean)


109
110
111
112
113
# File 'lib/gitlab/database/partitioning/sliding_list_strategy.rb', line 109

def no_partitions_exist?
  ensure_connection_set

  current_partitions.empty?
end

#validate_and_fixObject



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/gitlab/database/partitioning/sliding_list_strategy.rb', line 115

def validate_and_fix
  ensure_connection_set

  if different_connection_names?
    Gitlab::AppLogger.warn(
      message: 'Skipping fixing column default because connections mismatch',
      event: :partition_manager_validate_and_fix_connection_mismatch,
      model_connection_name: Gitlab::Database.db_config_name(model.connection),
      shared_connection_name: Gitlab::Database.db_config_name(Gitlab::Database::SharedModel.connection),
      table_name: model.table_name
    )

    return
  end

  return if no_partitions_exist?

  old_default_value = current_default_value
  expected_default_value = active_partition.value

  if old_default_value != expected_default_value
    with_lock_retries do
      model.connection.execute("LOCK TABLE #{model.table_name} IN ACCESS EXCLUSIVE MODE")

      old_default_value = current_default_value
      expected_default_value = active_partition.value

      if old_default_value == expected_default_value
        Gitlab::AppLogger.warn(
          message: "Table partitions or partition key default value have been changed by another process",
          table_name: table_name,
          default_value: expected_default_value
        )
        raise ActiveRecord::Rollback
      end

      model.connection.change_column_default(model.table_name, partitioning_key, expected_default_value)
      Gitlab::AppLogger.warn(
        message: "Fixed default value of sliding_list_strategy partitioning_key",
        column: partitioning_key,
        table_name: table_name,
        connection_name: model.connection.pool.db_config.name,
        old_value: old_default_value,
        new_value: expected_default_value
      )
    end
  end
end