Class: InternalId

Inherits:
ApplicationRecord show all
Includes:
Gitlab::Utils::StrongMemoize
Defined in:
app/models/internal_id.rb

Overview

An InternalId is a strictly monotone sequence of integers generated for a given scope and usage.

The monotone sequence may be broken if an ID is explicitly provided to `.track_greatest_and_save!` or `#track_greatest`.

For example, issues use their project to scope internal ids: In that sense, scope is “project” and usage is “issues”. Generated internal ids for an issue are unique per project.

See InternalId#usage enum for available usages.

In order to leverage InternalId for other usages, the idea is to

  • Add `usage` value to enum

  • (Optionally) add columns to `internal_ids` if needed for scope.

Defined Under Namespace

Classes: InternalIdGenerator

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Gitlab::Utils::StrongMemoize

#clear_memoization, #strong_memoize, #strong_memoized?

Methods inherited from ApplicationRecord

at_most, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, underscore, where_exists, with_fast_statement_timeout, without_order

Class Method Details

.flush_records!(filter) ⇒ Object

Flushing records is generally safe in a sense that those records are going to be re-created when needed.

A filter condition has to be provided to not accidentally flush records for all projects.

Raises:

  • (ArgumentError)

83
84
85
86
87
# File 'app/models/internal_id.rb', line 83

def flush_records!(filter)
  raise ArgumentError, "filter cannot be empty" if filter.blank?

  where(filter).delete_all
end

.generate_next(subject, scope, usage, init) ⇒ Object


68
69
70
71
# File 'app/models/internal_id.rb', line 68

def generate_next(subject, scope, usage, init)
  InternalIdGenerator.new(subject, scope, usage, init)
    .generate
end

.reset(subject, scope, usage, value) ⇒ Object


73
74
75
76
# File 'app/models/internal_id.rb', line 73

def reset(subject, scope, usage, value)
  InternalIdGenerator.new(subject, scope, usage)
    .reset(value)
end

.track_greatest(subject, scope, usage, new_value, init) ⇒ Object


63
64
65
66
# File 'app/models/internal_id.rb', line 63

def track_greatest(subject, scope, usage, new_value, init)
  InternalIdGenerator.new(subject, scope, usage, init)
    .track_greatest(new_value)
end

Instance Method Details

#increment_and_save!Object

Increments #last_value and saves the record

The operation locks the record and gathers a `ROW SHARE` lock (in PostgreSQL). As such, the increment is atomic and safe to be called concurrently.


32
33
34
# File 'app/models/internal_id.rb', line 32

def increment_and_save!
  update_and_save { self.last_value = (last_value || 0) + 1 }
end

#track_greatest_and_save!(new_value) ⇒ Object

Increments #last_value with new_value if it is greater than the current, and saves the record

The operation locks the record and gathers a `ROW SHARE` lock (in PostgreSQL). As such, the increment is atomic and safe to be called concurrently.


41
42
43
# File 'app/models/internal_id.rb', line 41

def track_greatest_and_save!(new_value)
  update_and_save { self.last_value = [last_value || 0, new_value].max }
end