Aphora v1.0.0 Aphora View Source

Build Status Hex.pm Version

Aphora is a system do distributedly generate unique id/0 without any coordination. It is similiar to Snowflake, but instead of a non_neg_integer/0 it generates ASCII sortable a String.t/0.

It uses a 72 Bit long binary() which get encoded to a 12 characters long id/0.
An id/0 is composed of:

  • 45 Bit timestamp/0 with millisecond precision. It gives up to 1115 years with a custom epoch.
  • 5 Bit datacenter/0 allows up to 32 different datacenters.
  • 10 Bit worker/0 allows up to 1_024 different processes/workers/machines within a datacenter.
  • 12 Bit counter/0 allows for 4_096 unique id/0s to be generated each millisecond. Aphora also protects against rollovers to happen within the same millisecond.

Just like Snowflake, Aphora protects from non-monotonic clocks, by refusing to generate timestamp/0 until it gets provided with a time equals or after the one used in the last generated id/0.

Installation

The package can be installed by adding :aphora to your list of dependencies in mix.exs:

def deps do
[
  {:aphora, "~> 1.0.0"}
]
end

The config file is located at /config/config.exs. If you run Aphora in more than one process, you'll need to adjust the worker/0 and datacenter/0.

config :aphora,
# Value between 0..31, unique between all [`datacenter/0`](#t:datacenter/0).
datacenter: 0,
# Value between 0..1_024, unique between all [`worker/0`](#t:worker/0) within a [`datacenter/0`](#t:datacenter/0).
worker: 0,
# Shouldn't be changed once the first [`id/0`](#t:id/0) is generated. Default: 2019-01-01 00:00:00.000000Z in milliseconds.
epoch: 1_546_300_800_000

The docs can be found at Hex.

Usage

The usage of Aphora is quite straightforward after setting up the config.exs correctly, as all important methods are within the Aphora module.
To generate a new id/0, you use Aphora.new_id/0.

Aphora.new_id()
# {:ok, "0eHHz1--abHr"}

And if you want to find out when a id/0 was generated, you use Aphora.to_timestamp/2.

Aphora.to_timestamp("0eHHz1--abHr", :relative)
# 912_988_800_000

License

Aphora is licensed under the Apache License 2.0.

Link to this section Summary

Types

A counter/0 is a 12 Bits long identifier, which by default has a value of 0. It only increments, if two id/0 are to be generated within the same timestamp/0.

This can be any non-negative integer within the span of 0..31 and represents the datacenter/0 in which the worker/0 resides in.

This can be any non-negative integer and represents the epoch/0 with which the timestamp/0 is generated.

A id/0 is a 72 Bit long unique & sortable identifier, which doesn't require any centralised coordination.

A timestamp/0 is a positive integer, which is equals to the time in which it was generated within Aphora.Worker.timestamp/1. It consists of System.os_time/1 minus the epoch/0.

This can be any non-negative integer within the span of 0..1_023 and represents the unique worker/0 within a datacenter/0.

Functions

Creates a new unique id/0.

Called when an application is started.

Extracts the timestamp/0 out of the given id/0.

Link to this section Types

Link to this type

counter() View Source (since 0.2.0)
counter() :: non_neg_integer()

A counter/0 is a 12 Bits long identifier, which by default has a value of 0. It only increments, if two id/0 are to be generated within the same timestamp/0.

This allows for a total of 4_096 id/0 to be generated within the same timestamp/0 on the same unique combination of worker/0 and datacenter/0.

Link to this type

datacenter() View Source (since 0.1.0)
datacenter() :: non_neg_integer()

This can be any non-negative integer within the span of 0..31 and represents the datacenter/0 in which the worker/0 resides in.

Link to this type

epoch() View Source (since 0.1.0)
epoch() :: non_neg_integer()

This can be any non-negative integer and represents the epoch/0 with which the timestamp/0 is generated.

epoch/0 should never be changed after deploying id/0 to prevent duplicates.

Link to this type

id() View Source (since 0.1.0)
id() :: String.t()

A id/0 is a 72 Bit long unique & sortable identifier, which doesn't require any centralised coordination.

It consists out of:

Link to this type

timestamp() View Source (since 0.1.0)
timestamp() :: non_neg_integer()

A timestamp/0 is a positive integer, which is equals to the time in which it was generated within Aphora.Worker.timestamp/1. It consists of System.os_time/1 minus the epoch/0.

It has a total size of 45 Bits within the Aphora.id/0 and a millisecond precision.
This allows for 35_184_372_088_832 millisecond or around 1115 years of millisecond precision timestamp/0.

Link to this type

worker() View Source (since 0.1.0)
worker() :: non_neg_integer()

This can be any non-negative integer within the span of 0..1_023 and represents the unique worker/0 within a datacenter/0.

A worker/0 should never be assigned twice with the same value within the same datacenter/0.

Link to this section Functions

Link to this function

new_id() View Source (since 0.1.0)
new_id() :: {:ok, id()} | {:error, atom()}

Creates a new unique id/0.

Please ensure, that the config.exs is setup correctly, to avoid a duplicate id/0 to be generated on different workers/machines. Aphora relies on unique worker/0 and datacenter/0 to be set, to avoid duplicate IDs to be generated.

Please also make sure to use smeared time, to prevent reverted ticks. These won't be generated as Aphora has safeguards against this, but will return an {:error, :reverted_tick} instead.

Examples

iex> Aphora.new_id()
{:ok, "-0dqbzfF----"}

# If the timestamp ticked backwards.
iex> Aphora.new_id()
{:error, :reverted_tick}

Called when an application is started.

This function is called when an application is started using Application.start/2 (and functions on top of that, such as Application.ensure_started/2). This function should start the top-level process of the application (which should be the top supervisor of the application's supervision tree if the application follows the OTP design principles around supervision).

start_type defines how the application is started:

  • :normal - used if the startup is a normal startup or if the application is distributed and is started on the current node because of a failover from another node and the application specification key :start_phases is :undefined.
  • {:takeover, node} - used if the application is distributed and is started on the current node because of a failover on the node node.
  • {:failover, node} - used if the application is distributed and is started on the current node because of a failover on node node, and the application specification key :start_phases is not :undefined.

start_args are the arguments passed to the application in the :mod specification key (e.g., mod: {MyApp, [:my_args]}).

This function should either return {:ok, pid} or {:ok, pid, state} if startup is successful. pid should be the PID of the top supervisor. state can be an arbitrary term, and if omitted will default to []; if the application is later stopped, state is passed to the stop/1 callback (see the documentation for the c:stop/1 callback for more information).

use Application provides no default implementation for the start/2 callback.

Callback implementation for Application.start/2.

Link to this function

to_timestamp(id, mode \\ :absolute) View Source (since 0.4.0)
to_timestamp(id(), :absolute | :relative) :: timestamp()

Extracts the timestamp/0 out of the given id/0.

By default to_timestamp/2 selects :absolute, which will calculate the actual timestamp/0.
If you instead want a timestamp/0 relative to your epoch/0, use :relative.

Examples

iex> Aphora.to_timestamp("0eHHz1--abHr", :relative)
912_988_800_000

iex> Aphora.to_timestamp("-0dqbzfF----")
1_560_508_218_202