Module: KSUID

Defined in:
lib/ksuid.rb,
lib/ksuid/type.rb,
lib/ksuid/utils.rb,
lib/ksuid/base62.rb,
lib/ksuid/version.rb,
lib/ksuid/prefixed.rb,
lib/ksuid/configuration.rb

Overview

The K-Sortable Unique IDentifier (KSUID)

Distributed systems require unique identifiers to track events throughout their subsystems. Many algorithms for generating unique identifiers, like the Snowflake ID system, require coordination with a central authority. This is an unacceptable constraint in the face of systems that run on client devices, yet we still need to be able to generate event identifiers and roughly sort them for processing.

The KSUID optimizes this problem into a roughly sortable identifier with a high possibility space to reduce the chance of collision. KSUID uses a 32-bit timestamp with second-level precision combined with 128 bytes of random data for the “payload”. The timestamp is based on the Unix epoch, but with its base shifted forward from 1970-01-01 00:00:00 UTC to 2014-05-13 16:532:20 UTC. This is to extend the useful life of the ID format to over 100 years.

Because KSUID timestamps use seconds as their unit of precision, they are unsuitable to tasks that require extreme levels of precision. If you need microsecond-level precision, a format like ULID may be more suitable for your use case.

KSUIDs are “roughly sorted”. Practically, this means that for any given event stream, there may be some events that are ordered in a slightly different way than they actually happened. There are two reasons for this. Firstly, the format is precise to the second. This means that two events that are generated in the same second will be sorted together, but the KSUID with the smaller payload value will be sorted first. Secondly, the format is generated on the client device using its clock, so KSUID is susceptible to clock shift as well. The result of sorting the identifiers is that they will be sorted into groups of identifiers that happened in the same second according to their generating device.

Examples:

Generate a new KSUID

KSUID.new

