SnowflakeId (snowflake_id v0.1.1)

Generates SnowflakeId's. This struct implements the Enumerable protocol, so you can use this with all the functions from Enum and Stream modules.

This implementation is functional and does not use any GenServer to store the state.

# override the time function for the test
iex> get_time = fn -> 1630163558780 end
iex> generator = SnowflakeId.new(1, 1, get_time: get_time)
iex> Enum.at(generator, 0)
6837401535245324288
iex> generator |> Enum.at(0) |> Integer.to_string(2)
"101111011100011010101000111100001011111000000100001000000000000"
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^xxxxxyyyyyzzzzzzzzzzzz
#^: timestamp
#x: machine_id
#y: node_id
#z: index

More realistic example:

iex> my_data =
...>   SnowflakeId.new(1, 1)
...>   |> Enum.take(10)
...>   |> Enum.map(fn id ->
...>     # insert your data in your database
...>     %{id: id, name: "my_id_#{id}"}
...>   end)
iex> length(my_data)
10

If you want to not use the Enumerable protocol you can do it yourself by:

# override the time function for the test
iex> get_time = fn -> 1630163558780 end
iex> generator = SnowflakeId.new(1, 1, get_time: get_time)
iex> SnowflakeId.format_id(generator)
6837401535245324288
iex> generator = SnowflakeId.next(generator)
iex> SnowflakeId.format_id(generator)
6837401535245324289

Small warning, because the timestamp only uses 41 bits and the default timestamp starts from 1970. The maximum time that can be stored is:

iex> bits = 0b11111111111111111111111111111111111111111
iex> DateTime.from_unix(bits, :millisecond)
{:ok, ~U[2039-09-07 15:47:35.551Z]}

You can mitigate this by using your own timestamp function or use the helper function:

# from the start of 2000
iex> from = ~U[2000-01-01T00:00:00Z]
iex> get_time = SnowflakeId.timestamp_factory(from)
iex> with_own_get_time = SnowflakeId.new(1, 1, get_time: get_time)
iex> default_get_time = SnowflakeId.new(1, 1)
iex> Enum.at(with_own_get_time, 0) < Enum.at(default_get_time, 0)
true

we can now use this longer:

iex> bits = 0b11111111111111111111111111111111111111111
iex> ~U[2000-01-01T00:00:00Z] |> DateTime.add(bits, :millisecond) |> DateTime.truncate(:second)
~U[2069-09-06 15:47:35Z]

Link to this section Summary

Functions

Format the struct to return a id

Update the struct to the next identifier

Default function to return the current timestamp in milliseconds

Helper function to generate a zero-arity function that returns the amount of milliseconds since epoch

Create a new SnowflakeId struct but with some extra checks. For more info check new/3

Link to this section Types

Specs

opts() :: [bulk: boolean(), get_time: (() -> integer())]

Specs

t() :: %SnowflakeId{
  bulk: boolean(),
  get_time: (() -> integer()),
  idx: integer(),
  last_time_millis: integer(),
  machine_id: integer(),
  node_id: integer()
}

Link to this section Functions

Link to this function

format_id(snowflake_id)

Specs

format_id(t()) :: integer()

Format the struct to return a id

Link to this function

new(machine_id, node_id, opts \\ [])

Specs

new(integer(), integer(), opts()) :: t()

Create a new SnowflakeId struct

options:

  • :get_time : (-> integer) override the function to get the current timestamp, this function should return the time since an epoch in milliseconds. by defaut it uses :os.system_time(:millisecond)

  • :bulk : boolean Don't check the time on every iteration, but only when the maximum amount of indexes is filled, which is 0..4095 This can be handy if you know you are going to generate a lot of indexes at once.

Specs

next(t()) :: t()

Update the struct to the next identifier

Link to this function

os_system_time()

Specs

os_system_time() :: integer()

Default function to return the current timestamp in milliseconds

Link to this function

timestamp_factory(epoch)

Specs

timestamp_factory(DateTime.t()) :: (() -> integer())

Helper function to generate a zero-arity function that returns the amount of milliseconds since epoch

Link to this function

try_new(machine_id, node_id, opts \\ [])

Specs

try_new(integer(), integer(), opts()) :: {:ok, t()} | {:error, atom()}

Create a new SnowflakeId struct but with some extra checks. For more info check new/3