Aphora v1.0.0 Aphora View Source
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 Bittimestamp/0with millisecond precision. It gives up to 1115 years with a custom epoch.5 Bitdatacenter/0allows up to32different datacenters.10 Bitworker/0allows up to1_024different processes/workers/machines within a datacenter.12 Bitcounter/0allows for4_096uniqueid/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.
Link to this section Types
counter()
View Source
(since 0.2.0)
counter() :: non_neg_integer()
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.
datacenter()
View Source
(since 0.1.0)
datacenter() :: non_neg_integer()
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.
epoch()
View Source
(since 0.1.0)
epoch() :: non_neg_integer()
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.
id()
View Source
(since 0.1.0)
id() :: String.t()
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:
45 Bittimestamp/0.5 Bitdatacenter/0.10 Bitworker/0.12 Bitcounter/0.
timestamp()
View Source
(since 0.1.0)
timestamp() :: non_neg_integer()
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.
worker()
View Source
(since 0.1.0)
worker() :: non_neg_integer()
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
new_id() View Source (since 0.1.0)
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}
start(type, args) View Source
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_phasesis:undefined.{:takeover, node}- used if the application is distributed and is started on the current node because of a failover on the nodenode.{:failover, node}- used if the application is distributed and is started on the current node because of a failover on nodenode, and the application specification key:start_phasesis 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.
to_timestamp(id, mode \\ :absolute) View Source (since 0.4.0)
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