Generate a KSUID prefixed by ‘evt_`

KSUID.prefixed('evt_')

Parse a KSUID string that you have received

KSUID.from_base62('aWgEPTl1tmebfsQzFP4bxwgy80V')

Parse a KSUID byte string that you have received

KSUID.from_bytes(
  "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
)

Parse a KSUID byte array that you have received

KSUID.from_bytes(
  [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
   255, 255, 255, 255]
)

Defined Under Namespace

Modules: Base62, Utils Classes: Configuration, Prefixed, Type

Constant Summary collapse

EPOCH_TIME =

The shift in the Unix epoch time between the standard and the KSUID base

Returns:

  • (Integer)

    the number of seconds by which we shift the epoch

1_400_000_000
BYTES =

The number of bytes that are used to represent each part of a KSUID

Returns:

  • (Hash{Symbol => Integer})

    the map of data type to number of bytes

{ base62: 27, payload: 16, timestamp: 4, total: 20 }.freeze
STRING_LENGTH =

The number of characters in a base 62-encoded KSUID

Returns:

  • (Integer)
27
MAX_STRING_ENCODED =

The maximum KSUID as a base 62-encoded string.

Returns:

  • (String)
'aWgEPTl1tmebfsQzFP4bxwgy80V'
VERSION =

The version of the KSUID gem

Returns:

  • (String)
'1.0.0'

Class Method Summary collapse

Class Method Details

.call(ksuid) ⇒ KSUID::Type

Converts a KSUID-compatible value into an actual KSUID

Examples:

Converts a base 62 KSUID string into a KSUID

KSUID.call('15Ew2nYeRDscBipuJicYjl970D1')

Parameters:

  • ksuid (String, Array<Integer>, KSUID::Type)

    the KSUID-compatible value

Returns:

Raises:

  • (ArgumentError)

    if the value is not KSUID-compatible



95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/ksuid.rb', line 95

def self.call(ksuid)
  return unless ksuid

  case ksuid
  when KSUID::Prefixed then ksuid.to_ksuid
  when KSUID::Type then ksuid
  when Array then KSUID.from_bytes(ksuid)
  when String then cast_string(ksuid)
  else
    raise ArgumentError, "Cannot convert #{ksuid.inspect} to KSUID"
  end
end

.configKSUID::Configuration

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The configuration for creating new KSUIDs

Returns:



113
114
115
# File 'lib/ksuid.rb', line 113

def self.config
  @config ||= KSUID::Configuration.new
end

.configure {|config| ... } ⇒ KSUID::Configuration

Configures the KSUID gem by passing a block

Examples:

Override the random generator with a null data generator

KSUID.configure do |config|
  config.random_generator = -> { "\x00" * KSUID::BYTES[:payload] }
end

Override the random generator with the faster, but less secure, Random

KSUID.configure do |config|
  config.random_generator = -> { Random.new.bytes(KSUID::BYTES[:payload]) }
end

Yields:

Returns:



132
133
134
135
# File 'lib/ksuid.rb', line 132

def self.configure
  yield config if block_given?
  config
end

.from_base62(string) ⇒ KSUID::Type

Converts a base 62-encoded string into a KSUID

Examples:

Parse a KSUID string into an object

KSUID.from_base62('0vdbMgWkU6slGpLVCqEFwkkZvuW')

Parameters:

  • string (String)

    the base 62-encoded KSUID to convert into an object

Returns:

  • (KSUID::Type)

    the KSUID generated from the string



146
147
148
149
150
151
152
# File 'lib/ksuid.rb', line 146

def self.from_base62(string)
  string = string.rjust(STRING_LENGTH, Base62::CHARSET[0]) if string.length < STRING_LENGTH
  int = Base62.decode(string)
  bytes = Utils.int_to_bytes(int, 160)

  from_bytes(bytes)
end

.from_bytes(bytes) ⇒ KSUID::Type

Converts a byte string or byte array into a KSUID

Examples:

Parse a KSUID byte string into an object

KSUID.from_bytes("\x06\x83\xF7\x89\x04\x9C\xC2\x15\xC0\x99\xD4+xM\xBE\x994\e\xD7\x9C")

Parameters:

  • bytes (String, Array<Integer>)

    the byte string or array to convert into an object

Returns:



163
164
165
166
167
168
169
170
# File 'lib/ksuid.rb', line 163

def self.from_bytes(bytes)
  bytes = bytes.bytes if bytes.is_a?(String)

  timestamp = Utils.int_from_bytes(bytes.first(BYTES[:timestamp]))
  payload = Utils.byte_string_from_array(bytes.last(BYTES[:payload]))

  KSUID::Type.new(payload: payload, time: Time.at(timestamp + EPOCH_TIME))
end

.maxKSUID::Type

Generates the maximum KSUID as a KSUID type

Examples:

Generate the maximum KSUID

KSUID.max

Returns:

  • (KSUID::Type)

    the maximum KSUID in both timestamp and payload



180
181
182
# File 'lib/ksuid.rb', line 180

def self.max
  from_bytes([255] * BYTES[:total])
end

.new(payload: nil, time: Time.now) ⇒ KSUID::Type

Instantiates a new KSUID

Examples:

Generate a new KSUID for the current second

KSUID.new

Generate a new KSUID for a given timestamp

KSUID.new(time: Time.parse('2017-11-05 15:00:04 UTC'))

Parameters:

  • payload (String, Array<Integer>, nil) (defaults to: nil)

    the payload for the KSUID

  • time (Time) (defaults to: Time.now)

    the timestamp to use for the KSUID

Returns:



197
198
199
# File 'lib/ksuid.rb', line 197

def self.new(payload: nil, time: Time.now)
  Type.new(payload: payload, time: time)
end

.prefixed(prefix, payload: nil, time: Time.now) ⇒ KSUID::Prefixed

Instantiates a new Prefixed

Examples:

Generate a new prefixed KSUID for the current second

KSUID.prefixed('evt_')

Generate a new prefixed KSUID for a given timestamp

KSUID.prefixed('cus_', time: Time.parse('2022-08-16 10:36:00 UTC'))

Parameters:

  • prefix (String)

    the prefix to apply to the KSUID

  • payload (String, Array<Integer>, nil) (defaults to: nil)

    the payload for the KSUID

  • time (Time) (defaults to: Time.now)

    the timestamp to use for the KSUID

Returns:

Since:

  • 0.5.0



216
217
218
# File 'lib/ksuid.rb', line 216

def self.prefixed(prefix, payload: nil, time: Time.now)
  Prefixed.new(prefix, payload: payload, time: time)
end

.string(payload: nil, time: Time.now) ⇒ String

Generates a KSUID string

Examples:

Generate a new KSUID string for the current second

KSUID.string

Generate a new KSUID string for a given timestamp

KSUID.string(time: Time.parse('2017-11-05 15:00:04 UTC'))

Parameters:

  • payload (String, Array<Integer>, nil) (defaults to: nil)

    the payload for the KSUID string

  • time (Time) (defaults to: Time.now)

    the timestamp to use for the KSUID string

Returns:

  • (String)

    the generated string

Since:

  • 0.5.0



234
235
236
# File 'lib/ksuid.rb', line 234

def self.string(payload: nil, time: Time.now)
  Type.new(payload: payload, time: time).to_s
end